Home | History | Annotate | Download | only in misc
      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      */
     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