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