1 /*
2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug     6901992
27  * @summary InvalidJarIndexException due to bug in sun.misc.JarIndex.merge()
28  *          Test URLClassLoader usage of the merge method when using indexes
29  * @author  Diego Belfer
30  */
31 import java.io.BufferedReader;
32 import java.io.File;
33 import java.io.FileNotFoundException;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.net.URL;
39 import java.net.URLClassLoader;
40 import java.util.jar.JarEntry;
41 import java.util.jar.JarOutputStream;
42 
43 public class JarIndexMergeForClassLoaderTest {
44     static final String slash = File.separator;
45     static final String testClassesDir = System.getProperty("test.classes", ".");
46     static final String jar;
47     static final boolean debug = true;
48     static final File tmpFolder = new File(testClassesDir);
49 
50     static {
51         String javaHome = System.getProperty("java.home");
52         if (javaHome.endsWith("jre")) {
53             int index = javaHome.lastIndexOf(slash);
54             if (index != -1)
55                 javaHome = javaHome.substring(0, index);
56         }
57 
58         jar = javaHome + slash + "bin" + slash + "jar";
59     }
60 
main(String[] args)61     public static void main(String[] args) throws Exception {
62         // Create the jars file
63         File jar1 = buildJar1();
64         File jar2 = buildJar2();
65         File jar3 = buildJar3();
66 
67         // Index jar files in two levels: jar1 -> jar2 -> jar3
68         createIndex(jar2.getName(), jar3.getName());
69         createIndex(jar1.getName(), jar2.getName());
70 
71         // Get root jar of the URLClassLoader
72         URL url = jar1.toURI().toURL();
73 
74         URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
75 
76         assertResource(classLoader, "com/jar1/resource.file", "jar1");
77         assertResource(classLoader, "com/test/resource1.file", "resource1");
78         assertResource(classLoader, "com/jar2/resource.file", "jar2");
79         assertResource(classLoader, "com/test/resource2.file", "resource2");
80         assertResource(classLoader, "com/test/resource3.file", "resource3");
81 
82         /*
83          * The following two asserts failed before the fix of the bug 6901992
84          */
85         // Check that an existing file is found using the merged index
86         assertResource(classLoader, "com/missing/jar3/resource.file", "jar3");
87         // Check that a non existent file in directory which does not contain
88         // any file is not found and it does not throw InvalidJarIndexException
89         assertResource(classLoader, "com/missing/nofile", null);
90     }
91 
buildJar3()92     private static File buildJar3() throws FileNotFoundException, IOException {
93         JarBuilder jar3Builder = new JarBuilder(tmpFolder, "jar3.jar");
94         jar3Builder.addResourceFile("com/test/resource3.file", "resource3");
95         jar3Builder.addResourceFile("com/missing/jar3/resource.file", "jar3");
96         return jar3Builder.build();
97     }
98 
buildJar2()99     private static File buildJar2() throws FileNotFoundException, IOException {
100         JarBuilder jar2Builder = new JarBuilder(tmpFolder, "jar2.jar");
101         jar2Builder.addResourceFile("com/jar2/resource.file", "jar2");
102         jar2Builder.addResourceFile("com/test/resource2.file", "resource2");
103         return jar2Builder.build();
104     }
105 
buildJar1()106     private static File buildJar1() throws FileNotFoundException, IOException {
107         JarBuilder jar1Builder = new JarBuilder(tmpFolder, "jar1.jar");
108         jar1Builder.addResourceFile("com/jar1/resource.file", "jar1");
109         jar1Builder.addResourceFile("com/test/resource1.file", "resource1");
110         return jar1Builder.build();
111     }
112 
113     /* create the index */
createIndex(String parentJar, String childJar)114     static void createIndex(String parentJar, String childJar) {
115         // ProcessBuilder is used so that the current directory can be set
116         // to the directory that directly contains the jars.
117         debug("Running jar to create the index for: " + parentJar + " and "
118                 + childJar);
119         ProcessBuilder pb = new ProcessBuilder(jar, "-i", parentJar, childJar);
120 
121         pb.directory(tmpFolder);
122         // pd.inheritIO();
123         try {
124             Process p = pb.start();
125             if (p.waitFor() != 0)
126                 throw new RuntimeException("jar indexing failed");
127 
128             if (debug && p != null) {
129                 debugStream(p.getInputStream());
130                 debugStream(p.getErrorStream());
131             }
132         } catch (InterruptedException | IOException x) {
133             throw new RuntimeException(x);
134         }
135     }
136 
debugStream(InputStream is)137     private static void debugStream(InputStream is) throws IOException {
138         try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
139             String line;
140             while ((line = reader.readLine()) != null) {
141                 debug(line);
142             }
143         }
144     }
145 
assertResource(URLClassLoader classLoader, String file, String expectedContent)146     private static void assertResource(URLClassLoader classLoader, String file,
147             String expectedContent) throws IOException {
148         InputStream fileStream = classLoader.getResourceAsStream(file);
149 
150         if (fileStream == null && expectedContent == null) {
151             return;
152         }
153         if (fileStream == null && expectedContent != null) {
154             throw new RuntimeException(
155                     buildMessage(file, expectedContent, null));
156         }
157         try {
158             String actualContent = readAsString(fileStream);
159 
160             if (fileStream != null && expectedContent == null) {
161                 throw new RuntimeException(buildMessage(file, null,
162                         actualContent));
163             }
164             if (!expectedContent.equals(actualContent)) {
165                 throw new RuntimeException(buildMessage(file, expectedContent,
166                         actualContent));
167             }
168         } finally {
169             fileStream.close();
170         }
171     }
172 
buildMessage(String file, String expectedContent, String actualContent)173     private static String buildMessage(String file, String expectedContent,
174             String actualContent) {
175         return "Expected: " + expectedContent + " for: " + file + " was: "
176                 + actualContent;
177     }
178 
readAsString(InputStream fileStream)179     private static String readAsString(InputStream fileStream)
180             throws IOException {
181         byte[] buffer = new byte[1024];
182         int count, len = 0;
183         while ((count = fileStream.read(buffer, len, buffer.length-len)) != -1)
184                 len += count;
185         return new String(buffer, 0, len, "ASCII");
186     }
187 
debug(Object message)188     static void debug(Object message) {
189         if (debug)
190             System.out.println(message);
191     }
192 
193     /*
194      * Helper class for building jar files
195      */
196     public static class JarBuilder {
197         private JarOutputStream os;
198         private File jarFile;
199 
JarBuilder(File tmpFolder, String jarName)200         public JarBuilder(File tmpFolder, String jarName)
201             throws FileNotFoundException, IOException
202         {
203             this.jarFile = new File(tmpFolder, jarName);
204             this.os = new JarOutputStream(new FileOutputStream(jarFile));
205         }
206 
addResourceFile(String pathFromRoot, String content)207         public void addResourceFile(String pathFromRoot, String content)
208             throws IOException
209         {
210             JarEntry entry = new JarEntry(pathFromRoot);
211             os.putNextEntry(entry);
212             os.write(content.getBytes("ASCII"));
213             os.closeEntry();
214         }
215 
build()216         public File build() throws IOException {
217             os.close();
218             return jarFile;
219         }
220     }
221 }
222 
223