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