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