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