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 
  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<String,LinkedList<String>> indexMap;
  52 
  53     /**
  54      * The hash map that maintains mappings from
  55      * jar file to package/class/resource lists
  56      */
  57     private HashMap<String,LinkedList<String>> 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      * @param jar the JAR file to get the index from.
 110      * @exception IOException if an I/O error has occurred.
 111      */
 112     public static JarIndex getJarIndex(JarFile jar) throws IOException {
 113         JarIndex index = null;
 114         JarEntry e = jar.getJarEntry(INDEX_NAME);
 115         // if found, then load the index
 116         if (e != null) {
 117             index = new JarIndex(jar.getInputStream(e));
 118         }
 119         return index;
 120     }
 121 
 122     /**
 123      * Returns the jar files that are defined in this index.
 124      */
 125     public String[] getJarFiles() {
 126         return jarFiles;
 127     }
 128 
 129     /*
 130      * Add the key, value pair to the hashmap, the value will
 131      * be put in a linked list which is created if necessary.
 132      */
 133     private void addToList(String key, String value,
 134                            HashMap<String,LinkedList<String>> t) {
 135         LinkedList<String> list = t.get(key);
 136         if (list == null) {
 137             list = new LinkedList<>();
 138             list.add(value);
 139             t.put(key, list);
 140         } else if (!list.contains(value)) {
 141             list.add(value);
 142         }
 143     }
 144 
 145     /**
 146      * Returns the list of jar files that are mapped to the file.
 147      *
 148      * @param fileName the key of the mapping
 149      */
 150     public LinkedList<String> get(String fileName) {
 151         LinkedList<String> jarFiles = null;
 152         if ((jarFiles = indexMap.get(fileName)) == null) {
 153             /* try the package name again */
 154             int pos;
 155             if((pos = fileName.lastIndexOf('/')) != -1) {
 156                 jarFiles = indexMap.get(fileName.substring(0, pos));
 157             }
 158         }
 159         return jarFiles;
 160     }
 161 
 162     /**
 163      * Add the mapping from the specified file to the specified
 164      * jar file. If there were no mapping for the package of the
 165      * specified file before, a new linked list will be created,
 166      * the jar file is added to the list and a new mapping from
 167      * the package to the jar file list is added to the hashmap.
 168      * Otherwise, the jar file will be added to the end of the
 169      * existing list.
 170      *
 171      * @param fileName the file name
 172      * @param jarName the jar file that the file is mapped to
 173      *
 174      */
 175     public void add(String fileName, String jarName) {
 176         String packageName;
 177         int pos;
 178         if((pos = fileName.lastIndexOf('/')) != -1) {
 179             packageName = fileName.substring(0, pos);
 180         } else {
 181             packageName = fileName;
 182         }
 183 
 184         addMapping(packageName, jarName);
 185     }
 186 
 187     /**
 188      * Same as add(String,String) except that it doesn't strip off from the
 189      * last index of '/'. It just adds the jarItem (filename or package)
 190      * as it is received.
 191      */
 192     private void addMapping(String jarItem, String jarName) {
 193         // add the mapping to indexMap
 194         addToList(jarItem, jarName, indexMap);
 195 
 196         // add the mapping to jarMap
 197         addToList(jarName, jarItem, jarMap);
 198      }
 199 
 200     /**
 201      * Go through all the jar files and construct the
 202      * index table.
 203      */
 204     private void parseJars(String[] files) throws IOException {
 205         if (files == null) {
 206             return;
 207         }
 208 
 209         String currentJar = null;
 210 
 211         for (int i = 0; i < files.length; i++) {
 212             currentJar = files[i];
 213             ZipFile zrf = new ZipFile(currentJar.replace
 214                                       ('/', File.separatorChar));
 215 
 216             Enumeration<? extends ZipEntry> entries = zrf.entries();
 217             while(entries.hasMoreElements()) {
 218                 ZipEntry entry = entries.nextElement();
 219                 String fileName = entry.getName();
 220 
 221                 // Skip the META-INF directory, the index, and manifest.
 222                 // Any files in META-INF/ will be indexed explicitly
 223                 if (fileName.equals("META-INF/") ||
 224                     fileName.equals(INDEX_NAME) ||
 225                     fileName.equals(JarFile.MANIFEST_NAME))
 226                     continue;
 227 
 228                 if (!metaInfFilenames || !fileName.startsWith("META-INF/")) {
 229                     add(fileName, currentJar);
 230                 } else if (!entry.isDirectory()) {
 231                         // Add files under META-INF explicitly so that certain
 232                         // services, like ServiceLoader, etc, can be located
 233                         // with greater accuracy. Directories can be skipped
 234                         // since each file will be added explicitly.
 235                         addMapping(fileName, currentJar);
 236                 }
 237             }
 238 
 239             zrf.close();
 240         }
 241     }
 242 
 243     /**
 244      * Writes the index to the specified OutputStream
 245      *
 246      * @param out the output stream
 247      * @exception IOException if an I/O error has occurred
 248      */
 249     public void write(OutputStream out) throws IOException {
 250         BufferedWriter bw = new BufferedWriter
 251             (new OutputStreamWriter(out, "UTF8"));
 252         bw.write("JarIndex-Version: 1.0\n\n");
 253 
 254         if (jarFiles != null) {
 255             for (int i = 0; i < jarFiles.length; i++) {
 256                 /* print out the jar file name */
 257                 String jar = jarFiles[i];
 258                 bw.write(jar + "\n");
 259                 LinkedList<String> jarlist = jarMap.get(jar);
 260                 if (jarlist != null) {
 261                     Iterator<String> listitr = jarlist.iterator();
 262                     while(listitr.hasNext()) {
 263                         bw.write(listitr.next() + "\n");
 264                     }
 265                 }
 266                 bw.write("\n");
 267             }
 268             bw.flush();
 269         }
 270     }
 271 
 272 
 273     /**
 274      * Reads the index from the specified InputStream.
 275      *
 276      * @param is the input stream
 277      * @exception IOException if an I/O error has occurred
 278      */
 279     public void read(InputStream is) throws IOException {
 280         BufferedReader br = new BufferedReader
 281             (new InputStreamReader(is, "UTF8"));
 282         String line = null;
 283         String currentJar = null;
 284 
 285         /* an ordered list of jar file names */
 286         Vector<String> jars = new Vector<>();
 287 
 288         /* read until we see a .jar line */
 289         while((line = br.readLine()) != null && !line.endsWith(".jar"));
 290 
 291         for(;line != null; line = br.readLine()) {
 292             if (line.length() == 0)
 293                 continue;
 294 
 295             if (line.endsWith(".jar")) {
 296                 currentJar = line;
 297                 jars.add(currentJar);
 298             } else {
 299                 String name = line;
 300                 addMapping(name, currentJar);
 301             }
 302         }
 303 
 304         jarFiles = jars.toArray(new String[jars.size()]);
 305     }
 306 
 307     /**
 308      * Merges the current index into another index, taking into account
 309      * the relative path of the current index.
 310      *
 311      * @param toIndex The destination index which the current index will
 312      *                merge into.
 313      * @param path    The relative path of the this index to the destination
 314      *                index.
 315      *
 316      */
 317     public void merge(JarIndex toIndex, String path) {
 318         Iterator<Map.Entry<String,LinkedList<String>>> itr = indexMap.entrySet().iterator();
 319         while(itr.hasNext()) {
 320             Map.Entry<String,LinkedList<String>> e = itr.next();
 321             String packageName = e.getKey();
 322             LinkedList<String> from_list = e.getValue();
 323             Iterator<String> listItr = from_list.iterator();
 324             while(listItr.hasNext()) {
 325                 String jarName = listItr.next();
 326                 if (path != null) {
 327                     jarName = path.concat(jarName);
 328                 }
 329                 toIndex.addMapping(packageName, jarName);
 330             }
 331         }
 332     }
 333 }