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 }