Skip to content

Commit

Permalink
Prepare Classpath reading for Java 9
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 171180273
  • Loading branch information
cushon authored and aehlig committed Oct 6, 2017
1 parent 06d2410 commit e9cc512
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 140 deletions.
5 changes: 4 additions & 1 deletion src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ java_library(
java_library(
name = "classpath-util",
srcs = ["util/Classpath.java"],
deps = ["//src/main/java/com/google/devtools/build/lib:preconditions"],
deps = [
"//src/main/java/com/google/devtools/build/lib:preconditions",
"//third_party:guava",
],
)

java_library(
Expand Down
151 changes: 12 additions & 139 deletions src/main/java/com/google/devtools/build/lib/util/Classpath.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,17 @@
// limitations under the License.
package com.google.devtools.build.lib.util;

import java.io.File;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
* A helper class to find all classes on the current classpath. This is used to automatically create
* JUnit 3 and 4 test suites.
*/
public final class Classpath {
private static final String CLASS_EXTENSION = ".class";

/**
* Base exception for any classpath related errors.
Expand All @@ -49,135 +37,20 @@ public ClassPathException(String format, Object... args) {
/** Finds all classes that live in or below the given package. */
public static Set<Class<?>> findClasses(String packageName) throws ClassPathException {
Set<Class<?>> result = new LinkedHashSet<>();
String pathPrefix = (packageName + '.').replace('.', '/');
for (String entryName : getClassPath()) {
File classPathEntry = new File(entryName);
if (classPathEntry.exists()) {
try {
Set<String> classNames;
if (classPathEntry.isDirectory()) {
classNames = findClassesInDirectory(classPathEntry, pathPrefix);
} else {
classNames = findClassesInJar(classPathEntry, pathPrefix);
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
result.add(clazz);
} catch (UnsatisfiedLinkError | NoClassDefFoundError unused) {
// Ignore: we're most likely running on a different platform.
}
}
} catch (IOException e) {
throw new ClassPathException(
"Can't read classpath entry %s: %s", entryName, e.getMessage());
} catch (ClassNotFoundException e) {
throw new ClassPathException(
"Class not found even though it is on the classpath %s: %s",
entryName, e.getMessage());
}
}
}
return result;
}

private static Set<String> findClassesInDirectory(File classPathEntry, String pathPrefix) {
Set<String> result = new TreeSet<>();
File directory = new File(classPathEntry, pathPrefix);
innerFindClassesInDirectory(result, directory, pathPrefix);
return result;
}

/**
* Finds all classes and sub packages in the given directory that are below the given package and
* add them to the respective sets.
*
* @param directory Directory to inspect
* @param pathPrefix Prefix for the path to the classes that are requested
* (ex: {@code com/google/foo/bar})
*/
private static void innerFindClassesInDirectory(Set<String> classNames, File directory,
String pathPrefix) {
Preconditions.checkArgument(pathPrefix.endsWith("/"));
if (directory.exists()) {
for (File f : directory.listFiles()) {
String name = f.getName();
if (name.endsWith(CLASS_EXTENSION)) {
String clzName = getClassName(pathPrefix + name);
classNames.add(clzName);
} else if (f.isDirectory()) {
findClassesInDirectory(f, pathPrefix + name + "/");
}
}
}
}

/**
* Returns a set of all classes in the jar that start with the given prefix.
*/
private static Set<String> findClassesInJar(File jarFile, String pathPrefix) throws IOException {
Set<String> classNames = new TreeSet<>();
try (ZipFile zipFile = new ZipFile(jarFile)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
String entryName = entries.nextElement().getName();
if (entryName.startsWith(pathPrefix) && entryName.endsWith(CLASS_EXTENSION)) {
classNames.add(getClassName(entryName));
}
}
}
return classNames;
}

/**
* Given the absolute path of a class file, return the class name.
*/
private static String getClassName(String className) {
int classNameEnd = className.length() - CLASS_EXTENSION.length();
return className.substring(0, classNameEnd).replace('/', '.');
}

private static void getClassPathsFromClasspathJar(File classpathJar, Set<String> classPaths)
throws IOException, ClassPathException {
Manifest manifest = new JarFile(classpathJar).getManifest();
Attributes attributes = manifest.getMainAttributes();
for (String classPath : attributes.getValue("Class-Path").split(" ")) {
try {
classPaths.add(Paths.get(new URI(classPath)).toAbsolutePath().toString());
} catch (URISyntaxException e) {
throw new ClassPathException(
"Error parsing classpath uri %s: %s", classPath, e.getMessage());
}
}
}

/** Gets the classpath from current classloader. */
private static Set<String> getClassPath() throws ClassPathException {
ClassLoader classloader = Classpath.class.getClassLoader();
if (!(classloader instanceof URLClassLoader)) {
throw new IllegalStateException("Unable to find classes to test, since Test Suite class is "
+ "loaded by an unsupported Classloader.");
}

Set<String> completeClassPaths = new TreeSet<>();
URL[] urls = ((URLClassLoader) classloader).getURLs();
for (URL url : urls) {
String entryName = url.getPath();
completeClassPaths.add(entryName);
if (entryName.endsWith("-classpath.jar")) {
// Bazel creates a classpath jar when the class path length exceeds command line length
// limit, read the class path value from its manifest file if it's a classpath jar.
File classPathEntry = new File(entryName);
if (classPathEntry.exists() && classPathEntry.isFile()) {
String packagePrefix = (packageName + '.').replace('/', '.');
try {
for (ClassInfo ci : ClassPath.from(Classpath.class.getClassLoader()).getAllClasses()) {
if (ci.getName().startsWith(packagePrefix)) {
try {
getClassPathsFromClasspathJar(classPathEntry, completeClassPaths);
} catch (IOException e) {
throw new ClassPathException(
"Can't read classpath entry %s: %s", entryName, e.getMessage());
result.add(ci.load());
} catch (UnsatisfiedLinkError | NoClassDefFoundError unused) {
// Ignore: we're most likely running on a different platform.
}
}
}
} catch (IOException e) {
throw new ClassPathException(e.getMessage());
}
return completeClassPaths;
return result;
}
}

0 comments on commit e9cc512

Please sign in to comment.