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