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