001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit http://imagingbook.com for additional details. 008 *******************************************************************************/ 009package imagingbook.lib.util; 010 011import java.io.File; 012import java.io.IOException; 013import java.io.InputStream; 014import java.net.URI; 015import java.net.URISyntaxException; 016import java.net.URL; 017import java.nio.file.FileSystem; 018import java.nio.file.FileSystemNotFoundException; 019import java.nio.file.FileSystems; 020import java.nio.file.Files; 021import java.nio.file.Path; 022import java.nio.file.Paths; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.Iterator; 026import java.util.List; 027import java.util.stream.Stream; 028 029import ij.IJ; 030import ij.ImagePlus; 031import ij.io.Opener; 032 033/** 034 * This class defines static methods for accessing resources. 035 * What makes things somewhat complex is the requirement that 036 * we want to retrieve resources located in the file system or 037 * contained inside a JAR file. 038 * 039 * A typical URI for a JAR-embedded file: 040 * "jar:file:/C:/PROJEC~2/parent/IM1D84~1/ImageJ/jars/jarWithResources.jar!/jarWithResouces/resources/clown.jpg" 041 * 042 * @author W. Burger 043 * @version 2016/06/04 044 * 045 */ 046public class ResourceUtils { 047 048 /** 049 * Determines if the specified class was loaded from 050 * a JAR file or a .class file in the file system. 051 * 052 * @param clazz the class 053 * @return true if contained in a JAR file, false otherwise 054 */ 055 public static boolean isInsideJar(Class<?> clazz) { 056 URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); 057 String path = url.getPath(); 058 File file = new File(path); 059 return file.isFile(); 060 } 061 062 /** 063 * Finds the URI for a resource relative to a specified class. 064 * The resource may be located in the file system or 065 * inside a JAR file. 066 * 067 * @param clazz the anchor class 068 * @param relPath the resource path relative to the anchor class 069 * @return the URI or {@code null} if the resource was not found 070 */ 071 public static URI getResourceUri(Class<?> clazz, String relPath) { 072 URI uri = null; 073 if (isInsideJar(clazz)) { 074 String classPath = clazz.getProtectionDomain().getCodeSource().getLocation().getFile(); 075 //String packagePath = clazz.getPackage().getName().replace('.', File.separatorChar); 076 String packagePath = clazz.getPackage().getName().replace('.', '/'); 077 String compPath = "jar:file:" + classPath + "!/" + packagePath + "/" + relPath; 078 try { 079 uri = new URI(compPath); 080 } catch (URISyntaxException e) { 081 throw new RuntimeException("getResourceURI: " + e.toString()); 082 } 083 } 084 else { // regular file path 085 try { 086 uri = clazz.getResource(relPath).toURI(); 087 } catch (Exception e) { 088 throw new RuntimeException("getResourceURI: " + e.toString()); 089 } 090 } 091 return uri; 092 } 093 094 /** 095 * Find the path to a resource relative to the location of class c. 096 * Example: Assume class C was loaded from file someLocation/C.class 097 * and there is a subfolder someLocation/resources/ that contains 098 * an image 'lenna.jpg'. Then the absolute path to this image 099 * is obtained by 100 * String path = getResourcePath(C.class, "resources/lenna.jpg"); 101 * 102 * 2016-06-03: modified to return proper path to resource inside 103 * a JAR file. 104 * 105 * @param clazz anchor class 106 * @param relPath the path of the resource to be found (relative to the location of the anchor class) 107 * @return the path to the specified resource 108 */ 109 public static Path getResourcePath(Class<?> clazz, String relPath) { 110 URI uri = getResourceUri(clazz, relPath); 111 if (uri != null) { 112 return uriToPath(uri); 113 } 114 else { 115 return null; 116 } 117 } 118 119 /** 120 * Converts an URI to a Path for locations that are either 121 * in the file system or inside a JAR file. 122 * 123 * @param uri the specified location 124 * @return the associated path 125 */ 126 public static Path uriToPath(URI uri) { 127 Path path = null; 128 String scheme = uri.getScheme(); 129 switch (scheme) { 130 case "jar": { // resource inside JAR file 131 FileSystem fs = null; 132 try { // check if this FileSystem already exists 133 fs = FileSystems.getFileSystem(uri); 134 } catch (FileSystemNotFoundException e) { 135 // that's OK to happen, the file system is not created automatically 136 } 137 138 if (fs == null) { // must not create the file system twice 139 try { 140 fs = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()); 141 } catch (IOException e) { 142 throw new RuntimeException("uriToPath: " + e.toString()); 143 } 144 } 145 146 String ssp = uri.getSchemeSpecificPart(); 147 int startIdx = ssp.lastIndexOf('!'); 148 String inJarPath = ssp.substring(startIdx + 1); // in-Jar path (after the last '!') 149 path = fs.getPath(inJarPath); 150 break; 151 } 152 case "file": { // resource in ordinary file system 153 path = Paths.get(uri); 154 break; 155 } 156 default: 157 throw new IllegalArgumentException("Cannot handle this URI type: " + scheme); 158 } 159 return path; 160 } 161 162 163 public static Path[] listResources(URI uri) { 164 return listResources(uriToPath(uri)); 165 } 166 167 168 /** 169 * Method to obtain the paths to all files in a directory specified 170 * by a path. This should work in an ordinary file system 171 * as well as a (possibly nested) JAR file. 172 * 173 * @param path path to a directory (may be contained in a JAR file) 174 * @return a sequence of paths or {@code null} if the specified path 175 * is not a directory 176 */ 177 public static Path[] listResources(Path path) { 178 // with help from http://stackoverflow.com/questions/1429172/how-do-i-list-the-files-inside-a-jar-file, #10 179 if (!Files.isDirectory(path)) { 180 throw new IllegalArgumentException("path is not a directory: " + path.toString()); 181 } 182 183 List<Path> pathList = new ArrayList<Path>(); 184 Stream<Path> walk = null; 185 try { 186 walk = Files.walk(path, 1); 187 } catch (IOException e) { 188 e.printStackTrace(); 189 } 190 191 for (Iterator<Path> it = walk.iterator(); it.hasNext();){ 192 Path p = it.next(); 193 if (Files.isRegularFile(p) && Files.isReadable(p)) { 194 pathList.add(p); 195 } 196 } 197 walk.close(); 198 return pathList.toArray(new Path[0]); 199 } 200 201 /** 202 * Use this method to obtain the paths to all files in a directory located 203 * relative to the specified class. This should work in an ordinary file system 204 * as well as a (possibly nested) JAR file. 205 * 206 * @param clazz class whose source location specifies the root 207 * @param relPath path relative to the root 208 * @return a sequence of paths or {@code null} if the specified path is not a directory 209 */ 210 public static Path[] listResources(Class<?> clazz, String relPath) { 211 return listResources(getResourceUri(clazz, relPath)); 212 } 213 214 215 /** 216 * Opens an image from the specified resource. 217 * If the resource is contained inside a JAR file, it is first 218 * extracted to a temporary file and subsequently opened 219 * with ImageJ's {@code Opener} class. 220 * 221 * @param clazz the anchor class 222 * @param resDir the directory relative to the anchor class 223 * @param resName the (file) name of the image resource 224 * @return the opened image or {@code null} if not successful. 225 */ 226 public static ImagePlus openImageFromResource(Class<?> clazz, String resDir, String resName) { 227 URI uri = getResourceUri(clazz, resDir + resName); 228 if (uri == null) { 229 IJ.error("resource not found: " + clazz.getName() + " | " + resDir + " | " + resName); 230 return null; 231 } 232 233 ImagePlus im = null; 234 235 String scheme = uri.getScheme(); 236 switch (scheme) { 237 case "file": { // resource in ordinary file system 238 Path path = Paths.get(uri); 239 im = new Opener().openImage(path.toString()); 240 break; 241 } 242 case "jar": { // resource inside JAR 243 // create a temporary file: 244 String ext = FileUtils.getFileExtension(resName); 245 File tmpFile = null; 246 try { 247 tmpFile = File.createTempFile("img", "." + ext); 248 tmpFile.deleteOnExit(); 249 } 250 catch (IOException e) { 251 throw new RuntimeException("Could not create temporary file"); 252 } 253 254 //IJ.log("copying to tmp file: " + tmpFile.getPath()); 255 String relPath = resDir + resName; 256 InputStream inStrm = clazz.getResourceAsStream(relPath); 257 258 try { 259 FileUtils.copyToFile(inStrm, tmpFile); 260 } catch (IOException e) { 261 throw new RuntimeException("Could not copy stream to temporary file"); 262 } 263 im = new Opener().openImage(tmpFile.getPath()); 264 if (im != null) { 265 im.setTitle(resName); 266 } 267 tmpFile.delete(); 268 break; 269 } 270 default: 271 throw new IllegalArgumentException("Cannot handle this resource type: " + scheme); 272 } 273 return im; 274 } 275 276 277 278// /** 279// * Checks 'by name' of a particular resource exists. 280// * 281// * @param classname name of the class, e.g. {@literal imagingbook.lib.util.FileUtils} 282// * @param recourcePath path (relative to the location of the class) to the specified resource 283// * @return {@code true} if the specified resource was found, {@code false} otherwise 284// */ 285// public static boolean checkResource(String classname, String recourcePath) { 286// String logStr = " checking resource " + classname + ":" + recourcePath + " ... "; 287// try { 288// //if (Class.forName(classname).getResourceAsStream(recourcePath) != null) { 289// if (Class.forName(classname).getResource(recourcePath)!= null) { 290// IJ.log(logStr + "OK"); 291// return true; 292// } 293// } catch (Exception e) { } 294// IJ.log(logStr + "ERROR"); 295// return false; 296// } 297 298}