View Javadoc
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 com.google.common.truth.Truth.assertThat;
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  
35  import org.junit.Test;
36  
37  import java.io.ByteArrayInputStream;
38  import java.io.File;
39  import java.io.FileOutputStream;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.net.URI;
43  import java.net.URISyntaxException;
44  import java.net.URL;
45  import java.net.URLClassLoader;
46  import java.util.Map;
47  import java.util.Set;
48  import java.util.jar.Attributes;
49  import java.util.jar.JarFile;
50  import java.util.jar.JarOutputStream;
51  import java.util.jar.Manifest;
52  import java.util.zip.ZipEntry;
53  
54  /**
55   * Functional tests of {@link ClassPath}.
56   */
57  public class ClassPathTest extends TestCase {
58  
59    public void testGetResources() throws Exception {
60      Map<String, ResourceInfo> byName = Maps.newHashMap();
61      Map<String, ResourceInfo> byToString = Maps.newHashMap();
62      ClassPath classpath = ClassPath.from(getClass().getClassLoader());
63      for (ResourceInfo resource : classpath.getResources()) {
64        assertThat(resource.getResourceName()).isNotEqualTo(JarFile.MANIFEST_NAME);
65        assertThat(resource.toString()).isNotEqualTo(JarFile.MANIFEST_NAME);
66        byName.put(resource.getResourceName(), resource);
67        byToString.put(resource.toString(), resource);
68        assertNotNull(resource.url());
69      }
70      String testResourceName = "com/google/common/reflect/test.txt";
71      assertThat(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      assertThat(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     assertThat(names).has().allOf(anonymousClass.getName(), LocalClass.class.getName(),
105         ClassPath.class.getName(), ClassPathTest.class.getName());
106     assertThat(strings).has().allOf(anonymousClass.getName(), LocalClass.class.getName(),
107         ClassPath.class.getName(), ClassPathTest.class.getName());
108     assertThat(classes).has().allOf(anonymousClass, LocalClass.class, ClassPath.class,
109         ClassPathTest.class);
110     assertThat(packageNames).has().exactly(ClassPath.class.getPackage().getName());
111     assertThat(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     assertThat(names).has().allOf(ClassPath.class.getName(), ClassPathTest.class.getName());
130     assertThat(strings).has().allOf(ClassPath.class.getName(), ClassPathTest.class.getName());
131     assertThat(classes).has().allOf(ClassPath.class, ClassPathTest.class);
132     assertThat(packageNames).has().item(ClassPath.class.getPackage().getName());
133     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest("")))
282         .isEmpty();
283   }
284 
285   public void testGetClassPathFromManifest_emptyClassPath() throws IOException {
286     File jarFile = new File("base.jar");
287     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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     assertThat(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));
455     Manifest manifest = new Manifest();
456     manifest.read(in);
457     return manifest;
458   }
459 }