Home | History | Annotate | Download | only in reflect
      1 /*
      2  * Copyright (C) 2012 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.reflect;
     18 
     19 import static org.truth0.Truth.ASSERT;
     20 
     21 import com.google.common.base.Charsets;
     22 import com.google.common.collect.ImmutableMap;
     23 import com.google.common.collect.Maps;
     24 import com.google.common.collect.Sets;
     25 import com.google.common.io.Closer;
     26 import com.google.common.io.Resources;
     27 import com.google.common.reflect.ClassPath.ClassInfo;
     28 import com.google.common.reflect.ClassPath.ResourceInfo;
     29 import com.google.common.reflect.subpackage.ClassInSubPackage;
     30 import com.google.common.testing.EqualsTester;
     31 import com.google.common.testing.NullPointerTester;
     32 
     33 import junit.framework.TestCase;
     34 import org.junit.Test;
     35 
     36 import java.io.ByteArrayInputStream;
     37 import java.io.File;
     38 import java.io.FileOutputStream;
     39 import java.io.IOException;
     40 import java.io.InputStream;
     41 import java.net.URI;
     42 import java.net.URISyntaxException;
     43 import java.net.URL;
     44 import java.net.URLClassLoader;
     45 import java.util.Map;
     46 import java.util.Set;
     47 import java.util.jar.Attributes;
     48 import java.util.jar.JarFile;
     49 import java.util.jar.JarOutputStream;
     50 import java.util.jar.Manifest;
     51 import java.util.zip.ZipEntry;
     52 
     53 /**
     54  * Functional tests of {@link ClassPath}.
     55  */
     56 public class ClassPathTest extends TestCase {
     57 
     58   public void testGetResources() throws Exception {
     59     Map<String, ResourceInfo> byName = Maps.newHashMap();
     60     Map<String, ResourceInfo> byToString = Maps.newHashMap();
     61     ClassPath classpath = ClassPath.from(getClass().getClassLoader());
     62     for (ResourceInfo resource : classpath.getResources()) {
     63       ASSERT.that(resource.getResourceName()).isNotEqualTo(JarFile.MANIFEST_NAME);
     64       ASSERT.that(resource.toString()).isNotEqualTo(JarFile.MANIFEST_NAME);
     65       byName.put(resource.getResourceName(), resource);
     66       byToString.put(resource.toString(), resource);
     67       // TODO: This will fail on maven resources in the classes directory on a mac.
     68       // assertNotNull(resource.url());
     69     }
     70     String testResourceName = "com/google/common/reflect/test.txt";
     71     ASSERT.that(byName.keySet()).has().allOf(
     72         "com/google/common/reflect/ClassPath.class",
     73         "com/google/common/reflect/ClassPathTest.class",
     74         "com/google/common/reflect/ClassPathTest$Nested.class",
     75         testResourceName);
     76     ASSERT.that(byToString.keySet()).has().allOf(
     77         "com.google.common.reflect.ClassPath",
     78         "com.google.common.reflect.ClassPathTest",
     79         "com.google.common.reflect.ClassPathTest$Nested",
     80         testResourceName);
     81     assertEquals(getClass().getClassLoader().getResource(testResourceName),
     82         byName.get("com/google/common/reflect/test.txt").url());
     83   }
     84 
     85   public void testGetAllClasses() throws Exception {
     86     Set<String> names = Sets.newHashSet();
     87     Set<String> strings = Sets.newHashSet();
     88     Set<Class<?>> classes = Sets.newHashSet();
     89     Set<String> packageNames = Sets.newHashSet();
     90     Set<String> simpleNames = Sets.newHashSet();
     91     ClassPath classpath = ClassPath.from(getClass().getClassLoader());
     92     for (ClassInfo classInfo : classpath.getAllClasses()) {
     93       if (!classInfo.getPackageName().equals(ClassPathTest.class.getPackage().getName())) {
     94         continue;
     95       }
     96       names.add(classInfo.getName());
     97       strings.add(classInfo.toString());
     98       classes.add(classInfo.load());
     99       packageNames.add(classInfo.getPackageName());
    100       simpleNames.add(classInfo.getSimpleName());
    101     }
    102     class LocalClass {}
    103     Class<?> anonymousClass = new Object() {}.getClass();
    104     ASSERT.that(names).has().allOf(anonymousClass.getName(), LocalClass.class.getName(),
    105         ClassPath.class.getName(), ClassPathTest.class.getName());
    106     ASSERT.that(strings).has().allOf(anonymousClass.getName(), LocalClass.class.getName(),
    107         ClassPath.class.getName(), ClassPathTest.class.getName());
    108     ASSERT.that(classes).has().allOf(anonymousClass, LocalClass.class, ClassPath.class,
    109         ClassPathTest.class);
    110     ASSERT.that(packageNames).has().exactly(ClassPath.class.getPackage().getName());
    111     ASSERT.that(simpleNames).has().allOf("", "Local", "ClassPath", "ClassPathTest");
    112   }
    113 
    114   public void testGetTopLevelClasses() throws Exception {
    115     Set<String> names = Sets.newHashSet();
    116     Set<String> strings = Sets.newHashSet();
    117     Set<Class<?>> classes = Sets.newHashSet();
    118     Set<String> packageNames = Sets.newHashSet();
    119     Set<String> simpleNames = Sets.newHashSet();
    120     ClassPath classpath = ClassPath.from(getClass().getClassLoader());
    121     for (ClassInfo classInfo
    122         : classpath.getTopLevelClasses(ClassPathTest.class.getPackage().getName())) {
    123       names.add(classInfo.getName());
    124       strings.add(classInfo.toString());
    125       classes.add(classInfo.load());
    126       packageNames.add(classInfo.getPackageName());
    127       simpleNames.add(classInfo.getSimpleName());
    128     }
    129     ASSERT.that(names).has().allOf(ClassPath.class.getName(), ClassPathTest.class.getName());
    130     ASSERT.that(strings).has().allOf(ClassPath.class.getName(), ClassPathTest.class.getName());
    131     ASSERT.that(classes).has().allOf(ClassPath.class, ClassPathTest.class);
    132     ASSERT.that(packageNames).has().item(ClassPath.class.getPackage().getName());
    133     ASSERT.that(simpleNames).has().allOf("ClassPath", "ClassPathTest");
    134     assertFalse(classes.contains(ClassInSubPackage.class));
    135   }
    136 
    137   public void testGetTopLevelClassesRecursive() throws Exception {
    138     Set<Class<?>> classes = Sets.newHashSet();
    139     ClassPath classpath = ClassPath.from(ClassPathTest.class.getClassLoader());
    140     for (ClassInfo classInfo
    141         : classpath.getTopLevelClassesRecursive(ClassPathTest.class.getPackage().getName())) {
    142       if (classInfo.getName().contains("ClassPathTest")) {
    143         System.err.println("");
    144       }
    145       classes.add(classInfo.load());
    146     }
    147     ASSERT.that(classes).has().allOf(ClassPathTest.class, ClassInSubPackage.class);
    148   }
    149 
    150   public void testGetTopLevelClasses_diamond() throws Exception {
    151     ClassLoader parent = ClassPathTest.class.getClassLoader();
    152     ClassLoader sub1 = new ClassLoader(parent) {};
    153     ClassLoader sub2 = new ClassLoader(parent) {};
    154     assertEquals(findClass(ClassPath.from(sub1).getTopLevelClasses(), ClassPathTest.class),
    155         findClass(ClassPath.from(sub2).getTopLevelClasses(), ClassPathTest.class));
    156   }
    157 
    158   public void testEquals() {
    159     new EqualsTester()
    160         .addEqualityGroup(classInfo(ClassPathTest.class), classInfo(ClassPathTest.class))
    161         .addEqualityGroup(classInfo(Test.class), classInfo(Test.class, getClass().getClassLoader()))
    162         .addEqualityGroup(
    163             new ResourceInfo("a/b/c.txt", getClass().getClassLoader()),
    164             new ResourceInfo("a/b/c.txt", getClass().getClassLoader()))
    165         .addEqualityGroup(
    166             new ResourceInfo("x.txt", getClass().getClassLoader()))
    167         .testEquals();
    168   }
    169 
    170   public void testClassPathEntries_emptyURLClassLoader_noParent() {
    171     ASSERT.that(ClassPath.getClassPathEntries(new URLClassLoader(new URL[0], null)).keySet())
    172         .isEmpty();
    173   }
    174 
    175   public void testClassPathEntries_URLClassLoader_noParent() throws Exception {
    176     URL url1 = new URL("file:/a");
    177     URL url2 = new URL("file:/b");
    178     URLClassLoader classloader = new URLClassLoader(new URL[] {url1, url2}, null);
    179     assertEquals(
    180         ImmutableMap.of(url1.toURI(), classloader, url2.toURI(), classloader),
    181         ClassPath.getClassPathEntries(classloader));
    182   }
    183 
    184   public void testClassPathEntries_URLClassLoader_withParent() throws Exception {
    185     URL url1 = new URL("file:/a");
    186     URL url2 = new URL("file:/b");
    187     URLClassLoader parent = new URLClassLoader(new URL[] {url1}, null);
    188     URLClassLoader child = new URLClassLoader(new URL[] {url2}, parent) {};
    189     ImmutableMap<URI, ClassLoader> classPathEntries = ClassPath.getClassPathEntries(child);
    190     assertEquals(ImmutableMap.of(url1.toURI(), parent, url2.toURI(), child),  classPathEntries);
    191     ASSERT.that(classPathEntries.keySet()).has().exactly(url1.toURI(), url2.toURI()).inOrder();
    192   }
    193 
    194   public void testClassPathEntries_duplicateUri_parentWins() throws Exception {
    195     URL url = new URL("file:/a");
    196     URLClassLoader parent = new URLClassLoader(new URL[] {url}, null);
    197     URLClassLoader child = new URLClassLoader(new URL[] {url}, parent) {};
    198     assertEquals(ImmutableMap.of(url.toURI(), parent), ClassPath.getClassPathEntries(child));
    199   }
    200 
    201   public void testClassPathEntries_notURLClassLoader_noParent() {
    202     ASSERT.that(ClassPath.getClassPathEntries(new ClassLoader(null) {}).keySet()).isEmpty();
    203   }
    204 
    205   public void testClassPathEntries_notURLClassLoader_withParent() throws Exception {
    206     URL url = new URL("file:/a");
    207     URLClassLoader parent = new URLClassLoader(new URL[] {url}, null);
    208     assertEquals(
    209         ImmutableMap.of(url.toURI(), parent),
    210         ClassPath.getClassPathEntries(new ClassLoader(parent) {}));
    211   }
    212 
    213   public void testClassPathEntries_notURLClassLoader_withParentAndGrandParent() throws Exception {
    214     URL url1 = new URL("file:/a");
    215     URL url2 = new URL("file:/b");
    216     URLClassLoader grandParent = new URLClassLoader(new URL[] {url1}, null);
    217     URLClassLoader parent = new URLClassLoader(new URL[] {url2}, grandParent);
    218     assertEquals(
    219         ImmutableMap.of(url1.toURI(), grandParent, url2.toURI(), parent),
    220         ClassPath.getClassPathEntries(new ClassLoader(parent) {}));
    221   }
    222 
    223   public void testClassPathEntries_notURLClassLoader_withGrandParent() throws Exception {
    224     URL url = new URL("file:/a");
    225     URLClassLoader grandParent = new URLClassLoader(new URL[] {url}, null);
    226     ClassLoader parent = new ClassLoader(grandParent) {};
    227     assertEquals(
    228         ImmutableMap.of(url.toURI(), grandParent),
    229         ClassPath.getClassPathEntries(new ClassLoader(parent) {}));
    230   }
    231 
    232   public void testScan_classPathCycle() throws IOException {
    233     File jarFile = File.createTempFile("with_circular_class_path", ".jar");
    234     try {
    235       writeSelfReferencingJarFile(jarFile, "test.txt");
    236       ClassPath.Scanner scanner = new ClassPath.Scanner();
    237       scanner.scan(jarFile.toURI(), ClassPathTest.class.getClassLoader());
    238       assertEquals(1, scanner.getResources().size());
    239     } finally {
    240       jarFile.delete();
    241     }
    242   }
    243 
    244   public void testScanFromFile_fileNotExists() throws IOException {
    245     ClassLoader classLoader = ClassPathTest.class.getClassLoader();
    246     ClassPath.Scanner scanner = new ClassPath.Scanner();
    247     scanner.scanFrom(new File("no/such/file/anywhere"), classLoader);
    248     ASSERT.that(scanner.getResources()).isEmpty();
    249   }
    250 
    251   public void testScanFromFile_notJarFile() throws IOException {
    252     ClassLoader classLoader = ClassPathTest.class.getClassLoader();
    253     File notJar = File.createTempFile("not_a_jar", "txt");
    254     ClassPath.Scanner scanner = new ClassPath.Scanner();
    255     try {
    256       scanner.scanFrom(notJar, classLoader);
    257     } finally {
    258       notJar.delete();
    259     }
    260     ASSERT.that(scanner.getResources()).isEmpty();
    261   }
    262 
    263   public void testGetClassPathEntry() throws URISyntaxException {
    264     assertEquals(URI.create("file:/usr/test/dep.jar"),
    265         ClassPath.Scanner.getClassPathEntry(
    266             new File("/home/build/outer.jar"), "file:/usr/test/dep.jar"));
    267     assertEquals(URI.create("file:/home/build/a.jar"),
    268         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "a.jar"));
    269     assertEquals(URI.create("file:/home/build/x/y/z"),
    270         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z"));
    271     assertEquals(URI.create("file:/home/build/x/y/z.jar"),
    272         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z.jar"));
    273   }
    274 
    275   public void testGetClassPathFromManifest_nullManifest() {
    276     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(new File("some.jar"), null)).isEmpty();
    277   }
    278 
    279   public void testGetClassPathFromManifest_noClassPath() throws IOException {
    280     File jarFile = new File("base.jar");
    281     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest("")))
    282         .isEmpty();
    283   }
    284 
    285   public void testGetClassPathFromManifest_emptyClassPath() throws IOException {
    286     File jarFile = new File("base.jar");
    287     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifestClasspath("")))
    288         .isEmpty();
    289   }
    290 
    291   public void testGetClassPathFromManifest_badClassPath() throws IOException {
    292     File jarFile = new File("base.jar");
    293     Manifest manifest = manifestClasspath("an_invalid^path");
    294     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    295         .isEmpty();
    296   }
    297 
    298   public void testGetClassPathFromManifest_relativeDirectory() throws IOException {
    299     File jarFile = new File("base/some.jar");
    300     // with/relative/directory is the Class-Path value in the mf file.
    301     Manifest manifest = manifestClasspath("with/relative/dir");
    302     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    303         .has().exactly(new File("base/with/relative/dir").toURI()).inOrder();
    304   }
    305 
    306   public void testGetClassPathFromManifest_relativeJar() throws IOException {
    307     File jarFile = new File("base/some.jar");
    308     // with/relative/directory is the Class-Path value in the mf file.
    309     Manifest manifest = manifestClasspath("with/relative.jar");
    310     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    311         .has().exactly(new File("base/with/relative.jar").toURI()).inOrder();
    312   }
    313 
    314   public void testGetClassPathFromManifest_jarInCurrentDirectory() throws IOException {
    315     File jarFile = new File("base/some.jar");
    316     // with/relative/directory is the Class-Path value in the mf file.
    317     Manifest manifest = manifestClasspath("current.jar");
    318     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    319         .has().exactly(new File("base/current.jar").toURI()).inOrder();
    320   }
    321 
    322   public void testGetClassPathFromManifest_absoluteDirectory() throws IOException {
    323     File jarFile = new File("base/some.jar");
    324     Manifest manifest = manifestClasspath("file:/with/absolute/dir");
    325     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    326         .has().exactly(new File("/with/absolute/dir").toURI()).inOrder();
    327   }
    328 
    329   public void testGetClassPathFromManifest_absoluteJar() throws IOException {
    330     File jarFile = new File("base/some.jar");
    331     Manifest manifest = manifestClasspath("file:/with/absolute.jar");
    332     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    333         .has().exactly(new File("/with/absolute.jar").toURI()).inOrder();
    334   }
    335 
    336   public void testGetClassPathFromManifest_multiplePaths() throws IOException {
    337     File jarFile = new File("base/some.jar");
    338     Manifest manifest = manifestClasspath("file:/with/absolute.jar relative.jar  relative/dir");
    339     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    340         .has().exactly(
    341             new File("/with/absolute.jar").toURI(),
    342             new File("base/relative.jar").toURI(),
    343             new File("base/relative/dir").toURI())
    344         .inOrder();
    345   }
    346 
    347   public void testGetClassPathFromManifest_leadingBlanks() throws IOException {
    348     File jarFile = new File("base/some.jar");
    349     Manifest manifest = manifestClasspath(" relative.jar");
    350     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    351         .has().exactly(new File("base/relative.jar").toURI()).inOrder();
    352   }
    353 
    354   public void testGetClassPathFromManifest_trailingBlanks() throws IOException {
    355     File jarFile = new File("base/some.jar");
    356     Manifest manifest = manifestClasspath("relative.jar ");
    357     ASSERT.that(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
    358         .has().exactly(new File("base/relative.jar").toURI()).inOrder();
    359   }
    360 
    361   public void testGetClassName() {
    362     assertEquals("abc.d.Abc", ClassPath.getClassName("abc/d/Abc.class"));
    363   }
    364 
    365   public void testResourceInfo_of() {
    366     assertEquals(ClassInfo.class, resourceInfo(ClassPathTest.class).getClass());
    367     assertEquals(ClassInfo.class, resourceInfo(ClassPath.class).getClass());
    368     assertEquals(ClassInfo.class, resourceInfo(Nested.class).getClass());
    369   }
    370 
    371   public void testGetSimpleName() {
    372     assertEquals("Foo",
    373         new ClassInfo("Foo.class", getClass().getClassLoader()).getSimpleName());
    374     assertEquals("Foo",
    375         new ClassInfo("a/b/Foo.class", getClass().getClassLoader()).getSimpleName());
    376     assertEquals("Foo",
    377         new ClassInfo("a/b/Bar$Foo.class", getClass().getClassLoader()).getSimpleName());
    378     assertEquals("",
    379         new ClassInfo("a/b/Bar$1.class", getClass().getClassLoader()).getSimpleName());
    380     assertEquals("Foo",
    381         new ClassInfo("a/b/Bar$Foo.class", getClass().getClassLoader()).getSimpleName());
    382     assertEquals("",
    383         new ClassInfo("a/b/Bar$1.class", getClass().getClassLoader()).getSimpleName());
    384     assertEquals("Local",
    385         new ClassInfo("a/b/Bar$1Local.class", getClass().getClassLoader()).getSimpleName());
    386 
    387   }
    388 
    389   public void testGetPackageName() {
    390     assertEquals("",
    391         new ClassInfo("Foo.class", getClass().getClassLoader()).getPackageName());
    392     assertEquals("a.b",
    393         new ClassInfo("a/b/Foo.class", getClass().getClassLoader()).getPackageName());
    394   }
    395 
    396   private static class Nested {}
    397 
    398   public void testNulls() throws IOException {
    399     new NullPointerTester().testAllPublicStaticMethods(ClassPath.class);
    400     new NullPointerTester()
    401         .testAllPublicInstanceMethods(ClassPath.from(getClass().getClassLoader()));
    402   }
    403 
    404   private static ClassPath.ClassInfo findClass(
    405       Iterable<ClassPath.ClassInfo> classes, Class<?> cls) {
    406     for (ClassPath.ClassInfo classInfo : classes) {
    407       if (classInfo.getName().equals(cls.getName())) {
    408         return classInfo;
    409       }
    410     }
    411     throw new AssertionError("failed to find " + cls);
    412   }
    413 
    414   private static ResourceInfo resourceInfo(Class<?> cls) {
    415     return ResourceInfo.of(cls.getName().replace('.', '/') + ".class", cls.getClassLoader());
    416   }
    417 
    418   private static ClassInfo classInfo(Class<?> cls) {
    419     return classInfo(cls, cls.getClassLoader());
    420   }
    421 
    422   private static ClassInfo classInfo(Class<?> cls, ClassLoader classLoader) {
    423     return new ClassInfo(cls.getName().replace('.', '/') + ".class", classLoader);
    424   }
    425 
    426   private static Manifest manifestClasspath(String classpath) throws IOException {
    427     return manifest("Class-Path: " + classpath + "\n");
    428   }
    429 
    430   private static void writeSelfReferencingJarFile(File jarFile, String... entries)
    431       throws IOException {
    432     Manifest manifest = new Manifest();
    433     // Without version, the manifest is silently ignored. Ugh!
    434     manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
    435     manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, jarFile.getName());
    436 
    437     Closer closer = Closer.create();
    438     try {
    439       FileOutputStream fileOut = closer.register(new FileOutputStream(jarFile));
    440       JarOutputStream jarOut = closer.register(new JarOutputStream(fileOut));
    441       for (String entry : entries) {
    442         jarOut.putNextEntry(new ZipEntry(entry));
    443         Resources.copy(ClassPathTest.class.getResource(entry), jarOut);
    444         jarOut.closeEntry();
    445       }
    446     } catch (Throwable e) {
    447       throw closer.rethrow(e);
    448     } finally {
    449       closer.close();
    450     }
    451   }
    452 
    453   private static Manifest manifest(String content) throws IOException {
    454     InputStream in = new ByteArrayInputStream(content.getBytes(Charsets.US_ASCII.name()));
    455     Manifest manifest = new Manifest();
    456     manifest.read(in);
    457     return manifest;
    458   }
    459 }
    460