• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.base.Charsets.US_ASCII;
20 import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
21 import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import com.google.common.base.Joiner;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.io.Closer;
27 import com.google.common.io.Files;
28 import com.google.common.io.Resources;
29 import com.google.common.reflect.ClassPath.ClassInfo;
30 import com.google.common.reflect.ClassPath.ResourceInfo;
31 import com.google.common.testing.EqualsTester;
32 import com.google.common.testing.NullPointerTester;
33 import java.io.ByteArrayInputStream;
34 import java.io.File;
35 import java.io.FileOutputStream;
36 import java.io.FilePermission;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.net.MalformedURLException;
40 import java.net.URISyntaxException;
41 import java.net.URL;
42 import java.net.URLClassLoader;
43 import java.security.Permission;
44 import java.security.PermissionCollection;
45 import java.util.HashSet;
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.logging.Logger;
52 import java.util.zip.ZipEntry;
53 import junit.framework.TestCase;
54 import org.junit.Test;
55 
56 /** Functional tests of {@link ClassPath}. */
57 public class ClassPathTest extends TestCase {
58   private static final Logger log = Logger.getLogger(ClassPathTest.class.getName());
59   private static final File FILE = new File(".");
60 
testEquals()61   public void testEquals() {
62     new EqualsTester()
63         .addEqualityGroup(classInfo(ClassPathTest.class), classInfo(ClassPathTest.class))
64         .addEqualityGroup(classInfo(Test.class), classInfo(Test.class, getClass().getClassLoader()))
65         .addEqualityGroup(
66             new ResourceInfo(FILE, "a/b/c.txt", getClass().getClassLoader()),
67             new ResourceInfo(FILE, "a/b/c.txt", getClass().getClassLoader()))
68         .addEqualityGroup(new ResourceInfo(FILE, "x.txt", getClass().getClassLoader()))
69         .testEquals();
70   }
71 
72   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_emptyURLClassLoader_noParent()73   public void testClassPathEntries_emptyURLClassLoader_noParent() {
74     assertThat(ClassPath.Scanner.getClassPathEntries(new URLClassLoader(new URL[0], null)).keySet())
75         .isEmpty();
76   }
77 
78   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_URLClassLoader_noParent()79   public void testClassPathEntries_URLClassLoader_noParent() throws Exception {
80     URL url1 = new URL("file:/a");
81     URL url2 = new URL("file:/b");
82     URLClassLoader classloader = new URLClassLoader(new URL[] {url1, url2}, null);
83     assertThat(ClassPath.Scanner.getClassPathEntries(classloader))
84         .containsExactly(new File("/a"), classloader, new File("/b"), classloader);
85   }
86 
87   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_URLClassLoader_withParent()88   public void testClassPathEntries_URLClassLoader_withParent() throws Exception {
89     URL url1 = new URL("file:/a");
90     URL url2 = new URL("file:/b");
91     URLClassLoader parent = new URLClassLoader(new URL[] {url1}, null);
92     URLClassLoader child = new URLClassLoader(new URL[] {url2}, parent) {};
93     assertThat(ClassPath.Scanner.getClassPathEntries(child))
94         .containsExactly(new File("/a"), parent, new File("/b"), child)
95         .inOrder();
96   }
97 
98   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_duplicateUri_parentWins()99   public void testClassPathEntries_duplicateUri_parentWins() throws Exception {
100     URL url = new URL("file:/a");
101     URLClassLoader parent = new URLClassLoader(new URL[] {url}, null);
102     URLClassLoader child = new URLClassLoader(new URL[] {url}, parent) {};
103     assertThat(ClassPath.Scanner.getClassPathEntries(child))
104         .containsExactly(new File("/a"), parent);
105   }
106 
107   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_noParent()108   public void testClassPathEntries_notURLClassLoader_noParent() {
109     assertThat(ClassPath.Scanner.getClassPathEntries(new ClassLoader(null) {})).isEmpty();
110   }
111 
112   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_withParent()113   public void testClassPathEntries_notURLClassLoader_withParent() throws Exception {
114     URL url = new URL("file:/a");
115     URLClassLoader parent = new URLClassLoader(new URL[] {url}, null);
116     assertThat(ClassPath.Scanner.getClassPathEntries(new ClassLoader(parent) {}))
117         .containsExactly(new File("/a"), parent);
118   }
119 
120   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_withParentAndGrandParent()121   public void testClassPathEntries_notURLClassLoader_withParentAndGrandParent() throws Exception {
122     URL url1 = new URL("file:/a");
123     URL url2 = new URL("file:/b");
124     URLClassLoader grandParent = new URLClassLoader(new URL[] {url1}, null);
125     URLClassLoader parent = new URLClassLoader(new URL[] {url2}, grandParent);
126     assertThat(ClassPath.Scanner.getClassPathEntries(new ClassLoader(parent) {}))
127         .containsExactly(new File("/a"), grandParent, new File("/b"), parent);
128   }
129 
130   @AndroidIncompatible // Android forbids null parent ClassLoader
testClassPathEntries_notURLClassLoader_withGrandParent()131   public void testClassPathEntries_notURLClassLoader_withGrandParent() throws Exception {
132     URL url = new URL("file:/a");
133     URLClassLoader grandParent = new URLClassLoader(new URL[] {url}, null);
134     ClassLoader parent = new ClassLoader(grandParent) {};
135     assertThat(ClassPath.Scanner.getClassPathEntries(new ClassLoader(parent) {}))
136         .containsExactly(new File("/a"), grandParent);
137   }
138 
139   @AndroidIncompatible // Android forbids null parent ClassLoader
140   // https://github.com/google/guava/issues/2152
testClassPathEntries_URLClassLoader_pathWithSpace()141   public void testClassPathEntries_URLClassLoader_pathWithSpace() throws Exception {
142     URL url = new URL("file:///c:/Documents and Settings/");
143     URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null);
144     assertThat(ClassPath.Scanner.getClassPathEntries(classloader))
145         .containsExactly(new File("/c:/Documents and Settings/"), classloader);
146   }
147 
148   @AndroidIncompatible // Android forbids null parent ClassLoader
149   // https://github.com/google/guava/issues/2152
testClassPathEntries_URLClassLoader_pathWithEscapedSpace()150   public void testClassPathEntries_URLClassLoader_pathWithEscapedSpace() throws Exception {
151     URL url = new URL("file:///c:/Documents%20and%20Settings/");
152     URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null);
153     assertThat(ClassPath.Scanner.getClassPathEntries(classloader))
154         .containsExactly(new File("/c:/Documents and Settings/"), classloader);
155   }
156 
157   // https://github.com/google/guava/issues/2152
testToFile()158   public void testToFile() throws Exception {
159     assertThat(ClassPath.toFile(new URL("file:///c:/Documents%20and%20Settings/")))
160         .isEqualTo(new File("/c:/Documents and Settings/"));
161     assertThat(ClassPath.toFile(new URL("file:///c:/Documents ~ Settings, or not/11-12 12:05")))
162         .isEqualTo(new File("/c:/Documents ~ Settings, or not/11-12 12:05"));
163   }
164 
165   // https://github.com/google/guava/issues/2152
166   @AndroidIncompatible // works in newer Android versions but fails at the version we test with
testToFile_AndroidIncompatible()167   public void testToFile_AndroidIncompatible() throws Exception {
168     assertThat(ClassPath.toFile(new URL("file:///c:\\Documents ~ Settings, or not\\11-12 12:05")))
169         .isEqualTo(new File("/c:\\Documents ~ Settings, or not\\11-12 12:05"));
170     assertThat(ClassPath.toFile(new URL("file:///C:\\Program Files\\Apache Software Foundation")))
171         .isEqualTo(new File("/C:\\Program Files\\Apache Software Foundation/"));
172     assertThat(ClassPath.toFile(new URL("file:///C:\\\u20320 \u22909"))) // Chinese Ni Hao
173         .isEqualTo(new File("/C:\\\u20320 \u22909"));
174   }
175 
176   @AndroidIncompatible // Android forbids null parent ClassLoader
177   // https://github.com/google/guava/issues/2152
testJarFileWithSpaces()178   public void testJarFileWithSpaces() throws Exception {
179     URL url = makeJarUrlWithName("To test unescaped spaces in jar file name.jar");
180     URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null);
181     assertThat(ClassPath.from(classloader).getTopLevelClasses()).isNotEmpty();
182   }
183 
testScan_classPathCycle()184   public void testScan_classPathCycle() throws IOException {
185     File jarFile = File.createTempFile("with_circular_class_path", ".jar");
186     try {
187       writeSelfReferencingJarFile(jarFile, "test.txt");
188       ClassPath.DefaultScanner scanner = new ClassPath.DefaultScanner();
189       scanner.scan(jarFile, ClassPathTest.class.getClassLoader());
190       assertThat(scanner.getResources()).hasSize(1);
191     } finally {
192       jarFile.delete();
193     }
194   }
195 
testScanFromFile_fileNotExists()196   public void testScanFromFile_fileNotExists() throws IOException {
197     ClassLoader classLoader = ClassPathTest.class.getClassLoader();
198     ClassPath.DefaultScanner scanner = new ClassPath.DefaultScanner();
199     scanner.scan(new File("no/such/file/anywhere"), classLoader);
200     assertThat(scanner.getResources()).isEmpty();
201   }
202 
testScanFromFile_notJarFile()203   public void testScanFromFile_notJarFile() throws IOException {
204     ClassLoader classLoader = ClassPathTest.class.getClassLoader();
205     File notJar = File.createTempFile("not_a_jar", "txt");
206     ClassPath.DefaultScanner scanner = new ClassPath.DefaultScanner();
207     try {
208       scanner.scan(notJar, classLoader);
209     } finally {
210       notJar.delete();
211     }
212     assertThat(scanner.getResources()).isEmpty();
213   }
214 
testGetClassPathEntry()215   public void testGetClassPathEntry() throws MalformedURLException, URISyntaxException {
216     assertEquals(
217         new File("/usr/test/dep.jar").toURI(),
218         ClassPath.Scanner.getClassPathEntry(
219                 new File("/home/build/outer.jar"), "file:/usr/test/dep.jar")
220             .toURI());
221     assertEquals(
222         new File("/home/build/a.jar").toURI(),
223         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "a.jar").toURI());
224     assertEquals(
225         new File("/home/build/x/y/z").toURI(),
226         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z").toURI());
227     assertEquals(
228         new File("/home/build/x/y/z.jar").toURI(),
229         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x/y/z.jar")
230             .toURI());
231     assertEquals(
232         "/home/build/x y.jar",
233         ClassPath.Scanner.getClassPathEntry(new File("/home/build/outer.jar"), "x y.jar")
234             .getFile());
235   }
236 
testGetClassPathFromManifest_nullManifest()237   public void testGetClassPathFromManifest_nullManifest() {
238     assertThat(ClassPath.Scanner.getClassPathFromManifest(new File("some.jar"), null)).isEmpty();
239   }
240 
testGetClassPathFromManifest_noClassPath()241   public void testGetClassPathFromManifest_noClassPath() throws IOException {
242     File jarFile = new File("base.jar");
243     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest(""))).isEmpty();
244   }
245 
testGetClassPathFromManifest_emptyClassPath()246   public void testGetClassPathFromManifest_emptyClassPath() throws IOException {
247     File jarFile = new File("base.jar");
248     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifestClasspath("")))
249         .isEmpty();
250   }
251 
testGetClassPathFromManifest_badClassPath()252   public void testGetClassPathFromManifest_badClassPath() throws IOException {
253     File jarFile = new File("base.jar");
254     Manifest manifest = manifestClasspath("nosuchscheme:an_invalid^path");
255     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest)).isEmpty();
256   }
257 
testGetClassPathFromManifest_pathWithStrangeCharacter()258   public void testGetClassPathFromManifest_pathWithStrangeCharacter() throws IOException {
259     File jarFile = new File("base/some.jar");
260     Manifest manifest = manifestClasspath("file:the^file.jar");
261     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
262         .containsExactly(fullpath("base/the^file.jar"));
263   }
264 
testGetClassPathFromManifest_relativeDirectory()265   public void testGetClassPathFromManifest_relativeDirectory() throws IOException {
266     File jarFile = new File("base/some.jar");
267     // with/relative/directory is the Class-Path value in the mf file.
268     Manifest manifest = manifestClasspath("with/relative/dir");
269     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
270         .containsExactly(fullpath("base/with/relative/dir"));
271   }
272 
testGetClassPathFromManifest_relativeJar()273   public void testGetClassPathFromManifest_relativeJar() throws IOException {
274     File jarFile = new File("base/some.jar");
275     // with/relative/directory is the Class-Path value in the mf file.
276     Manifest manifest = manifestClasspath("with/relative.jar");
277     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
278         .containsExactly(fullpath("base/with/relative.jar"));
279   }
280 
testGetClassPathFromManifest_jarInCurrentDirectory()281   public void testGetClassPathFromManifest_jarInCurrentDirectory() throws IOException {
282     File jarFile = new File("base/some.jar");
283     // with/relative/directory is the Class-Path value in the mf file.
284     Manifest manifest = manifestClasspath("current.jar");
285     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
286         .containsExactly(fullpath("base/current.jar"));
287   }
288 
testGetClassPathFromManifest_absoluteDirectory()289   public void testGetClassPathFromManifest_absoluteDirectory() throws IOException {
290     File jarFile = new File("base/some.jar");
291     Manifest manifest = manifestClasspath("file:/with/absolute/dir");
292     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
293         .containsExactly(fullpath("/with/absolute/dir"));
294   }
295 
testGetClassPathFromManifest_absoluteJar()296   public void testGetClassPathFromManifest_absoluteJar() throws IOException {
297     File jarFile = new File("base/some.jar");
298     Manifest manifest = manifestClasspath("file:/with/absolute.jar");
299     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
300         .containsExactly(fullpath("/with/absolute.jar"));
301   }
302 
testGetClassPathFromManifest_multiplePaths()303   public void testGetClassPathFromManifest_multiplePaths() throws IOException {
304     File jarFile = new File("base/some.jar");
305     Manifest manifest = manifestClasspath("file:/with/absolute.jar relative.jar  relative/dir");
306     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
307         .containsExactly(
308             fullpath("/with/absolute.jar"),
309             fullpath("base/relative.jar"),
310             fullpath("base/relative/dir"))
311         .inOrder();
312   }
313 
testGetClassPathFromManifest_leadingBlanks()314   public void testGetClassPathFromManifest_leadingBlanks() throws IOException {
315     File jarFile = new File("base/some.jar");
316     Manifest manifest = manifestClasspath(" relative.jar");
317     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
318         .containsExactly(fullpath("base/relative.jar"));
319   }
320 
testGetClassPathFromManifest_trailingBlanks()321   public void testGetClassPathFromManifest_trailingBlanks() throws IOException {
322     File jarFile = new File("base/some.jar");
323     Manifest manifest = manifestClasspath("relative.jar ");
324     assertThat(ClassPath.Scanner.getClassPathFromManifest(jarFile, manifest))
325         .containsExactly(fullpath("base/relative.jar"));
326   }
327 
testGetClassName()328   public void testGetClassName() {
329     assertEquals("abc.d.Abc", ClassPath.getClassName("abc/d/Abc.class"));
330   }
331 
testResourceInfo_of()332   public void testResourceInfo_of() {
333     assertEquals(ClassInfo.class, resourceInfo(ClassPathTest.class).getClass());
334     assertEquals(ClassInfo.class, resourceInfo(ClassPath.class).getClass());
335     assertEquals(ClassInfo.class, resourceInfo(Nested.class).getClass());
336   }
337 
testGetSimpleName()338   public void testGetSimpleName() {
339     ClassLoader classLoader = getClass().getClassLoader();
340     assertEquals("Foo", new ClassInfo(FILE, "Foo.class", classLoader).getSimpleName());
341     assertEquals("Foo", new ClassInfo(FILE, "a/b/Foo.class", classLoader).getSimpleName());
342     assertEquals("Foo", new ClassInfo(FILE, "a/b/Bar$Foo.class", classLoader).getSimpleName());
343     assertEquals("", new ClassInfo(FILE, "a/b/Bar$1.class", classLoader).getSimpleName());
344     assertEquals("Foo", new ClassInfo(FILE, "a/b/Bar$Foo.class", classLoader).getSimpleName());
345     assertEquals("", new ClassInfo(FILE, "a/b/Bar$1.class", classLoader).getSimpleName());
346     assertEquals("Local", new ClassInfo(FILE, "a/b/Bar$1Local.class", classLoader).getSimpleName());
347   }
348 
testGetPackageName()349   public void testGetPackageName() {
350     assertEquals(
351         "", new ClassInfo(FILE, "Foo.class", getClass().getClassLoader()).getPackageName());
352     assertEquals(
353         "a.b", new ClassInfo(FILE, "a/b/Foo.class", getClass().getClassLoader()).getPackageName());
354   }
355 
356   // Test that ResourceInfo.urls() returns identical content to ClassLoader.getResources()
357 
testGetClassPathUrls()358   public void testGetClassPathUrls() throws Exception {
359     String oldPathSeparator = PATH_SEPARATOR.value();
360     String oldClassPath = JAVA_CLASS_PATH.value();
361     System.setProperty(PATH_SEPARATOR.key(), ":");
362     System.setProperty(
363         JAVA_CLASS_PATH.key(),
364         Joiner.on(":")
365             .join(
366                 "relative/path/to/some.jar",
367                 "/absolute/path/to/some.jar",
368                 "relative/path/to/class/root",
369                 "/absolute/path/to/class/root"));
370     try {
371       ImmutableList<URL> urls = ClassPath.Scanner.parseJavaClassPath();
372 
373       assertThat(urls.get(0).getProtocol()).isEqualTo("file");
374       assertThat(urls.get(0).getAuthority()).isNull();
375       assertThat(urls.get(0).getPath()).endsWith("/relative/path/to/some.jar");
376 
377       assertThat(urls.get(1)).isEqualTo(new URL("file:///absolute/path/to/some.jar"));
378 
379       assertThat(urls.get(2).getProtocol()).isEqualTo("file");
380       assertThat(urls.get(2).getAuthority()).isNull();
381       assertThat(urls.get(2).getPath()).endsWith("/relative/path/to/class/root");
382 
383       assertThat(urls.get(3)).isEqualTo(new URL("file:///absolute/path/to/class/root"));
384 
385       assertThat(urls).hasSize(4);
386     } finally {
387       System.setProperty(PATH_SEPARATOR.key(), oldPathSeparator);
388       System.setProperty(JAVA_CLASS_PATH.key(), oldClassPath);
389     }
390   }
391 
contentEquals(URL left, URL right)392   private static boolean contentEquals(URL left, URL right) throws IOException {
393     return Resources.asByteSource(left).contentEquals(Resources.asByteSource(right));
394   }
395 
396   private static class Nested {}
397 
testNulls()398   public void testNulls() throws IOException {
399     new NullPointerTester().testAllPublicStaticMethods(ClassPath.class);
400     new NullPointerTester()
401         .testAllPublicInstanceMethods(ClassPath.from(getClass().getClassLoader()));
402   }
403 
testResourceScanner()404   public void testResourceScanner() throws IOException {
405     ResourceScanner scanner = new ResourceScanner();
406     scanner.scan(ClassLoader.getSystemClassLoader());
407     assertThat(scanner.resources).contains("com/google/common/reflect/ClassPathTest.class");
408   }
409 
testExistsThrowsSecurityException()410   public void testExistsThrowsSecurityException() throws IOException, URISyntaxException {
411     SecurityManager oldSecurityManager = System.getSecurityManager();
412     try {
413       doTestExistsThrowsSecurityException();
414     } finally {
415       System.setSecurityManager(oldSecurityManager);
416     }
417   }
418 
doTestExistsThrowsSecurityException()419   private void doTestExistsThrowsSecurityException() throws IOException, URISyntaxException {
420     File file = null;
421     // In Java 9, Logger may read the TZ database. Only disallow reading the class path URLs.
422     final PermissionCollection readClassPathFiles =
423         new FilePermission("", "read").newPermissionCollection();
424     for (URL url : ClassPath.Scanner.parseJavaClassPath()) {
425       if (url.getProtocol().equalsIgnoreCase("file")) {
426         file = new File(url.toURI());
427         readClassPathFiles.add(new FilePermission(file.getAbsolutePath(), "read"));
428       }
429     }
430     assertThat(file).isNotNull();
431     SecurityManager disallowFilesSecurityManager =
432         new SecurityManager() {
433           @Override
434           public void checkPermission(Permission p) {
435             if (readClassPathFiles.implies(p)) {
436               throw new SecurityException("Disallowed: " + p);
437             }
438           }
439         };
440     System.setSecurityManager(disallowFilesSecurityManager);
441     try {
442       file.exists();
443       fail("Did not get expected SecurityException");
444     } catch (SecurityException expected) {
445     }
446     ClassPath classPath = ClassPath.from(getClass().getClassLoader());
447     // ClassPath may contain resources from the boot class loader; just not from the class path.
448     for (ResourceInfo resource : classPath.getResources()) {
449       assertThat(resource.getResourceName()).doesNotContain("com/google/common/reflect/");
450     }
451   }
452 
findClass( Iterable<ClassPath.ClassInfo> classes, Class<?> cls)453   private static ClassPath.ClassInfo findClass(
454       Iterable<ClassPath.ClassInfo> classes, Class<?> cls) {
455     for (ClassPath.ClassInfo classInfo : classes) {
456       if (classInfo.getName().equals(cls.getName())) {
457         return classInfo;
458       }
459     }
460     throw new AssertionError("failed to find " + cls);
461   }
462 
resourceInfo(Class<?> cls)463   private static ResourceInfo resourceInfo(Class<?> cls) {
464     String resource = cls.getName().replace('.', '/') + ".class";
465     ClassLoader loader = cls.getClassLoader();
466     return ResourceInfo.of(FILE, resource, loader);
467   }
468 
classInfo(Class<?> cls)469   private static ClassInfo classInfo(Class<?> cls) {
470     return classInfo(cls, cls.getClassLoader());
471   }
472 
classInfo(Class<?> cls, ClassLoader classLoader)473   private static ClassInfo classInfo(Class<?> cls, ClassLoader classLoader) {
474     String resource = cls.getName().replace('.', '/') + ".class";
475     return new ClassInfo(FILE, resource, classLoader);
476   }
477 
manifestClasspath(String classpath)478   private static Manifest manifestClasspath(String classpath) throws IOException {
479     return manifest("Class-Path: " + classpath + "\n");
480   }
481 
writeSelfReferencingJarFile(File jarFile, String... entries)482   private static void writeSelfReferencingJarFile(File jarFile, String... entries)
483       throws IOException {
484     Manifest manifest = new Manifest();
485     // Without version, the manifest is silently ignored. Ugh!
486     manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
487     manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, jarFile.getName());
488 
489     Closer closer = Closer.create();
490     try {
491       FileOutputStream fileOut = closer.register(new FileOutputStream(jarFile));
492       JarOutputStream jarOut = closer.register(new JarOutputStream(fileOut));
493       for (String entry : entries) {
494         jarOut.putNextEntry(new ZipEntry(entry));
495         Resources.copy(ClassPathTest.class.getResource(entry), jarOut);
496         jarOut.closeEntry();
497       }
498     } catch (Throwable e) {
499       throw closer.rethrow(e);
500     } finally {
501       closer.close();
502     }
503   }
504 
manifest(String content)505   private static Manifest manifest(String content) throws IOException {
506     InputStream in = new ByteArrayInputStream(content.getBytes(US_ASCII));
507     Manifest manifest = new Manifest();
508     manifest.read(in);
509     return manifest;
510   }
511 
fullpath(String path)512   private static File fullpath(String path) {
513     return new File(new File(path).toURI());
514   }
515 
516   private static class ResourceScanner extends ClassPath.Scanner {
517     final Set<String> resources = new HashSet<>();
518 
519     @Override
scanResource(ResourceInfo resource)520     protected void scanResource(ResourceInfo resource) throws IOException {
521       resources.add(resource.getResourceName());
522     }
523   }
524 
makeJarUrlWithName(String name)525   private static URL makeJarUrlWithName(String name) throws IOException {
526     File fullPath = new File(Files.createTempDir(), name);
527     File jarFile = JarFileFinder.pickAnyJarFile();
528     Files.copy(jarFile, fullPath);
529     return fullPath.toURI().toURL();
530   }
531 
532   private static final class JarFileFinder extends ClassPath.Scanner {
533 
534     private File found;
535 
pickAnyJarFile()536     static File pickAnyJarFile() throws IOException {
537       JarFileFinder finder = new JarFileFinder();
538       try {
539         finder.scan(JarFileFinder.class.getClassLoader());
540         throw new IllegalStateException("No jar file found!");
541       } catch (StopScanningException expected) {
542         return finder.found;
543       }
544     }
545 
546     @Override
scanJarFile(ClassLoader classloader, JarFile file)547     void scanJarFile(ClassLoader classloader, JarFile file) throws IOException {
548       this.found = new File(file.getName());
549       throw new StopScanningException();
550     }
551 
552     @Override
scanResource(ResourceInfo resource)553     protected void scanResource(ResourceInfo resource) {}
554 
555     // Special exception just to terminate the scanning when we get any jar file to use.
556     private static final class StopScanningException extends RuntimeException {}
557   }
558 }
559