OLD | NEW |
(Empty) | |
| 1 /* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved |
| 2 * |
| 3 * This program is distributed under the terms of |
| 4 * the GNU General Public License Version 2. See the LICENSE file |
| 5 * at the top of the source tree. |
| 6 */ |
| 7 package org.pantsbuild.jmake; |
| 8 |
| 9 import java.io.File; |
| 10 import java.io.FilenameFilter; |
| 11 import java.io.IOException; |
| 12 import java.net.URL; |
| 13 import java.net.URLClassLoader; |
| 14 import java.util.ArrayList; |
| 15 import java.util.Collection; |
| 16 import java.util.LinkedHashMap; |
| 17 import java.util.List; |
| 18 import java.util.Locale; |
| 19 import java.util.Map; |
| 20 import java.util.Set; |
| 21 import java.util.StringTokenizer; |
| 22 import java.util.zip.ZipEntry; |
| 23 import java.util.zip.ZipFile; |
| 24 |
| 25 /** |
| 26 * An instance of this class represents a class path, on which binary classes ca
n be looked up. |
| 27 * It also provides several static methods to create and utilize several specifi
c class paths used |
| 28 * throughout jmake. |
| 29 * |
| 30 * @author Misha Dmitriev |
| 31 * 12 October 2004 |
| 32 */ |
| 33 public class ClassPath { |
| 34 |
| 35 private PathEntry[] paths; |
| 36 private static ClassPath projectClassPath; // Class path (currently it can
contain only JARs) containing sourceless project classes. |
| 37 // See also the comment to standardClassPath. |
| 38 private static ClassPath standardClassPath; // Class path that the user spe
cifies via the -classpath option. A sum of the |
| 39 // standardClassPath, the projectClassPath, and the virtualPath is passed to
the compiler. Each of these |
| 40 // class paths are also used to look up non-project superclasses/superinterf
aces of |
| 41 // project classes. |
| 42 private static ClassPath bootClassPath, extClassPath; // Class paths that b
y default are sun.boot.class.path and all JARs on |
| 43 // java.ext.class.path, respectively. They are used to look up non-project |
| 44 // superclasses/superinterfaces of project classes. Their values can be chan
ged using |
| 45 // setBootClassPath() and setExtDirs(). |
| 46 private static ClassPath virtualPath; // Class path that the user specifies
via the -vpath option. |
| 47 private static String compilerUserClassPath; // Class path to be passed to t
he compiler; equals to the sum of values of parameters of |
| 48 // setClassPath() and setProjectClassPath() methods. |
| 49 private static String standardClassPathStr, projectClassPathStr, bootClass
PathStr, extDirsStr, |
| 50 virtualPathStr; |
| 51 private static Map<String,ClassInfo> classCache; |
| 52 |
| 53 |
| 54 static { |
| 55 resetOnFinish(); |
| 56 } |
| 57 |
| 58 /** |
| 59 * Needed since some environments, e.g. NetBeans, can keep jmake classes in
memory |
| 60 * permanently. Thus unchanged class paths from previous, possibly unrelated
invocations |
| 61 * of jmake, may interfere with the current settings. |
| 62 */ |
| 63 public static void resetOnFinish() { |
| 64 projectClassPath = standardClassPath = bootClassPath = extClassPath = vi
rtualPath = |
| 65 null; |
| 66 compilerUserClassPath = null; |
| 67 standardClassPathStr = projectClassPathStr = bootClassPathStr = |
| 68 extDirsStr = virtualPathStr = null; |
| 69 classCache = new LinkedHashMap<String,ClassInfo>(); |
| 70 } |
| 71 |
| 72 public static void setClassPath(String value) throws PublicExceptions.Invali
dCmdOptionException { |
| 73 standardClassPathStr = value; |
| 74 standardClassPath = new ClassPath(value, false); |
| 75 } |
| 76 |
| 77 public static void setProjectClassPath(String value) throws PublicExceptions
.InvalidCmdOptionException { |
| 78 projectClassPathStr = value; |
| 79 projectClassPath = new ClassPath(value, true); |
| 80 } |
| 81 |
| 82 public static void setBootClassPath(String value) throws PublicExceptions.In
validCmdOptionException { |
| 83 bootClassPathStr = value; |
| 84 bootClassPath = new ClassPath(value, false); |
| 85 } |
| 86 |
| 87 public static void setExtDirs(String value) throws PublicExceptions.InvalidC
mdOptionException { |
| 88 extDirsStr = value; |
| 89 // Extension class path needs special handling, since it consists of dir
ectories, which contain .jars |
| 90 // So we need to find all these .jars in all these dirs and add them to
extClassPathElementList |
| 91 List<String> extClassPathElements = new ArrayList<String>(); |
| 92 for (StringTokenizer tok = |
| 93 new StringTokenizer(value, File.pathSeparator); tok.hasMoreToken
s();) { |
| 94 File extDir = new File(tok.nextToken()); |
| 95 String[] extJars = extDir.list(new FilenameFilter() { |
| 96 |
| 97 public boolean accept(File dir, String name) { |
| 98 name = name.toLowerCase(Locale.ENGLISH); |
| 99 return name.endsWith(".zip") || name.endsWith(".jar"); |
| 100 } |
| 101 }); |
| 102 if (extJars == null) { |
| 103 continue; |
| 104 } |
| 105 for (int i = 0; i < extJars.length; i++) { |
| 106 extClassPathElements.add(extDir + File.separator + extJars[i]); |
| 107 } |
| 108 } |
| 109 extClassPath = new ClassPath(extClassPathElements, false); |
| 110 } |
| 111 |
| 112 public static void setVirtualPath(String value) throws PublicExceptions.Inva
lidCmdOptionException { |
| 113 if (value == null) { |
| 114 throw new PublicExceptions.InvalidCmdOptionException("null argument"
); |
| 115 } |
| 116 StringTokenizer st = new StringTokenizer(value, File.pathSeparator); |
| 117 while (st.hasMoreElements()) { |
| 118 String dir = st.nextToken(); |
| 119 if ( ! (new File(dir)).isDirectory()) { |
| 120 throw new PublicExceptions.InvalidCmdOptionException("Virtual pa
th must contain only directories." + |
| 121 " Entry " + dir + " is not a directory."); |
| 122 } |
| 123 } |
| 124 virtualPathStr = value; |
| 125 virtualPath = new ClassPath(value, false); |
| 126 } |
| 127 |
| 128 public static void initializeAllClassPaths() { |
| 129 // First set the compiler class path value |
| 130 if (standardClassPathStr == null && projectClassPathStr == null) { |
| 131 compilerUserClassPath = "."; |
| 132 } else if (standardClassPathStr == null) { |
| 133 compilerUserClassPath = projectClassPathStr; |
| 134 } else if (projectClassPathStr == null) { |
| 135 compilerUserClassPath = standardClassPathStr; |
| 136 } else { |
| 137 compilerUserClassPath = |
| 138 standardClassPathStr + File.pathSeparator + projectClassPath
Str; |
| 139 } |
| 140 |
| 141 if (virtualPathStr != null) { |
| 142 compilerUserClassPath += File.pathSeparator + virtualPathStr; |
| 143 } |
| 144 |
| 145 if (standardClassPathStr == null) { |
| 146 try { |
| 147 String tmp = "."; |
| 148 if (virtualPathStr != null) { |
| 149 tmp += File.pathSeparator + virtualPathStr; |
| 150 } |
| 151 standardClassPath = new ClassPath(tmp, false); |
| 152 } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Should
not happen */ } |
| 153 } |
| 154 if (projectClassPathStr == null) { |
| 155 projectClassPath = new ClassPath(); |
| 156 } |
| 157 |
| 158 // Create the core class path as a combination of sun.boot.class.path an
d java.ext.dirs contents |
| 159 if (bootClassPathStr == null) { |
| 160 try { |
| 161 bootClassPath = |
| 162 new ClassPath(System.getProperty("sun.boot.class.path"),
false); |
| 163 } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Shouldn
't happen */ } |
| 164 // bootClassPathStr should remain null, so that nothing that the user di
dn't specify is passed to the compiler |
| 165 } |
| 166 |
| 167 if (extDirsStr == null) { |
| 168 try { |
| 169 setExtDirs(System.getProperty("java.ext.dirs")); |
| 170 } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Shouldn
't happen */ } |
| 171 // extDirsStr should remain null, so that nothing that the user didn
't specify is passed to the compiler |
| 172 extDirsStr = null; |
| 173 } |
| 174 } |
| 175 |
| 176 /** Never returns null - if classpath wasn't set explicitly, returns "." */ |
| 177 public static String getCompilerUserClassPath() { |
| 178 return compilerUserClassPath; |
| 179 } |
| 180 |
| 181 /** Will return null if boot class path wasn't explicitly specified */ |
| 182 public static String getCompilerBootClassPath() { |
| 183 return bootClassPathStr; |
| 184 } |
| 185 |
| 186 /** Will return null if extdirs weren't explicitly specified */ |
| 187 public static String getCompilerExtDirs() { |
| 188 return extDirsStr; |
| 189 } |
| 190 |
| 191 /** Will return null if virtualPath wasn't explicitly specified */ |
| 192 public static String getVirtualPath() { |
| 193 return virtualPathStr; |
| 194 } |
| 195 |
| 196 /** |
| 197 * For the given class return the list of all of its superclasses (excluding
Object), that can be loaded from |
| 198 * projectClassPath or standardClassPath, plus the first superclass that can
be loaded from coreClassPath. |
| 199 * The latter is an optimization based on the assumption that core classes n
ever change, or rather the programmer |
| 200 * will recompile everything when they switch to a new JDK version. The opti
mization prevents us from wasting time |
| 201 * repeatedly loading the same sets of core classes. |
| 202 */ |
| 203 public static void getSuperclasses(String className, |
| 204 Collection<String> res, PCDManager pcdm) { |
| 205 int iterNo = 0; |
| 206 while (!"java/lang/Object".equals(className)) { |
| 207 ClassInfo ci = getClassInfoForName(className, pcdm); |
| 208 if (ci == null) { |
| 209 return; |
| 210 } |
| 211 if (iterNo++ > 0) { |
| 212 res.add(ci.name); |
| 213 } |
| 214 className = ci.superName; |
| 215 } |
| 216 } |
| 217 |
| 218 /** |
| 219 * Add to the given set the names of all interfaces implemented by the given
class, that can be loaded from |
| 220 * projectClassPath or standardClassPath, plus the first interface on each b
ranch that can be loaded from |
| 221 * coreClassPath. It's the same optimization as in getSuperclasses(). |
| 222 */ |
| 223 public static void addAllImplementedInterfaceNames(String className, |
| 224 Set<String> intfSet, PCDManager pcdm) { |
| 225 if ("java/lang/Object".equals(className)) { |
| 226 return; |
| 227 } |
| 228 ClassInfo ci = getClassInfoForName(className, pcdm); |
| 229 if (ci == null) { |
| 230 return; |
| 231 } |
| 232 String[] interfaces = ci.interfaces; |
| 233 if (interfaces != null) { |
| 234 for (int i = 0; i < interfaces.length; i++) { |
| 235 intfSet.add(interfaces[i]); |
| 236 addAllImplementedInterfaceNames(interfaces[i], intfSet, pcdm); |
| 237 } |
| 238 } |
| 239 |
| 240 String superName = ci.superName; |
| 241 if (superName != null) { |
| 242 addAllImplementedInterfaceNames(superName, intfSet, pcdm); |
| 243 } |
| 244 } |
| 245 |
| 246 public static String[] getProjectJars() { |
| 247 if (projectClassPath == null || projectClassPath.isEmpty()) { |
| 248 return null; |
| 249 } |
| 250 PathEntry paths[] = projectClassPath.paths; |
| 251 String[] ret = new String[paths.length]; |
| 252 for (int i = 0; i < paths.length; i++) { |
| 253 ret[i] = paths[i].toString(); |
| 254 } |
| 255 return ret; |
| 256 } |
| 257 |
| 258 public static ClassInfo getClassInfoForName(String className, PCDManager pcd
m) { |
| 259 ClassInfo info = classCache.get(className); |
| 260 if (info != null) { |
| 261 return info; |
| 262 } |
| 263 |
| 264 byte buf[] = bootClassPath.getBytesForClass(className); |
| 265 if (buf == null) { |
| 266 buf = extClassPath.getBytesForClass(className); |
| 267 } |
| 268 if (buf == null) { |
| 269 buf = standardClassPath.getBytesForClass(className); |
| 270 } |
| 271 if (buf == null) { |
| 272 buf = projectClassPath.getBytesForClass(className); |
| 273 } |
| 274 if (buf == null) { |
| 275 return null; |
| 276 } |
| 277 |
| 278 info = new ClassInfo(buf, pcdm, className); |
| 279 classCache.put(className, info); |
| 280 return info; |
| 281 } |
| 282 |
| 283 /** Returns the class loader that would load classes from the given class pa
th. */ |
| 284 public static ClassLoader getClassLoaderForPath(String classPath) throws Exc
eption { |
| 285 boolean isWindows = System.getProperty("os.name").startsWith("Win"); |
| 286 ClassPath cp = new ClassPath(classPath, false); |
| 287 PathEntry[] paths = cp.paths; |
| 288 URL[] urls = new URL[paths.length]; |
| 289 for (int i = 0; i < paths.length; i++) { |
| 290 String dirOrJar = paths[i].toString(); |
| 291 if (!(dirOrJar.startsWith("file://") || dirOrJar.startsWith("http://
"))) { |
| 292 // On Windows, if I have path specified as "file://c:\...", (i.e
. with the drive name) URLClassLoader works |
| 293 // unbelievably slow. However, if an additional slash is added,
like : "file:///c:\...", the speed becomes |
| 294 // normal. To me it looks like a bug, but, anyway, I am taking m
easure here. |
| 295 if (isWindows && dirOrJar.charAt(1) == ':') { |
| 296 dirOrJar = "/" + dirOrJar; |
| 297 } |
| 298 dirOrJar = new File(dirOrJar).toURI().toString(); |
| 299 } |
| 300 if (!(dirOrJar.endsWith(".jar") || dirOrJar.endsWith(".zip") || dirO
rJar.endsWith(File.separator))) { |
| 301 dirOrJar += File.separator; // So that URLClassLoader correctly
handles it as a directory |
| 302 } |
| 303 urls[i] = new URL(dirOrJar); |
| 304 } |
| 305 |
| 306 return new URLClassLoader(urls); |
| 307 //} catch (java.net.MalformedURLException e) { |
| 308 |
| 309 //} |
| 310 } |
| 311 |
| 312 |
| 313 // ------------------------------------ Private implementation -------------
------------------------------- |
| 314 private ClassPath() { |
| 315 paths = new PathEntry[0]; |
| 316 } |
| 317 |
| 318 private ClassPath(String classPath, boolean isJarOnly) throws PublicExceptio
ns.InvalidCmdOptionException { |
| 319 if (classPath == null) { |
| 320 throw new PublicExceptions.InvalidCmdOptionException("null argument"
); |
| 321 } |
| 322 List<String> vec = new ArrayList<String>(); |
| 323 |
| 324 for (StringTokenizer tok = |
| 325 new StringTokenizer(classPath, File.pathSeparator); tok.hasMoreT
okens();) { |
| 326 String path = tok.nextToken(); |
| 327 vec.add(path); |
| 328 } |
| 329 init(vec, isJarOnly); |
| 330 } |
| 331 |
| 332 private ClassPath(List<String> pathEntries, boolean isJarOnly) throws Public
Exceptions.InvalidCmdOptionException { |
| 333 init(pathEntries, isJarOnly); |
| 334 } |
| 335 |
| 336 private void init(List<String> pathEntries, boolean isJarOnly) throws Public
Exceptions.InvalidCmdOptionException { |
| 337 if (pathEntries == null) { |
| 338 throw new PublicExceptions.InvalidCmdOptionException("null argument"
); |
| 339 } |
| 340 List<PathEntry> vec = new ArrayList<PathEntry>(pathEntries.size()); |
| 341 for (int i = 0; i < pathEntries.size(); i++) { |
| 342 String path = pathEntries.get(i); |
| 343 if (!path.equals("")) { |
| 344 File file = new File(path); |
| 345 try { |
| 346 if (file.exists() && file.canRead()) { |
| 347 if (file.isDirectory()) { |
| 348 if (isJarOnly) { |
| 349 throw new PublicExceptions.InvalidCmdOptionExcep
tion("directories are not allowed on this class path: " + path); |
| 350 } |
| 351 vec.add(new Dir(file)); |
| 352 } else { |
| 353 vec.add(new Zip(new ZipFile(file))); |
| 354 } |
| 355 } else if (isJarOnly) { |
| 356 throw new IOException("file does not exist"); |
| 357 } |
| 358 } catch (IOException e) { |
| 359 if (isJarOnly) { |
| 360 throw new PublicExceptions.InvalidCmdOptionException("er
ror initializing class path component " + path + ": " + e.getMessage()); |
| 361 } |
| 362 } |
| 363 } |
| 364 } |
| 365 |
| 366 paths = new PathEntry[vec.size()]; |
| 367 vec.toArray(paths); |
| 368 } |
| 369 |
| 370 private boolean isEmpty() { |
| 371 return paths.length == 0; |
| 372 } |
| 373 |
| 374 private byte[] getBytesForClass(String className) { |
| 375 String fileName = className + ".class"; |
| 376 for (int i = 0; i < paths.length; i++) { |
| 377 byte buf[] = paths[i].getBytesForClassFile(fileName); |
| 378 if (buf != null) { |
| 379 return buf; |
| 380 } |
| 381 } |
| 382 return null; |
| 383 } |
| 384 |
| 385 public String toString() { |
| 386 if (paths == null) { |
| 387 return "NULL"; |
| 388 } |
| 389 StringBuilder res = new StringBuilder(); |
| 390 for (int i = 0; i < paths.length; i++) { |
| 391 res.append(paths[i].toString()); |
| 392 } |
| 393 return res.toString(); |
| 394 } |
| 395 |
| 396 |
| 397 // ------------------------------------ Private helper classes -------------
------------------------------- |
| 398 private static abstract class PathEntry { |
| 399 |
| 400 abstract byte[] getBytesForClassFile(String fileName); |
| 401 |
| 402 public abstract String toString(); |
| 403 } |
| 404 |
| 405 private static class Dir extends PathEntry { |
| 406 |
| 407 private String dir; |
| 408 |
| 409 Dir(File f) throws IOException { |
| 410 dir = f.getCanonicalPath(); |
| 411 } |
| 412 |
| 413 byte[] getBytesForClassFile(String fileName) { |
| 414 File file = new File(dir + File.separatorChar + fileName); |
| 415 if (file.exists()) { |
| 416 return Utils.readFileIntoBuffer(file); |
| 417 } else { |
| 418 return null; |
| 419 } |
| 420 } |
| 421 |
| 422 public String toString() { |
| 423 return dir; |
| 424 } |
| 425 } |
| 426 |
| 427 private static class Zip extends PathEntry { |
| 428 |
| 429 private ZipFile zip; |
| 430 |
| 431 Zip(ZipFile z) { |
| 432 zip = z; |
| 433 } |
| 434 |
| 435 byte[] getBytesForClassFile(String fileName) { |
| 436 ZipEntry entry = zip.getEntry(fileName); |
| 437 if (entry != null) { |
| 438 return Utils.readZipEntryIntoBuffer(zip, entry); |
| 439 } else { |
| 440 return null; |
| 441 } |
| 442 } |
| 443 |
| 444 public String toString() { |
| 445 return zip.getName(); |
| 446 } |
| 447 } |
| 448 } |
OLD | NEW |