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