Index: third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java |
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java b/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e6e1aebd86a31054fc802960991986414288ef01 |
--- /dev/null |
+++ b/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java |
@@ -0,0 +1,448 @@ |
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved |
+ * |
+ * This program is distributed under the terms of |
+ * the GNU General Public License Version 2. See the LICENSE file |
+ * at the top of the source tree. |
+ */ |
+package org.pantsbuild.jmake; |
+ |
+import java.io.File; |
+import java.io.FilenameFilter; |
+import java.io.IOException; |
+import java.net.URL; |
+import java.net.URLClassLoader; |
+import java.util.ArrayList; |
+import java.util.Collection; |
+import java.util.LinkedHashMap; |
+import java.util.List; |
+import java.util.Locale; |
+import java.util.Map; |
+import java.util.Set; |
+import java.util.StringTokenizer; |
+import java.util.zip.ZipEntry; |
+import java.util.zip.ZipFile; |
+ |
+/** |
+ * An instance of this class represents a class path, on which binary classes can be looked up. |
+ * It also provides several static methods to create and utilize several specific class paths used |
+ * throughout jmake. |
+ * |
+ * @author Misha Dmitriev |
+ * 12 October 2004 |
+ */ |
+public class ClassPath { |
+ |
+ private PathEntry[] paths; |
+ private static ClassPath projectClassPath; // Class path (currently it can contain only JARs) containing sourceless project classes. |
+ // See also the comment to standardClassPath. |
+ private static ClassPath standardClassPath; // Class path that the user specifies via the -classpath option. A sum of the |
+ // standardClassPath, the projectClassPath, and the virtualPath is passed to the compiler. Each of these |
+ // class paths are also used to look up non-project superclasses/superinterfaces of |
+ // project classes. |
+ private static ClassPath bootClassPath, extClassPath; // Class paths that by default are sun.boot.class.path and all JARs on |
+ // java.ext.class.path, respectively. They are used to look up non-project |
+ // superclasses/superinterfaces of project classes. Their values can be changed using |
+ // setBootClassPath() and setExtDirs(). |
+ private static ClassPath virtualPath; // Class path that the user specifies via the -vpath option. |
+ private static String compilerUserClassPath; // Class path to be passed to the compiler; equals to the sum of values of parameters of |
+ // setClassPath() and setProjectClassPath() methods. |
+ private static String standardClassPathStr, projectClassPathStr, bootClassPathStr, extDirsStr, |
+ virtualPathStr; |
+ private static Map<String,ClassInfo> classCache; |
+ |
+ |
+ static { |
+ resetOnFinish(); |
+ } |
+ |
+ /** |
+ * Needed since some environments, e.g. NetBeans, can keep jmake classes in memory |
+ * permanently. Thus unchanged class paths from previous, possibly unrelated invocations |
+ * of jmake, may interfere with the current settings. |
+ */ |
+ public static void resetOnFinish() { |
+ projectClassPath = standardClassPath = bootClassPath = extClassPath = virtualPath = |
+ null; |
+ compilerUserClassPath = null; |
+ standardClassPathStr = projectClassPathStr = bootClassPathStr = |
+ extDirsStr = virtualPathStr = null; |
+ classCache = new LinkedHashMap<String,ClassInfo>(); |
+ } |
+ |
+ public static void setClassPath(String value) throws PublicExceptions.InvalidCmdOptionException { |
+ standardClassPathStr = value; |
+ standardClassPath = new ClassPath(value, false); |
+ } |
+ |
+ public static void setProjectClassPath(String value) throws PublicExceptions.InvalidCmdOptionException { |
+ projectClassPathStr = value; |
+ projectClassPath = new ClassPath(value, true); |
+ } |
+ |
+ public static void setBootClassPath(String value) throws PublicExceptions.InvalidCmdOptionException { |
+ bootClassPathStr = value; |
+ bootClassPath = new ClassPath(value, false); |
+ } |
+ |
+ public static void setExtDirs(String value) throws PublicExceptions.InvalidCmdOptionException { |
+ extDirsStr = value; |
+ // Extension class path needs special handling, since it consists of directories, which contain .jars |
+ // So we need to find all these .jars in all these dirs and add them to extClassPathElementList |
+ List<String> extClassPathElements = new ArrayList<String>(); |
+ for (StringTokenizer tok = |
+ new StringTokenizer(value, File.pathSeparator); tok.hasMoreTokens();) { |
+ File extDir = new File(tok.nextToken()); |
+ String[] extJars = extDir.list(new FilenameFilter() { |
+ |
+ public boolean accept(File dir, String name) { |
+ name = name.toLowerCase(Locale.ENGLISH); |
+ return name.endsWith(".zip") || name.endsWith(".jar"); |
+ } |
+ }); |
+ if (extJars == null) { |
+ continue; |
+ } |
+ for (int i = 0; i < extJars.length; i++) { |
+ extClassPathElements.add(extDir + File.separator + extJars[i]); |
+ } |
+ } |
+ extClassPath = new ClassPath(extClassPathElements, false); |
+ } |
+ |
+ public static void setVirtualPath(String value) throws PublicExceptions.InvalidCmdOptionException { |
+ if (value == null) { |
+ throw new PublicExceptions.InvalidCmdOptionException("null argument"); |
+ } |
+ StringTokenizer st = new StringTokenizer(value, File.pathSeparator); |
+ while (st.hasMoreElements()) { |
+ String dir = st.nextToken(); |
+ if ( ! (new File(dir)).isDirectory()) { |
+ throw new PublicExceptions.InvalidCmdOptionException("Virtual path must contain only directories." + |
+ " Entry " + dir + " is not a directory."); |
+ } |
+ } |
+ virtualPathStr = value; |
+ virtualPath = new ClassPath(value, false); |
+ } |
+ |
+ public static void initializeAllClassPaths() { |
+ // First set the compiler class path value |
+ if (standardClassPathStr == null && projectClassPathStr == null) { |
+ compilerUserClassPath = "."; |
+ } else if (standardClassPathStr == null) { |
+ compilerUserClassPath = projectClassPathStr; |
+ } else if (projectClassPathStr == null) { |
+ compilerUserClassPath = standardClassPathStr; |
+ } else { |
+ compilerUserClassPath = |
+ standardClassPathStr + File.pathSeparator + projectClassPathStr; |
+ } |
+ |
+ if (virtualPathStr != null) { |
+ compilerUserClassPath += File.pathSeparator + virtualPathStr; |
+ } |
+ |
+ if (standardClassPathStr == null) { |
+ try { |
+ String tmp = "."; |
+ if (virtualPathStr != null) { |
+ tmp += File.pathSeparator + virtualPathStr; |
+ } |
+ standardClassPath = new ClassPath(tmp, false); |
+ } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Should not happen */ } |
+ } |
+ if (projectClassPathStr == null) { |
+ projectClassPath = new ClassPath(); |
+ } |
+ |
+ // Create the core class path as a combination of sun.boot.class.path and java.ext.dirs contents |
+ if (bootClassPathStr == null) { |
+ try { |
+ bootClassPath = |
+ new ClassPath(System.getProperty("sun.boot.class.path"), false); |
+ } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Shouldn't happen */ } |
+ // bootClassPathStr should remain null, so that nothing that the user didn't specify is passed to the compiler |
+ } |
+ |
+ if (extDirsStr == null) { |
+ try { |
+ setExtDirs(System.getProperty("java.ext.dirs")); |
+ } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Shouldn't happen */ } |
+ // extDirsStr should remain null, so that nothing that the user didn't specify is passed to the compiler |
+ extDirsStr = null; |
+ } |
+ } |
+ |
+ /** Never returns null - if classpath wasn't set explicitly, returns "." */ |
+ public static String getCompilerUserClassPath() { |
+ return compilerUserClassPath; |
+ } |
+ |
+ /** Will return null if boot class path wasn't explicitly specified */ |
+ public static String getCompilerBootClassPath() { |
+ return bootClassPathStr; |
+ } |
+ |
+ /** Will return null if extdirs weren't explicitly specified */ |
+ public static String getCompilerExtDirs() { |
+ return extDirsStr; |
+ } |
+ |
+ /** Will return null if virtualPath wasn't explicitly specified */ |
+ public static String getVirtualPath() { |
+ return virtualPathStr; |
+ } |
+ |
+ /** |
+ * For the given class return the list of all of its superclasses (excluding Object), that can be loaded from |
+ * projectClassPath or standardClassPath, plus the first superclass that can be loaded from coreClassPath. |
+ * The latter is an optimization based on the assumption that core classes never change, or rather the programmer |
+ * will recompile everything when they switch to a new JDK version. The optimization prevents us from wasting time |
+ * repeatedly loading the same sets of core classes. |
+ */ |
+ public static void getSuperclasses(String className, |
+ Collection<String> res, PCDManager pcdm) { |
+ int iterNo = 0; |
+ while (!"java/lang/Object".equals(className)) { |
+ ClassInfo ci = getClassInfoForName(className, pcdm); |
+ if (ci == null) { |
+ return; |
+ } |
+ if (iterNo++ > 0) { |
+ res.add(ci.name); |
+ } |
+ className = ci.superName; |
+ } |
+ } |
+ |
+ /** |
+ * Add to the given set the names of all interfaces implemented by the given class, that can be loaded from |
+ * projectClassPath or standardClassPath, plus the first interface on each branch that can be loaded from |
+ * coreClassPath. It's the same optimization as in getSuperclasses(). |
+ */ |
+ public static void addAllImplementedInterfaceNames(String className, |
+ Set<String> intfSet, PCDManager pcdm) { |
+ if ("java/lang/Object".equals(className)) { |
+ return; |
+ } |
+ ClassInfo ci = getClassInfoForName(className, pcdm); |
+ if (ci == null) { |
+ return; |
+ } |
+ String[] interfaces = ci.interfaces; |
+ if (interfaces != null) { |
+ for (int i = 0; i < interfaces.length; i++) { |
+ intfSet.add(interfaces[i]); |
+ addAllImplementedInterfaceNames(interfaces[i], intfSet, pcdm); |
+ } |
+ } |
+ |
+ String superName = ci.superName; |
+ if (superName != null) { |
+ addAllImplementedInterfaceNames(superName, intfSet, pcdm); |
+ } |
+ } |
+ |
+ public static String[] getProjectJars() { |
+ if (projectClassPath == null || projectClassPath.isEmpty()) { |
+ return null; |
+ } |
+ PathEntry paths[] = projectClassPath.paths; |
+ String[] ret = new String[paths.length]; |
+ for (int i = 0; i < paths.length; i++) { |
+ ret[i] = paths[i].toString(); |
+ } |
+ return ret; |
+ } |
+ |
+ public static ClassInfo getClassInfoForName(String className, PCDManager pcdm) { |
+ ClassInfo info = classCache.get(className); |
+ if (info != null) { |
+ return info; |
+ } |
+ |
+ byte buf[] = bootClassPath.getBytesForClass(className); |
+ if (buf == null) { |
+ buf = extClassPath.getBytesForClass(className); |
+ } |
+ if (buf == null) { |
+ buf = standardClassPath.getBytesForClass(className); |
+ } |
+ if (buf == null) { |
+ buf = projectClassPath.getBytesForClass(className); |
+ } |
+ if (buf == null) { |
+ return null; |
+ } |
+ |
+ info = new ClassInfo(buf, pcdm, className); |
+ classCache.put(className, info); |
+ return info; |
+ } |
+ |
+ /** Returns the class loader that would load classes from the given class path. */ |
+ public static ClassLoader getClassLoaderForPath(String classPath) throws Exception { |
+ boolean isWindows = System.getProperty("os.name").startsWith("Win"); |
+ ClassPath cp = new ClassPath(classPath, false); |
+ PathEntry[] paths = cp.paths; |
+ URL[] urls = new URL[paths.length]; |
+ for (int i = 0; i < paths.length; i++) { |
+ String dirOrJar = paths[i].toString(); |
+ if (!(dirOrJar.startsWith("file://") || dirOrJar.startsWith("http://"))) { |
+ // On Windows, if I have path specified as "file://c:\...", (i.e. with the drive name) URLClassLoader works |
+ // unbelievably slow. However, if an additional slash is added, like : "file:///c:\...", the speed becomes |
+ // normal. To me it looks like a bug, but, anyway, I am taking measure here. |
+ if (isWindows && dirOrJar.charAt(1) == ':') { |
+ dirOrJar = "/" + dirOrJar; |
+ } |
+ dirOrJar = new File(dirOrJar).toURI().toString(); |
+ } |
+ if (!(dirOrJar.endsWith(".jar") || dirOrJar.endsWith(".zip") || dirOrJar.endsWith(File.separator))) { |
+ dirOrJar += File.separator; // So that URLClassLoader correctly handles it as a directory |
+ } |
+ urls[i] = new URL(dirOrJar); |
+ } |
+ |
+ return new URLClassLoader(urls); |
+ //} catch (java.net.MalformedURLException e) { |
+ |
+ //} |
+ } |
+ |
+ |
+ // ------------------------------------ Private implementation -------------------------------------------- |
+ private ClassPath() { |
+ paths = new PathEntry[0]; |
+ } |
+ |
+ private ClassPath(String classPath, boolean isJarOnly) throws PublicExceptions.InvalidCmdOptionException { |
+ if (classPath == null) { |
+ throw new PublicExceptions.InvalidCmdOptionException("null argument"); |
+ } |
+ List<String> vec = new ArrayList<String>(); |
+ |
+ for (StringTokenizer tok = |
+ new StringTokenizer(classPath, File.pathSeparator); tok.hasMoreTokens();) { |
+ String path = tok.nextToken(); |
+ vec.add(path); |
+ } |
+ init(vec, isJarOnly); |
+ } |
+ |
+ private ClassPath(List<String> pathEntries, boolean isJarOnly) throws PublicExceptions.InvalidCmdOptionException { |
+ init(pathEntries, isJarOnly); |
+ } |
+ |
+ private void init(List<String> pathEntries, boolean isJarOnly) throws PublicExceptions.InvalidCmdOptionException { |
+ if (pathEntries == null) { |
+ throw new PublicExceptions.InvalidCmdOptionException("null argument"); |
+ } |
+ List<PathEntry> vec = new ArrayList<PathEntry>(pathEntries.size()); |
+ for (int i = 0; i < pathEntries.size(); i++) { |
+ String path = pathEntries.get(i); |
+ if (!path.equals("")) { |
+ File file = new File(path); |
+ try { |
+ if (file.exists() && file.canRead()) { |
+ if (file.isDirectory()) { |
+ if (isJarOnly) { |
+ throw new PublicExceptions.InvalidCmdOptionException("directories are not allowed on this class path: " + path); |
+ } |
+ vec.add(new Dir(file)); |
+ } else { |
+ vec.add(new Zip(new ZipFile(file))); |
+ } |
+ } else if (isJarOnly) { |
+ throw new IOException("file does not exist"); |
+ } |
+ } catch (IOException e) { |
+ if (isJarOnly) { |
+ throw new PublicExceptions.InvalidCmdOptionException("error initializing class path component " + path + ": " + e.getMessage()); |
+ } |
+ } |
+ } |
+ } |
+ |
+ paths = new PathEntry[vec.size()]; |
+ vec.toArray(paths); |
+ } |
+ |
+ private boolean isEmpty() { |
+ return paths.length == 0; |
+ } |
+ |
+ private byte[] getBytesForClass(String className) { |
+ String fileName = className + ".class"; |
+ for (int i = 0; i < paths.length; i++) { |
+ byte buf[] = paths[i].getBytesForClassFile(fileName); |
+ if (buf != null) { |
+ return buf; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ public String toString() { |
+ if (paths == null) { |
+ return "NULL"; |
+ } |
+ StringBuilder res = new StringBuilder(); |
+ for (int i = 0; i < paths.length; i++) { |
+ res.append(paths[i].toString()); |
+ } |
+ return res.toString(); |
+ } |
+ |
+ |
+ // ------------------------------------ Private helper classes -------------------------------------------- |
+ private static abstract class PathEntry { |
+ |
+ abstract byte[] getBytesForClassFile(String fileName); |
+ |
+ public abstract String toString(); |
+ } |
+ |
+ private static class Dir extends PathEntry { |
+ |
+ private String dir; |
+ |
+ Dir(File f) throws IOException { |
+ dir = f.getCanonicalPath(); |
+ } |
+ |
+ byte[] getBytesForClassFile(String fileName) { |
+ File file = new File(dir + File.separatorChar + fileName); |
+ if (file.exists()) { |
+ return Utils.readFileIntoBuffer(file); |
+ } else { |
+ return null; |
+ } |
+ } |
+ |
+ public String toString() { |
+ return dir; |
+ } |
+ } |
+ |
+ private static class Zip extends PathEntry { |
+ |
+ private ZipFile zip; |
+ |
+ Zip(ZipFile z) { |
+ zip = z; |
+ } |
+ |
+ byte[] getBytesForClassFile(String fileName) { |
+ ZipEntry entry = zip.getEntry(fileName); |
+ if (entry != null) { |
+ return Utils.readZipEntryIntoBuffer(zip, entry); |
+ } else { |
+ return null; |
+ } |
+ } |
+ |
+ public String toString() { |
+ return zip.getName(); |
+ } |
+ } |
+} |