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 }