aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools
diff options
context:
space:
mode:
authorGravatar Florian Weikert <fwe@google.com>2017-02-21 12:39:21 +0000
committerGravatar Irina Iancu <elenairina@google.com>2017-02-21 13:50:49 +0000
commit9739b479c5e3acd959cd586a2c0615bfa5011637 (patch)
treec7489da7471368b11e756e6cf80c2f37ef809209 /src/main/java/com/google/devtools
parent57eec8c33d35336049f11db251d2b87c70661805 (diff)
Move Classpath.java from src/test/... to src/main/... since it will be used by the documentation generation in the future.
-- PiperOrigin-RevId: 148081562 MOS_MIGRATED_REVID=148081562
Diffstat (limited to 'src/main/java/com/google/devtools')
-rw-r--r--src/main/java/com/google/devtools/build/lib/util/Classpath.java179
1 files changed, 179 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/util/Classpath.java b/src/main/java/com/google/devtools/build/lib/util/Classpath.java
new file mode 100644
index 0000000000..5c26e6505e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/util/Classpath.java
@@ -0,0 +1,179 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.util;
+
+import java.io.File;
+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.
+ */
+ public static final class ClassPathException extends Exception {
+ public ClassPathException(String format, Object... args) {
+ super(String.format(format, 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) {
+ Class<?> clazz = Class.forName(className);
+ result.add(clazz);
+ }
+ } 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()) {
+ try {
+ getClassPathsFromClasspathJar(classPathEntry, completeClassPaths);
+ } catch (IOException e) {
+ throw new ClassPathException(
+ "Can't read classpath entry %s: %s", entryName, e.getMessage());
+ }
+ }
+ }
+ }
+ return completeClassPaths;
+ }
+}