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