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