blob: e6e1aebd86a31054fc802960991986414288ef01 [file] [log] [blame]
/* 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();
}
}
}