1 /*
2  * Copyright (c) 1999, 2019, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.internal.util.jar;
27 
28 import static java.nio.charset.StandardCharsets.UTF_8;
29 
30 import java.io.*;
31 import java.util.*;
32 import java.util.jar.*;
33 import java.util.zip.*;
34 
35 import static sun.security.action.GetPropertyAction.privilegedGetProperty;
36 
37 /**
38  * This class is used to maintain mappings from packages, classes
39  * and resources to their enclosing JAR files. Mappings are kept
40  * at the package level except for class or resource files that
41  * are located at the root directory. URLClassLoader uses the mapping
42  * information to determine where to fetch an extension class or
43  * resource from.
44  *
45  * @author  Zhenghua Li
46  * @since   1.3
47  */
48 
49 public class JarIndex {
50 
51     /**
52      * The hash map that maintains mappings from
53      * package/classe/resource to jar file list(s)
54      */
55     private HashMap<String,LinkedList<String>> indexMap;
56 
57     /**
58      * The hash map that maintains mappings from
59      * jar file to package/class/resource lists
60      */
61     private HashMap<String,LinkedList<String>> jarMap;
62 
63     /*
64      * An ordered list of jar file names.
65      */
66     private String[] jarFiles;
67 
68     /**
69      * The index file name.
70      */
71     public static final String INDEX_NAME = "META-INF/INDEX.LIST";
72 
73     /**
74      * true if, and only if, sun.misc.JarIndex.metaInfFilenames is set to true.
75      * If true, the names of the files in META-INF, and its subdirectories, will
76      * be added to the index. Otherwise, just the directory names are added.
77      */
78     private static final boolean metaInfFilenames =
79         "true".equals(privilegedGetProperty("sun.misc.JarIndex.metaInfFilenames"));
80 
81     /**
82      * Constructs a new, empty jar index.
83      */
JarIndex()84     public JarIndex() {
85         indexMap = new HashMap<>();
86         jarMap = new HashMap<>();
87     }
88 
89     /**
90      * Constructs a new index from the specified input stream.
91      *
92      * @param is the input stream containing the index data
93      */
JarIndex(InputStream is)94     public JarIndex(InputStream is) throws IOException {
95         this();
96         read(is);
97     }
98 
99     /**
100      * Constructs a new index for the specified list of jar files.
101      *
102      * @param files the list of jar files to construct the index from.
103      */
JarIndex(String[] files)104     public JarIndex(String[] files) throws IOException {
105         this();
106         this.jarFiles = files;
107         parseJars(files);
108     }
109 
110     /**
111      * Returns the jar index, or <code>null</code> if none.
112      *
113      * @param jar the JAR file to get the index from.
114      * @exception IOException if an I/O error has occurred.
115      */
getJarIndex(JarFile jar)116     public static JarIndex getJarIndex(JarFile jar) throws IOException {
117         JarIndex index = null;
118         JarEntry e = jar.getJarEntry(INDEX_NAME);
119         // if found, then load the index
120         if (e != null) {
121             index = new JarIndex(jar.getInputStream(e));
122         }
123         return index;
124     }
125 
126     /**
127      * Returns the jar files that are defined in this index.
128      */
getJarFiles()129     public String[] getJarFiles() {
130         return jarFiles;
131     }
132 
133     /*
134      * Add the key, value pair to the hashmap, the value will
135      * be put in a linked list which is created if necessary.
136      */
addToList(String key, String value, HashMap<String,LinkedList<String>> t)137     private void addToList(String key, String value,
138                            HashMap<String,LinkedList<String>> t) {
139         LinkedList<String> list = t.get(key);
140         if (list == null) {
141             list = new LinkedList<>();
142             list.add(value);
143             t.put(key, list);
144         } else if (!list.contains(value)) {
145             list.add(value);
146         }
147     }
148 
149     /**
150      * Returns the list of jar files that are mapped to the file.
151      *
152      * @param fileName the key of the mapping
153      */
get(String fileName)154     public LinkedList<String> get(String fileName) {
155         LinkedList<String> jarFiles = null;
156         if ((jarFiles = indexMap.get(fileName)) == null) {
157             /* try the package name again */
158             int pos;
159             if((pos = fileName.lastIndexOf('/')) != -1) {
160                 jarFiles = indexMap.get(fileName.substring(0, pos));
161             }
162         }
163         return jarFiles;
164     }
165 
166     /**
167      * Add the mapping from the specified file to the specified
168      * jar file. If there were no mapping for the package of the
169      * specified file before, a new linked list will be created,
170      * the jar file is added to the list and a new mapping from
171      * the package to the jar file list is added to the hashmap.
172      * Otherwise, the jar file will be added to the end of the
173      * existing list.
174      *
175      * @param fileName the file name
176      * @param jarName the jar file that the file is mapped to
177      *
178      */
add(String fileName, String jarName)179     public void add(String fileName, String jarName) {
180         String packageName;
181         int pos;
182         if((pos = fileName.lastIndexOf('/')) != -1) {
183             packageName = fileName.substring(0, pos);
184         } else {
185             packageName = fileName;
186         }
187 
188         addMapping(packageName, jarName);
189     }
190 
191     /**
192      * Same as add(String,String) except that it doesn't strip off from the
193      * last index of '/'. It just adds the jarItem (filename or package)
194      * as it is received.
195      */
addMapping(String jarItem, String jarName)196     private void addMapping(String jarItem, String jarName) {
197         // add the mapping to indexMap
198         addToList(jarItem, jarName, indexMap);
199 
200         // add the mapping to jarMap
201         addToList(jarName, jarItem, jarMap);
202      }
203 
204     /**
205      * Go through all the jar files and construct the
206      * index table.
207      */
parseJars(String[] files)208     private void parseJars(String[] files) throws IOException {
209         if (files == null) {
210             return;
211         }
212 
213         String currentJar = null;
214 
215         for (int i = 0; i < files.length; i++) {
216             currentJar = files[i];
217             ZipFile zrf = new ZipFile(currentJar.replace
218                                       ('/', File.separatorChar));
219 
220             Enumeration<? extends ZipEntry> entries = zrf.entries();
221             while(entries.hasMoreElements()) {
222                 ZipEntry entry = entries.nextElement();
223                 String fileName = entry.getName();
224 
225                 // Skip the META-INF directory, the index, and manifest.
226                 // Any files in META-INF/ will be indexed explicitly
227                 if (fileName.equals("META-INF/") ||
228                     fileName.equals(INDEX_NAME) ||
229                     fileName.equals(JarFile.MANIFEST_NAME) ||
230                     fileName.startsWith("META-INF/versions/"))
231                     continue;
232 
233                 if (!metaInfFilenames || !fileName.startsWith("META-INF/")) {
234                     add(fileName, currentJar);
235                 } else if (!entry.isDirectory()) {
236                         // Add files under META-INF explicitly so that certain
237                         // services, like ServiceLoader, etc, can be located
238                         // with greater accuracy. Directories can be skipped
239                         // since each file will be added explicitly.
240                         addMapping(fileName, currentJar);
241                 }
242             }
243 
244             zrf.close();
245         }
246     }
247 
248     /**
249      * Writes the index to the specified OutputStream
250      *
251      * @param out the output stream
252      * @exception IOException if an I/O error has occurred
253      */
write(OutputStream out)254     public void write(OutputStream out) throws IOException {
255         BufferedWriter bw = new BufferedWriter
256             (new OutputStreamWriter(out, UTF_8));
257         bw.write("JarIndex-Version: 1.0\n\n");
258 
259         if (jarFiles != null) {
260             for (int i = 0; i < jarFiles.length; i++) {
261                 /* print out the jar file name */
262                 String jar = jarFiles[i];
263                 bw.write(jar + "\n");
264                 LinkedList<String> jarlist = jarMap.get(jar);
265                 if (jarlist != null) {
266                     Iterator<String> listitr = jarlist.iterator();
267                     while(listitr.hasNext()) {
268                         bw.write(listitr.next() + "\n");
269                     }
270                 }
271                 bw.write("\n");
272             }
273             bw.flush();
274         }
275     }
276 
277 
278     /**
279      * Reads the index from the specified InputStream.
280      *
281      * @param is the input stream
282      * @exception IOException if an I/O error has occurred
283      */
read(InputStream is)284     public void read(InputStream is) throws IOException {
285         BufferedReader br = new BufferedReader
286             (new InputStreamReader(is, UTF_8));
287         String line = null;
288         String currentJar = null;
289 
290         /* an ordered list of jar file names */
291         Vector<String> jars = new Vector<>();
292 
293         /* read until we see a .jar line */
294         while((line = br.readLine()) != null && !line.endsWith(".jar"));
295 
296         for(;line != null; line = br.readLine()) {
297             if (line.isEmpty())
298                 continue;
299 
300             if (line.endsWith(".jar")) {
301                 currentJar = line;
302                 jars.add(currentJar);
303             } else {
304                 String name = line;
305                 addMapping(name, currentJar);
306             }
307         }
308 
309         jarFiles = jars.toArray(new String[jars.size()]);
310     }
311 
312     /**
313      * Merges the current index into another index, taking into account
314      * the relative path of the current index.
315      *
316      * @param toIndex The destination index which the current index will
317      *                merge into.
318      * @param path    The relative path of the this index to the destination
319      *                index.
320      *
321      */
merge(JarIndex toIndex, String path)322     public void merge(JarIndex toIndex, String path) {
323         Iterator<Map.Entry<String,LinkedList<String>>> itr = indexMap.entrySet().iterator();
324         while(itr.hasNext()) {
325             Map.Entry<String,LinkedList<String>> e = itr.next();
326             String packageName = e.getKey();
327             LinkedList<String> from_list = e.getValue();
328             Iterator<String> listItr = from_list.iterator();
329             while(listItr.hasNext()) {
330                 String jarName = listItr.next();
331                 if (path != null) {
332                     jarName = path.concat(jarName);
333                 }
334                 toIndex.addMapping(packageName, jarName);
335             }
336         }
337     }
338 }
339