001/* Utilities used to manipulate classes 002 003 Copyright (c) 2003-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.util; 029 030import java.io.File; 031import java.io.IOException; 032import java.net.JarURLConnection; 033import java.net.URL; 034import java.util.Enumeration; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.jar.JarEntry; 038import java.util.jar.JarFile; 039 040/////////////////////////////////////////////////////////////////// 041//// ClassUtilities 042 043/** 044 A collection of utilities for manipulating classes. 045 These utilities do not depend on any other ptolemy.* packages. 046 047 048 @author Christopher Hylands 049 @version $Id$ 050 @since Ptolemy II 4.0 051 @Pt.ProposedRating Green (cxh) 052 @Pt.AcceptedRating Green (cxh) 053 */ 054public class ClassUtilities { 055 /** Instances of this class cannot be created. 056 */ 057 private ClassUtilities() { 058 } 059 060 /////////////////////////////////////////////////////////////////// 061 //// public methods //// 062 063 /** Return the directories in a jar URI, relative to the jar entry, 064 * if any. . 065 * Jar URLS have several forms, the most common being: 066 * <code>jar:file:///foo/bar.jar/!/bif/baz</code>, which means that 067 * the jar file /foo/bar.jar has a directory or file name bif/baz. 068 * If such a file is passed to this method, then any directories 069 * in the jar file directory bif/baz will be returned. 070 * @param jarURL The Jar URL for which we are to look for directories. 071 * @return An list of Strings that name the directories 072 * @exception IOException If opening the connection fails or if 073 * getting the jar file from the connection fails 074 */ 075 public static List jarURLDirectories(URL jarURL) throws IOException { 076 List directories = new LinkedList(); 077 JarURLConnection connection = (JarURLConnection) jarURL 078 .openConnection(); 079 String jarEntryName = connection.getEntryName(); 080 if (jarEntryName.endsWith("/")) { 081 jarEntryName = jarEntryName.substring(0, jarEntryName.length() - 1); 082 } 083 JarFile jarFile = connection.getJarFile(); 084 Enumeration entries = jarFile.entries(); 085 while (entries.hasMoreElements()) { 086 JarEntry entry = (JarEntry) entries.nextElement(); 087 String name = entry.getName(); 088 int jarEntryIndex = name.indexOf(jarEntryName + "/"); 089 int jarEntrySlashIndex = jarEntryIndex + jarEntryName.length() + 1; 090 091 int nextSlashIndex = name.indexOf("/", jarEntrySlashIndex); 092 int lastSlashIndex = name.indexOf("/", jarEntrySlashIndex); 093 094 if (jarEntryIndex > -1 && jarEntrySlashIndex > -1 095 && nextSlashIndex > -1 && nextSlashIndex == lastSlashIndex 096 && nextSlashIndex == name.length() - 1 097 && entry.isDirectory()) { 098 directories.add(name); 099 } 100 } 101 return directories; 102 } 103 104 /** Lookup a jar URL and return the resource. 105 106 * A resource is a file such as a class file or image file that 107 * is found in the classpath. A jar URL is a URL that refers to 108 * a resource in a jar file. For example, 109 * <code>file://./foo.jar!/a/b/c.class</code> is a jar URL that 110 * refers to the <code>a/b/c.class</code> resource in 111 * <code>foo.jar</code>. If this method is called with 112 * <code>file://./foo.jar!/a/b/c.class</code> then it will return 113 * <code>a/b/c.class</code> if <code>a/b/c.class</code> can be 114 * found as a resource in the class loader that loaded this class 115 * (ptolemy.util.ClassUtilities). If the resource cannot be found, 116 * then an IOException is thrown. If the jarURLString parameter 117 * does not contain <code>!/</code>, then return null. 118 * Note that everything before the <code>!/</code> is removed before 119 * searching the classpath. 120 * 121 * <p>This method is necessary because Web Start uses jar URL, and 122 * there are some cases where if we have a jar URL, then we may 123 * need to strip off the jar:<i>url</i>!/ part so that we can 124 * search for the {entry} as a resource. 125 * 126 * @param jarURLString The string containing the jar URL. 127 * If no resource is found and the string contains a "#" then the text 128 * consisting of the # and the remaining text is removed and the shorter 129 * string is used as a search pattern. 130 * @return The resource, if any. If the spec string does not 131 * contain <code>!/</code>, then return null. 132 * @exception IOException If this method cannot convert the specification 133 * to a URL. 134 * @see java.net.JarURLConnection 135 */ 136 public static URL jarURLEntryResource(String jarURLString) 137 throws IOException { 138 // At first glance, it would appear that this method could appear 139 // in specToURL(), but the problem is that specToURL() creates 140 // a new URL with the spec, so it only does further checks if 141 // the URL is malformed. Unfortunately, in Web Start applications 142 // the URL will often refer to a resource in another jar file, 143 // which means that the jar url is not malformed, but there is 144 // no resource by that name. Probably specToURL() should return 145 // the resource after calling new URL(). 146 int jarEntry = jarURLString.indexOf("!/"); 147 148 if (jarEntry == -1) { 149 jarEntry = jarURLString.indexOf("!\\"); 150 151 if (jarEntry == -1) { 152 return null; 153 } 154 } 155 156 try { 157 // !/ means that this could be in a jar file. 158 String entry = jarURLString.substring(jarEntry + 2); 159 160 // We might be in the Swing Event thread, so 161 // Thread.currentThread().getContextClassLoader() 162 // .getResource(entry) probably will not work. 163 Class refClass = Class.forName("ptolemy.util.ClassUtilities"); 164 URL entryURL = refClass.getClassLoader().getResource(entry); 165 if (entryURL == null && entry.indexOf("#") != -1) { 166 // If entry contains a #, then strip it off and try again. 167 entryURL = refClass.getClassLoader() 168 .getResource(entry.substring(0, entry.indexOf("#"))); 169 } 170 return entryURL; 171 } catch (Exception ex) { 172 // IOException constructor does not take a cause, so we add it. 173 IOException ioException = new IOException( 174 "Cannot find \"" + jarURLString + "\"."); 175 ioException.initCause(ex); 176 throw ioException; 177 } 178 } 179 180 /** Lookup a URL in the classpath, but search up the classpath 181 * for directories named src. 182 * This method is useful for IDEs such as Eclipse where 183 * the default is to place .class files in a separate directory 184 * from the .java files. 185 * 186 * <p>Before calling this method, call ClassLoader.getResource() 187 * because ClassLoader.getResource() is presumably faster.</p> 188 * 189 * @param sourceURLString The string containing the URL. 190 * @return The resource, if any. If the spec string does not 191 * contain <code>!/</code>, then return null. 192 * @exception IOException If this method cannot convert the specification 193 * to a URL. 194 * @see java.net.JarURLConnection 195 */ 196 public static URL sourceResource(String sourceURLString) 197 throws IOException { 198 // FIXME: Maybe only allow relative paths? 199 200 // Hmm. Might be Eclipse, where sadly the 201 // .class files are often in a separate directory 202 // than the .java files. So, we look at the CLASSPATH 203 // and for each element that names a directory, traverse 204 // the parents directories and look for adjacent directories 205 // that contain a "src" directory. For example if 206 // the classpath contains "kepler/ptolemy/target/classes/", 207 // then we will find kepler/ptolemy/src and return it 208 // as a URL. 209 210 // First time through, search each element of the CLASSPATH that names 211 // a directory 212 if (_sourceDirectories == null) { 213 List<File> sourceDirectories = new LinkedList<File>(); 214 String classPath[] = StringUtilities.getProperty("java.class.path") 215 .split(StringUtilities.getProperty("path.separator")); 216 for (String element : classPath) { 217 File directory = new File(element); 218 if (directory.isDirectory()) { 219 // We have a potential winner. 220 while (directory != null) { 221 File sourceDirectory = new File(directory, "src"); 222 if (sourceDirectory.isDirectory()) { 223 sourceDirectories.add(sourceDirectory); 224 break; 225 } 226 directory = directory.getParentFile(); 227 } 228 } 229 } 230 // Avoid FindBugs: LI: Incorrect lazy initialization and update of static field. 231 _sourceDirectories = sourceDirectories; 232 } 233 234 // Search _sourceDirectories for sourceURLString 235 for (File sourceDirectory : _sourceDirectories) { 236 File sourceFile = new File(sourceDirectory, sourceURLString); 237 if (sourceFile.exists()) { 238 return sourceFile.getCanonicalFile().toURI().toURL(); 239 } 240 } 241 return null; 242 } 243 244 /** Get the resource. 245 * If the current thread has a non-null context class loader, 246 * then use it to get the resource. Othewise, get the 247 * NamedObj class and use that to get the resource. 248 * This is necessary because Thread.currentThread() can return null. 249 * @param spec The string to be found as a resource. 250 * @return The URL 251 */ 252 public static URL getResource(String spec) { 253 URL url = null; 254 // Unfortunately, Thread.currentThread().getContextClassLoader() can 255 // return null. This happened when 256 // $CLASSPATH/ptolemy/actor/lib/vertx/demo/TokenTransmissionTime/Sender.xml 257 // failed and subsequent calls to ConfigurationApplication.openModelOrEntity() would 258 // fail because the configuration could not be found. 259 // So, we have our own getResource() that handles this. 260 261 ClassLoader classLoader = Thread.currentThread() 262 .getContextClassLoader(); 263 if (classLoader != null) { 264 url = classLoader.getResource(spec); 265 } else { 266 classLoader = ClassLoader.getSystemClassLoader(); 267 if (classLoader != null) { 268 url = classLoader.getResource(spec); 269 } else { 270 try { 271 Class refClass = Class 272 .forName("ptolemy.util.ClassUtilities"); 273 url = refClass.getClassLoader().getResource(spec); 274 } catch (Exception ex) { 275 throw new RuntimeException( 276 "Failed to get system class loader" 277 + " and failed to get the Class for ClassUtilities", 278 ex); 279 } 280 } 281 } 282 return url; 283 } 284 285 /** Given a dot separated classname, return the jar file or directory 286 * where the class can be found. 287 * @param necessaryClass The dot separated class name, for example 288 * "ptolemy.util.ClassUtilities" 289 * @return If the class can be found as a resource, return the 290 * directory or jar file where the necessary class can be found. 291 * otherwise, return null. If the resource is found in a directory, 292 * then the return value will always have forward slashes, it will 293 * never use backslashes. 294 */ 295 public static String lookupClassAsResource(String necessaryClass) { 296 // This method is called from copernicus.kernel.GeneratorAttribute 297 // and actor.lib.python.PythonScript. We moved it here 298 // to avoid dependencies. 299 String necessaryResource = StringUtilities.substitute(necessaryClass, 300 ".", "/") + ".class"; 301 302 URL necessaryURL = ClassUtilities.getResource(necessaryResource); 303 304 if (necessaryURL == null) { 305 necessaryResource = StringUtilities.substitute(necessaryClass, ".", 306 "/") + ".xml"; 307 necessaryURL = ClassUtilities.getResource(necessaryResource); 308 } 309 if (necessaryURL != null) { 310 String resourceResults = necessaryURL.getFile(); 311 312 // Strip off the file:/ and the necessaryResource. 313 if (resourceResults.startsWith("file:/")) { 314 resourceResults = resourceResults.substring(6); 315 } 316 317 // Strip off the name of the resource we were looking for 318 // so that we are left with the directory or jar file 319 // it is in 320 resourceResults = resourceResults.substring(0, 321 resourceResults.length() - necessaryResource.length()); 322 323 // Strip off the trailing !/ 324 if (resourceResults.endsWith("!/")) { 325 resourceResults = resourceResults.substring(0, 326 resourceResults.length() - 2); 327 } 328 329 // Unfortunately, under Windows, URL.getFile() may 330 // return things like /c:/ptII, so we create a new 331 // File and get its path, which will return c:\ptII 332 File resourceFile = new File(resourceResults); 333 334 // Convert backslashes 335 String sanitizedResourceName = StringUtilities 336 .substitute(resourceFile.getPath(), "\\", "/"); 337 return sanitizedResourceName; 338 } 339 340 return null; 341 } 342 343 /** A list of directories that end with "src" that are found in 344 * in the paths of individual elements in the classpath. 345 */ 346 private static List<File> _sourceDirectories = null; 347}