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