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
26package sun.misc;
27
28import java.io.*;
29import java.security.AccessController;
30import java.util.*;
31import java.util.jar.*;
32import java.util.zip.*;
33import 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
47public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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