17dd252788645e940eada959bdde927426e2531c9Paul Duffin/* 27dd252788645e940eada959bdde927426e2531c9Paul Duffin * Copyright (C) 2012 The Guava Authors 37dd252788645e940eada959bdde927426e2531c9Paul Duffin * 47dd252788645e940eada959bdde927426e2531c9Paul Duffin * Licensed under the Apache License, Version 2.0 (the "License"); 57dd252788645e940eada959bdde927426e2531c9Paul Duffin * you may not use this file except in compliance with the License. 67dd252788645e940eada959bdde927426e2531c9Paul Duffin * You may obtain a copy of the License at 77dd252788645e940eada959bdde927426e2531c9Paul Duffin * 87dd252788645e940eada959bdde927426e2531c9Paul Duffin * http://www.apache.org/licenses/LICENSE-2.0 97dd252788645e940eada959bdde927426e2531c9Paul Duffin * 107dd252788645e940eada959bdde927426e2531c9Paul Duffin * Unless required by applicable law or agreed to in writing, software 117dd252788645e940eada959bdde927426e2531c9Paul Duffin * distributed under the License is distributed on an "AS IS" BASIS, 127dd252788645e940eada959bdde927426e2531c9Paul Duffin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137dd252788645e940eada959bdde927426e2531c9Paul Duffin * See the License for the specific language governing permissions and 147dd252788645e940eada959bdde927426e2531c9Paul Duffin * limitations under the License. 157dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 167dd252788645e940eada959bdde927426e2531c9Paul Duffin 177dd252788645e940eada959bdde927426e2531c9Paul Duffinpackage com.google.common.reflect; 187dd252788645e940eada959bdde927426e2531c9Paul Duffin 197dd252788645e940eada959bdde927426e2531c9Paul Duffinimport static com.google.common.base.Preconditions.checkNotNull; 207dd252788645e940eada959bdde927426e2531c9Paul Duffin 217dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.annotations.Beta; 227dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.annotations.VisibleForTesting; 230888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport com.google.common.base.CharMatcher; 240888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport com.google.common.base.Predicate; 257dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.base.Splitter; 260888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport com.google.common.collect.FluentIterable; 277dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.collect.ImmutableMap; 287dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.collect.ImmutableSet; 297dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.collect.ImmutableSortedSet; 307dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.collect.Maps; 317dd252788645e940eada959bdde927426e2531c9Paul Duffinimport com.google.common.collect.Ordering; 320888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport com.google.common.collect.Sets; 337dd252788645e940eada959bdde927426e2531c9Paul Duffin 347dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.io.File; 357dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.io.IOException; 367dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.net.URI; 377dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.net.URISyntaxException; 387dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.net.URL; 397dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.net.URLClassLoader; 407dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.Enumeration; 417dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.LinkedHashMap; 427dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.Map; 430888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport java.util.Set; 440888a09821a98ac0680fad765217302858e70fa4Paul Duffinimport java.util.jar.Attributes; 457dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.jar.JarEntry; 467dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.jar.JarFile; 477dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.jar.Manifest; 487dd252788645e940eada959bdde927426e2531c9Paul Duffinimport java.util.logging.Logger; 497dd252788645e940eada959bdde927426e2531c9Paul Duffin 507dd252788645e940eada959bdde927426e2531c9Paul Duffinimport javax.annotation.Nullable; 517dd252788645e940eada959bdde927426e2531c9Paul Duffin 527dd252788645e940eada959bdde927426e2531c9Paul Duffin/** 530888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Scans the source of a {@link ClassLoader} and finds all loadable classes and resources. 547dd252788645e940eada959bdde927426e2531c9Paul Duffin * 557dd252788645e940eada959bdde927426e2531c9Paul Duffin * @author Ben Yu 567dd252788645e940eada959bdde927426e2531c9Paul Duffin * @since 14.0 577dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 587dd252788645e940eada959bdde927426e2531c9Paul Duffin@Beta 597dd252788645e940eada959bdde927426e2531c9Paul Duffinpublic final class ClassPath { 607dd252788645e940eada959bdde927426e2531c9Paul Duffin private static final Logger logger = Logger.getLogger(ClassPath.class.getName()); 617dd252788645e940eada959bdde927426e2531c9Paul Duffin 620888a09821a98ac0680fad765217302858e70fa4Paul Duffin private static final Predicate<ClassInfo> IS_TOP_LEVEL = new Predicate<ClassInfo>() { 630888a09821a98ac0680fad765217302858e70fa4Paul Duffin @Override public boolean apply(ClassInfo info) { 640888a09821a98ac0680fad765217302858e70fa4Paul Duffin return info.className.indexOf('$') == -1; 650888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 660888a09821a98ac0680fad765217302858e70fa4Paul Duffin }; 670888a09821a98ac0680fad765217302858e70fa4Paul Duffin 687dd252788645e940eada959bdde927426e2531c9Paul Duffin /** Separator for the Class-Path manifest attribute value in jar files. */ 690888a09821a98ac0680fad765217302858e70fa4Paul Duffin private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR = 700888a09821a98ac0680fad765217302858e70fa4Paul Duffin Splitter.on(" ").omitEmptyStrings(); 717dd252788645e940eada959bdde927426e2531c9Paul Duffin 727dd252788645e940eada959bdde927426e2531c9Paul Duffin private static final String CLASS_FILE_NAME_EXTENSION = ".class"; 737dd252788645e940eada959bdde927426e2531c9Paul Duffin 747dd252788645e940eada959bdde927426e2531c9Paul Duffin private final ImmutableSet<ResourceInfo> resources; 757dd252788645e940eada959bdde927426e2531c9Paul Duffin 767dd252788645e940eada959bdde927426e2531c9Paul Duffin private ClassPath(ImmutableSet<ResourceInfo> resources) { 777dd252788645e940eada959bdde927426e2531c9Paul Duffin this.resources = resources; 787dd252788645e940eada959bdde927426e2531c9Paul Duffin } 797dd252788645e940eada959bdde927426e2531c9Paul Duffin 807dd252788645e940eada959bdde927426e2531c9Paul Duffin /** 817dd252788645e940eada959bdde927426e2531c9Paul Duffin * Returns a {@code ClassPath} representing all classes and resources loadable from {@code 827dd252788645e940eada959bdde927426e2531c9Paul Duffin * classloader} and its parent class loaders. 837dd252788645e940eada959bdde927426e2531c9Paul Duffin * 847dd252788645e940eada959bdde927426e2531c9Paul Duffin * <p>Currently only {@link URLClassLoader} and only {@code file://} urls are supported. 857dd252788645e940eada959bdde927426e2531c9Paul Duffin * 867dd252788645e940eada959bdde927426e2531c9Paul Duffin * @throws IOException if the attempt to read class path resources (jar files or directories) 877dd252788645e940eada959bdde927426e2531c9Paul Duffin * failed. 887dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 897dd252788645e940eada959bdde927426e2531c9Paul Duffin public static ClassPath from(ClassLoader classloader) throws IOException { 900888a09821a98ac0680fad765217302858e70fa4Paul Duffin Scanner scanner = new Scanner(); 917dd252788645e940eada959bdde927426e2531c9Paul Duffin for (Map.Entry<URI, ClassLoader> entry : getClassPathEntries(classloader).entrySet()) { 920888a09821a98ac0680fad765217302858e70fa4Paul Duffin scanner.scan(entry.getKey(), entry.getValue()); 937dd252788645e940eada959bdde927426e2531c9Paul Duffin } 940888a09821a98ac0680fad765217302858e70fa4Paul Duffin return new ClassPath(scanner.getResources()); 957dd252788645e940eada959bdde927426e2531c9Paul Duffin } 967dd252788645e940eada959bdde927426e2531c9Paul Duffin 977dd252788645e940eada959bdde927426e2531c9Paul Duffin /** 987dd252788645e940eada959bdde927426e2531c9Paul Duffin * Returns all resources loadable from the current class path, including the class files of all 990888a09821a98ac0680fad765217302858e70fa4Paul Duffin * loadable classes but excluding the "META-INF/MANIFEST.MF" file. 1007dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 1017dd252788645e940eada959bdde927426e2531c9Paul Duffin public ImmutableSet<ResourceInfo> getResources() { 1027dd252788645e940eada959bdde927426e2531c9Paul Duffin return resources; 1037dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1047dd252788645e940eada959bdde927426e2531c9Paul Duffin 1050888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 1060888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Returns all classes loadable from the current class path. 1070888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 1080888a09821a98ac0680fad765217302858e70fa4Paul Duffin * @since 16.0 1090888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 1100888a09821a98ac0680fad765217302858e70fa4Paul Duffin public ImmutableSet<ClassInfo> getAllClasses() { 1110888a09821a98ac0680fad765217302858e70fa4Paul Duffin return FluentIterable.from(resources).filter(ClassInfo.class).toSet(); 1120888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 1130888a09821a98ac0680fad765217302858e70fa4Paul Duffin 1147dd252788645e940eada959bdde927426e2531c9Paul Duffin /** Returns all top level classes loadable from the current class path. */ 1157dd252788645e940eada959bdde927426e2531c9Paul Duffin public ImmutableSet<ClassInfo> getTopLevelClasses() { 1160888a09821a98ac0680fad765217302858e70fa4Paul Duffin return FluentIterable.from(resources).filter(ClassInfo.class).filter(IS_TOP_LEVEL).toSet(); 1177dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1187dd252788645e940eada959bdde927426e2531c9Paul Duffin 1197dd252788645e940eada959bdde927426e2531c9Paul Duffin /** Returns all top level classes whose package name is {@code packageName}. */ 1207dd252788645e940eada959bdde927426e2531c9Paul Duffin public ImmutableSet<ClassInfo> getTopLevelClasses(String packageName) { 1217dd252788645e940eada959bdde927426e2531c9Paul Duffin checkNotNull(packageName); 1227dd252788645e940eada959bdde927426e2531c9Paul Duffin ImmutableSet.Builder<ClassInfo> builder = ImmutableSet.builder(); 1237dd252788645e940eada959bdde927426e2531c9Paul Duffin for (ClassInfo classInfo : getTopLevelClasses()) { 1247dd252788645e940eada959bdde927426e2531c9Paul Duffin if (classInfo.getPackageName().equals(packageName)) { 1257dd252788645e940eada959bdde927426e2531c9Paul Duffin builder.add(classInfo); 1267dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1277dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1287dd252788645e940eada959bdde927426e2531c9Paul Duffin return builder.build(); 1297dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1307dd252788645e940eada959bdde927426e2531c9Paul Duffin 1317dd252788645e940eada959bdde927426e2531c9Paul Duffin /** 1327dd252788645e940eada959bdde927426e2531c9Paul Duffin * Returns all top level classes whose package name is {@code packageName} or starts with 1337dd252788645e940eada959bdde927426e2531c9Paul Duffin * {@code packageName} followed by a '.'. 1347dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 1357dd252788645e940eada959bdde927426e2531c9Paul Duffin public ImmutableSet<ClassInfo> getTopLevelClassesRecursive(String packageName) { 1367dd252788645e940eada959bdde927426e2531c9Paul Duffin checkNotNull(packageName); 1377dd252788645e940eada959bdde927426e2531c9Paul Duffin String packagePrefix = packageName + '.'; 1387dd252788645e940eada959bdde927426e2531c9Paul Duffin ImmutableSet.Builder<ClassInfo> builder = ImmutableSet.builder(); 1397dd252788645e940eada959bdde927426e2531c9Paul Duffin for (ClassInfo classInfo : getTopLevelClasses()) { 1407dd252788645e940eada959bdde927426e2531c9Paul Duffin if (classInfo.getName().startsWith(packagePrefix)) { 1417dd252788645e940eada959bdde927426e2531c9Paul Duffin builder.add(classInfo); 1427dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1437dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1447dd252788645e940eada959bdde927426e2531c9Paul Duffin return builder.build(); 1457dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1467dd252788645e940eada959bdde927426e2531c9Paul Duffin 1477dd252788645e940eada959bdde927426e2531c9Paul Duffin /** 1487dd252788645e940eada959bdde927426e2531c9Paul Duffin * Represents a class path resource that can be either a class file or any other resource file 1497dd252788645e940eada959bdde927426e2531c9Paul Duffin * loadable from the class path. 1507dd252788645e940eada959bdde927426e2531c9Paul Duffin * 1517dd252788645e940eada959bdde927426e2531c9Paul Duffin * @since 14.0 1527dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 1537dd252788645e940eada959bdde927426e2531c9Paul Duffin @Beta 1547dd252788645e940eada959bdde927426e2531c9Paul Duffin public static class ResourceInfo { 1557dd252788645e940eada959bdde927426e2531c9Paul Duffin private final String resourceName; 1567dd252788645e940eada959bdde927426e2531c9Paul Duffin final ClassLoader loader; 1577dd252788645e940eada959bdde927426e2531c9Paul Duffin 1587dd252788645e940eada959bdde927426e2531c9Paul Duffin static ResourceInfo of(String resourceName, ClassLoader loader) { 1590888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (resourceName.endsWith(CLASS_FILE_NAME_EXTENSION)) { 1607dd252788645e940eada959bdde927426e2531c9Paul Duffin return new ClassInfo(resourceName, loader); 1617dd252788645e940eada959bdde927426e2531c9Paul Duffin } else { 1627dd252788645e940eada959bdde927426e2531c9Paul Duffin return new ResourceInfo(resourceName, loader); 1637dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1647dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1650888a09821a98ac0680fad765217302858e70fa4Paul Duffin 1667dd252788645e940eada959bdde927426e2531c9Paul Duffin ResourceInfo(String resourceName, ClassLoader loader) { 1677dd252788645e940eada959bdde927426e2531c9Paul Duffin this.resourceName = checkNotNull(resourceName); 1687dd252788645e940eada959bdde927426e2531c9Paul Duffin this.loader = checkNotNull(loader); 1697dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1707dd252788645e940eada959bdde927426e2531c9Paul Duffin 1717dd252788645e940eada959bdde927426e2531c9Paul Duffin /** Returns the url identifying the resource. */ 1727dd252788645e940eada959bdde927426e2531c9Paul Duffin public final URL url() { 1737dd252788645e940eada959bdde927426e2531c9Paul Duffin return checkNotNull(loader.getResource(resourceName), 1747dd252788645e940eada959bdde927426e2531c9Paul Duffin "Failed to load resource: %s", resourceName); 1757dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1767dd252788645e940eada959bdde927426e2531c9Paul Duffin 1777dd252788645e940eada959bdde927426e2531c9Paul Duffin /** Returns the fully qualified name of the resource. Such as "com/mycomp/foo/bar.txt". */ 1787dd252788645e940eada959bdde927426e2531c9Paul Duffin public final String getResourceName() { 1797dd252788645e940eada959bdde927426e2531c9Paul Duffin return resourceName; 1807dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1817dd252788645e940eada959bdde927426e2531c9Paul Duffin 1827dd252788645e940eada959bdde927426e2531c9Paul Duffin @Override public int hashCode() { 1837dd252788645e940eada959bdde927426e2531c9Paul Duffin return resourceName.hashCode(); 1847dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1857dd252788645e940eada959bdde927426e2531c9Paul Duffin 1867dd252788645e940eada959bdde927426e2531c9Paul Duffin @Override public boolean equals(Object obj) { 1877dd252788645e940eada959bdde927426e2531c9Paul Duffin if (obj instanceof ResourceInfo) { 1887dd252788645e940eada959bdde927426e2531c9Paul Duffin ResourceInfo that = (ResourceInfo) obj; 1897dd252788645e940eada959bdde927426e2531c9Paul Duffin return resourceName.equals(that.resourceName) 1907dd252788645e940eada959bdde927426e2531c9Paul Duffin && loader == that.loader; 1917dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1927dd252788645e940eada959bdde927426e2531c9Paul Duffin return false; 1937dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1947dd252788645e940eada959bdde927426e2531c9Paul Duffin 1950888a09821a98ac0680fad765217302858e70fa4Paul Duffin // Do not change this arbitrarily. We rely on it for sorting ResourceInfo. 1967dd252788645e940eada959bdde927426e2531c9Paul Duffin @Override public String toString() { 1977dd252788645e940eada959bdde927426e2531c9Paul Duffin return resourceName; 1987dd252788645e940eada959bdde927426e2531c9Paul Duffin } 1997dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2007dd252788645e940eada959bdde927426e2531c9Paul Duffin 2017dd252788645e940eada959bdde927426e2531c9Paul Duffin /** 2027dd252788645e940eada959bdde927426e2531c9Paul Duffin * Represents a class that can be loaded through {@link #load}. 2037dd252788645e940eada959bdde927426e2531c9Paul Duffin * 2047dd252788645e940eada959bdde927426e2531c9Paul Duffin * @since 14.0 2057dd252788645e940eada959bdde927426e2531c9Paul Duffin */ 2067dd252788645e940eada959bdde927426e2531c9Paul Duffin @Beta 2077dd252788645e940eada959bdde927426e2531c9Paul Duffin public static final class ClassInfo extends ResourceInfo { 2087dd252788645e940eada959bdde927426e2531c9Paul Duffin private final String className; 2097dd252788645e940eada959bdde927426e2531c9Paul Duffin 2107dd252788645e940eada959bdde927426e2531c9Paul Duffin ClassInfo(String resourceName, ClassLoader loader) { 2117dd252788645e940eada959bdde927426e2531c9Paul Duffin super(resourceName, loader); 2127dd252788645e940eada959bdde927426e2531c9Paul Duffin this.className = getClassName(resourceName); 2137dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2147dd252788645e940eada959bdde927426e2531c9Paul Duffin 2150888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 2160888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Returns the package name of the class, without attempting to load the class. 2170888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 2180888a09821a98ac0680fad765217302858e70fa4Paul Duffin * <p>Behaves identically to {@link Package#getName()} but does not require the class (or 2190888a09821a98ac0680fad765217302858e70fa4Paul Duffin * package) to be loaded. 2200888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 2217dd252788645e940eada959bdde927426e2531c9Paul Duffin public String getPackageName() { 2227dd252788645e940eada959bdde927426e2531c9Paul Duffin return Reflection.getPackageName(className); 2237dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2247dd252788645e940eada959bdde927426e2531c9Paul Duffin 2250888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 2260888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Returns the simple name of the underlying class as given in the source code. 2270888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 2280888a09821a98ac0680fad765217302858e70fa4Paul Duffin * <p>Behaves identically to {@link Class#getSimpleName()} but does not require the class to be 2290888a09821a98ac0680fad765217302858e70fa4Paul Duffin * loaded. 2300888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 2317dd252788645e940eada959bdde927426e2531c9Paul Duffin public String getSimpleName() { 2320888a09821a98ac0680fad765217302858e70fa4Paul Duffin int lastDollarSign = className.lastIndexOf('$'); 2330888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (lastDollarSign != -1) { 2340888a09821a98ac0680fad765217302858e70fa4Paul Duffin String innerClassName = className.substring(lastDollarSign + 1); 2350888a09821a98ac0680fad765217302858e70fa4Paul Duffin // local and anonymous classes are prefixed with number (1,2,3...), anonymous classes are 2360888a09821a98ac0680fad765217302858e70fa4Paul Duffin // entirely numeric whereas local classes have the user supplied name as a suffix 2370888a09821a98ac0680fad765217302858e70fa4Paul Duffin return CharMatcher.DIGIT.trimLeadingFrom(innerClassName); 2380888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 2397dd252788645e940eada959bdde927426e2531c9Paul Duffin String packageName = getPackageName(); 2403ecfa412eddc4b084663f38d562537b86b9734d5Paul Duffin if (packageName.isEmpty()) { 2417dd252788645e940eada959bdde927426e2531c9Paul Duffin return className; 2427dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2430888a09821a98ac0680fad765217302858e70fa4Paul Duffin 2447dd252788645e940eada959bdde927426e2531c9Paul Duffin // Since this is a top level class, its simple name is always the part after package name. 2457dd252788645e940eada959bdde927426e2531c9Paul Duffin return className.substring(packageName.length() + 1); 2467dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2477dd252788645e940eada959bdde927426e2531c9Paul Duffin 2480888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 2490888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Returns the fully qualified name of the class. 2500888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 2510888a09821a98ac0680fad765217302858e70fa4Paul Duffin * <p>Behaves identically to {@link Class#getName()} but does not require the class to be 2520888a09821a98ac0680fad765217302858e70fa4Paul Duffin * loaded. 2530888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 2547dd252788645e940eada959bdde927426e2531c9Paul Duffin public String getName() { 2557dd252788645e940eada959bdde927426e2531c9Paul Duffin return className; 2567dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2577dd252788645e940eada959bdde927426e2531c9Paul Duffin 2580888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 2590888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Loads (but doesn't link or initialize) the class. 2600888a09821a98ac0680fad765217302858e70fa4Paul Duffin * 2610888a09821a98ac0680fad765217302858e70fa4Paul Duffin * @throws LinkageError when there were errors in loading classes that this class depends on. 2620888a09821a98ac0680fad765217302858e70fa4Paul Duffin * For example, {@link NoClassDefFoundError}. 2630888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 2647dd252788645e940eada959bdde927426e2531c9Paul Duffin public Class<?> load() { 2657dd252788645e940eada959bdde927426e2531c9Paul Duffin try { 2667dd252788645e940eada959bdde927426e2531c9Paul Duffin return loader.loadClass(className); 2677dd252788645e940eada959bdde927426e2531c9Paul Duffin } catch (ClassNotFoundException e) { 2687dd252788645e940eada959bdde927426e2531c9Paul Duffin // Shouldn't happen, since the class name is read from the class path. 2697dd252788645e940eada959bdde927426e2531c9Paul Duffin throw new IllegalStateException(e); 2707dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2717dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2727dd252788645e940eada959bdde927426e2531c9Paul Duffin 2737dd252788645e940eada959bdde927426e2531c9Paul Duffin @Override public String toString() { 2747dd252788645e940eada959bdde927426e2531c9Paul Duffin return className; 2757dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2767dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2777dd252788645e940eada959bdde927426e2531c9Paul Duffin 2780888a09821a98ac0680fad765217302858e70fa4Paul Duffin @VisibleForTesting static ImmutableMap<URI, ClassLoader> getClassPathEntries( 2790888a09821a98ac0680fad765217302858e70fa4Paul Duffin ClassLoader classloader) { 2807dd252788645e940eada959bdde927426e2531c9Paul Duffin LinkedHashMap<URI, ClassLoader> entries = Maps.newLinkedHashMap(); 2817dd252788645e940eada959bdde927426e2531c9Paul Duffin // Search parent first, since it's the order ClassLoader#loadClass() uses. 2827dd252788645e940eada959bdde927426e2531c9Paul Duffin ClassLoader parent = classloader.getParent(); 2837dd252788645e940eada959bdde927426e2531c9Paul Duffin if (parent != null) { 2847dd252788645e940eada959bdde927426e2531c9Paul Duffin entries.putAll(getClassPathEntries(parent)); 2857dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2867dd252788645e940eada959bdde927426e2531c9Paul Duffin if (classloader instanceof URLClassLoader) { 2877dd252788645e940eada959bdde927426e2531c9Paul Duffin URLClassLoader urlClassLoader = (URLClassLoader) classloader; 2887dd252788645e940eada959bdde927426e2531c9Paul Duffin for (URL entry : urlClassLoader.getURLs()) { 2897dd252788645e940eada959bdde927426e2531c9Paul Duffin URI uri; 2907dd252788645e940eada959bdde927426e2531c9Paul Duffin try { 2917dd252788645e940eada959bdde927426e2531c9Paul Duffin uri = entry.toURI(); 2927dd252788645e940eada959bdde927426e2531c9Paul Duffin } catch (URISyntaxException e) { 2937dd252788645e940eada959bdde927426e2531c9Paul Duffin throw new IllegalArgumentException(e); 2947dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2957dd252788645e940eada959bdde927426e2531c9Paul Duffin if (!entries.containsKey(uri)) { 2967dd252788645e940eada959bdde927426e2531c9Paul Duffin entries.put(uri, classloader); 2977dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2987dd252788645e940eada959bdde927426e2531c9Paul Duffin } 2997dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3007dd252788645e940eada959bdde927426e2531c9Paul Duffin return ImmutableMap.copyOf(entries); 3017dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3027dd252788645e940eada959bdde927426e2531c9Paul Duffin 3030888a09821a98ac0680fad765217302858e70fa4Paul Duffin @VisibleForTesting static final class Scanner { 3047dd252788645e940eada959bdde927426e2531c9Paul Duffin 3050888a09821a98ac0680fad765217302858e70fa4Paul Duffin private final ImmutableSortedSet.Builder<ResourceInfo> resources = 3060888a09821a98ac0680fad765217302858e70fa4Paul Duffin new ImmutableSortedSet.Builder<ResourceInfo>(Ordering.usingToString()); 3070888a09821a98ac0680fad765217302858e70fa4Paul Duffin private final Set<URI> scannedUris = Sets.newHashSet(); 3087dd252788645e940eada959bdde927426e2531c9Paul Duffin 3090888a09821a98ac0680fad765217302858e70fa4Paul Duffin ImmutableSortedSet<ResourceInfo> getResources() { 3100888a09821a98ac0680fad765217302858e70fa4Paul Duffin return resources.build(); 3110888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3127dd252788645e940eada959bdde927426e2531c9Paul Duffin 3130888a09821a98ac0680fad765217302858e70fa4Paul Duffin void scan(URI uri, ClassLoader classloader) throws IOException { 3140888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (uri.getScheme().equals("file") && scannedUris.add(uri)) { 3150888a09821a98ac0680fad765217302858e70fa4Paul Duffin scanFrom(new File(uri), classloader); 3160888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3170888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3180888a09821a98ac0680fad765217302858e70fa4Paul Duffin 3190888a09821a98ac0680fad765217302858e70fa4Paul Duffin @VisibleForTesting void scanFrom(File file, ClassLoader classloader) 3200888a09821a98ac0680fad765217302858e70fa4Paul Duffin throws IOException { 3210888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (!file.exists()) { 3220888a09821a98ac0680fad765217302858e70fa4Paul Duffin return; 3230888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3240888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (file.isDirectory()) { 3250888a09821a98ac0680fad765217302858e70fa4Paul Duffin scanDirectory(file, classloader); 3267dd252788645e940eada959bdde927426e2531c9Paul Duffin } else { 3270888a09821a98ac0680fad765217302858e70fa4Paul Duffin scanJar(file, classloader); 3287dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3297dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3300888a09821a98ac0680fad765217302858e70fa4Paul Duffin 3310888a09821a98ac0680fad765217302858e70fa4Paul Duffin private void scanDirectory(File directory, ClassLoader classloader) throws IOException { 3320888a09821a98ac0680fad765217302858e70fa4Paul Duffin scanDirectory(directory, classloader, "", ImmutableSet.<File>of()); 3337dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3340888a09821a98ac0680fad765217302858e70fa4Paul Duffin 3350888a09821a98ac0680fad765217302858e70fa4Paul Duffin private void scanDirectory( 3360888a09821a98ac0680fad765217302858e70fa4Paul Duffin File directory, ClassLoader classloader, String packagePrefix, 3370888a09821a98ac0680fad765217302858e70fa4Paul Duffin ImmutableSet<File> ancestors) throws IOException { 3380888a09821a98ac0680fad765217302858e70fa4Paul Duffin File canonical = directory.getCanonicalFile(); 3390888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (ancestors.contains(canonical)) { 3400888a09821a98ac0680fad765217302858e70fa4Paul Duffin // A cycle in the filesystem, for example due to a symbolic link. 3410888a09821a98ac0680fad765217302858e70fa4Paul Duffin return; 3420888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3430888a09821a98ac0680fad765217302858e70fa4Paul Duffin File[] files = directory.listFiles(); 3440888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (files == null) { 3450888a09821a98ac0680fad765217302858e70fa4Paul Duffin logger.warning("Cannot read directory " + directory); 3460888a09821a98ac0680fad765217302858e70fa4Paul Duffin // IO error, just skip the directory 3470888a09821a98ac0680fad765217302858e70fa4Paul Duffin return; 3487dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3490888a09821a98ac0680fad765217302858e70fa4Paul Duffin ImmutableSet<File> newAncestors = ImmutableSet.<File>builder() 3500888a09821a98ac0680fad765217302858e70fa4Paul Duffin .addAll(ancestors) 3510888a09821a98ac0680fad765217302858e70fa4Paul Duffin .add(canonical) 3520888a09821a98ac0680fad765217302858e70fa4Paul Duffin .build(); 3530888a09821a98ac0680fad765217302858e70fa4Paul Duffin for (File f : files) { 3540888a09821a98ac0680fad765217302858e70fa4Paul Duffin String name = f.getName(); 3550888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (f.isDirectory()) { 3560888a09821a98ac0680fad765217302858e70fa4Paul Duffin scanDirectory(f, classloader, packagePrefix + name + "/", newAncestors); 3570888a09821a98ac0680fad765217302858e70fa4Paul Duffin } else { 3580888a09821a98ac0680fad765217302858e70fa4Paul Duffin String resourceName = packagePrefix + name; 3590888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (!resourceName.equals(JarFile.MANIFEST_NAME)) { 3600888a09821a98ac0680fad765217302858e70fa4Paul Duffin resources.add(ResourceInfo.of(resourceName, classloader)); 3610888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3627dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3637dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3647dd252788645e940eada959bdde927426e2531c9Paul Duffin } 3650888a09821a98ac0680fad765217302858e70fa4Paul Duffin 3660888a09821a98ac0680fad765217302858e70fa4Paul Duffin private void scanJar(File file, ClassLoader classloader) throws IOException { 3670888a09821a98ac0680fad765217302858e70fa4Paul Duffin JarFile jarFile; 3680888a09821a98ac0680fad765217302858e70fa4Paul Duffin try { 3690888a09821a98ac0680fad765217302858e70fa4Paul Duffin jarFile = new JarFile(file); 3700888a09821a98ac0680fad765217302858e70fa4Paul Duffin } catch (IOException e) { 3710888a09821a98ac0680fad765217302858e70fa4Paul Duffin // Not a jar file 3720888a09821a98ac0680fad765217302858e70fa4Paul Duffin return; 3730888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3740888a09821a98ac0680fad765217302858e70fa4Paul Duffin try { 3750888a09821a98ac0680fad765217302858e70fa4Paul Duffin for (URI uri : getClassPathFromManifest(file, jarFile.getManifest())) { 3760888a09821a98ac0680fad765217302858e70fa4Paul Duffin scan(uri, classloader); 3770888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3780888a09821a98ac0680fad765217302858e70fa4Paul Duffin Enumeration<JarEntry> entries = jarFile.entries(); 3790888a09821a98ac0680fad765217302858e70fa4Paul Duffin while (entries.hasMoreElements()) { 3800888a09821a98ac0680fad765217302858e70fa4Paul Duffin JarEntry entry = entries.nextElement(); 3810888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) { 3820888a09821a98ac0680fad765217302858e70fa4Paul Duffin continue; 3830888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3840888a09821a98ac0680fad765217302858e70fa4Paul Duffin resources.add(ResourceInfo.of(entry.getName(), classloader)); 3850888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3860888a09821a98ac0680fad765217302858e70fa4Paul Duffin } finally { 3877dd252788645e940eada959bdde927426e2531c9Paul Duffin try { 3880888a09821a98ac0680fad765217302858e70fa4Paul Duffin jarFile.close(); 3890888a09821a98ac0680fad765217302858e70fa4Paul Duffin } catch (IOException ignored) {} 3900888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3910888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 3920888a09821a98ac0680fad765217302858e70fa4Paul Duffin 3930888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 3940888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according 3950888a09821a98ac0680fad765217302858e70fa4Paul Duffin * to <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Main%20Attributes"> 3960888a09821a98ac0680fad765217302858e70fa4Paul Duffin * JAR File Specification</a>. If {@code manifest} is null, it means the jar file has no 3970888a09821a98ac0680fad765217302858e70fa4Paul Duffin * manifest, and an empty set will be returned. 3980888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 3990888a09821a98ac0680fad765217302858e70fa4Paul Duffin @VisibleForTesting static ImmutableSet<URI> getClassPathFromManifest( 4000888a09821a98ac0680fad765217302858e70fa4Paul Duffin File jarFile, @Nullable Manifest manifest) { 4010888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (manifest == null) { 4020888a09821a98ac0680fad765217302858e70fa4Paul Duffin return ImmutableSet.of(); 4030888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 4040888a09821a98ac0680fad765217302858e70fa4Paul Duffin ImmutableSet.Builder<URI> builder = ImmutableSet.builder(); 4050888a09821a98ac0680fad765217302858e70fa4Paul Duffin String classpathAttribute = manifest.getMainAttributes() 4060888a09821a98ac0680fad765217302858e70fa4Paul Duffin .getValue(Attributes.Name.CLASS_PATH.toString()); 4070888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (classpathAttribute != null) { 4080888a09821a98ac0680fad765217302858e70fa4Paul Duffin for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) { 4090888a09821a98ac0680fad765217302858e70fa4Paul Duffin URI uri; 4100888a09821a98ac0680fad765217302858e70fa4Paul Duffin try { 4110888a09821a98ac0680fad765217302858e70fa4Paul Duffin uri = getClassPathEntry(jarFile, path); 4120888a09821a98ac0680fad765217302858e70fa4Paul Duffin } catch (URISyntaxException e) { 4130888a09821a98ac0680fad765217302858e70fa4Paul Duffin // Ignore bad entry 4140888a09821a98ac0680fad765217302858e70fa4Paul Duffin logger.warning("Invalid Class-Path entry: " + path); 4150888a09821a98ac0680fad765217302858e70fa4Paul Duffin continue; 4160888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 4170888a09821a98ac0680fad765217302858e70fa4Paul Duffin builder.add(uri); 4187dd252788645e940eada959bdde927426e2531c9Paul Duffin } 4197dd252788645e940eada959bdde927426e2531c9Paul Duffin } 4200888a09821a98ac0680fad765217302858e70fa4Paul Duffin return builder.build(); 4217dd252788645e940eada959bdde927426e2531c9Paul Duffin } 4220888a09821a98ac0680fad765217302858e70fa4Paul Duffin 4230888a09821a98ac0680fad765217302858e70fa4Paul Duffin /** 4240888a09821a98ac0680fad765217302858e70fa4Paul Duffin * Returns the absolute uri of the Class-Path entry value as specified in 4250888a09821a98ac0680fad765217302858e70fa4Paul Duffin * <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Main%20Attributes"> 4260888a09821a98ac0680fad765217302858e70fa4Paul Duffin * JAR File Specification</a>. Even though the specification only talks about relative urls, 4270888a09821a98ac0680fad765217302858e70fa4Paul Duffin * absolute urls are actually supported too (for example, in Maven surefire plugin). 4280888a09821a98ac0680fad765217302858e70fa4Paul Duffin */ 4290888a09821a98ac0680fad765217302858e70fa4Paul Duffin @VisibleForTesting static URI getClassPathEntry(File jarFile, String path) 4300888a09821a98ac0680fad765217302858e70fa4Paul Duffin throws URISyntaxException { 4310888a09821a98ac0680fad765217302858e70fa4Paul Duffin URI uri = new URI(path); 4320888a09821a98ac0680fad765217302858e70fa4Paul Duffin if (uri.isAbsolute()) { 4330888a09821a98ac0680fad765217302858e70fa4Paul Duffin return uri; 4340888a09821a98ac0680fad765217302858e70fa4Paul Duffin } else { 4350888a09821a98ac0680fad765217302858e70fa4Paul Duffin return new File(jarFile.getParentFile(), path.replace('/', File.separatorChar)).toURI(); 4360888a09821a98ac0680fad765217302858e70fa4Paul Duffin } 4377dd252788645e940eada959bdde927426e2531c9Paul Duffin } 4387dd252788645e940eada959bdde927426e2531c9Paul Duffin } 4397dd252788645e940eada959bdde927426e2531c9Paul Duffin 4407dd252788645e940eada959bdde927426e2531c9Paul Duffin @VisibleForTesting static String getClassName(String filename) { 4417dd252788645e940eada959bdde927426e2531c9Paul Duffin int classNameEnd = filename.length() - CLASS_FILE_NAME_EXTENSION.length(); 4427dd252788645e940eada959bdde927426e2531c9Paul Duffin return filename.substring(0, classNameEnd).replace('/', '.'); 4437dd252788645e940eada959bdde927426e2531c9Paul Duffin } 4447dd252788645e940eada959bdde927426e2531c9Paul Duffin} 445