/* * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.misc; import java.io.BufferedReader; import java.io.FileReader; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /* * MetaIndex is intended to decrease startup time (in particular cold * start, when files are not yet in the disk cache) by providing a * quick reject mechanism for probes into jar files. The on-disk * representation of the meta-index is a flat text file with per-jar * entries indicating (generally speaking) prefixes of package names * contained in the jar. As an example, here is an edited excerpt of * the meta-index generated for jre/lib in the current build: * <PRE> % VERSION 1 # charsets.jar sun/ # jce.jar javax/ ! jsse.jar sun/ com/sun/net/ javax/ com/sun/security/ @ resources.jar com/sun/xml/ com/sun/rowset/ com/sun/org/ sun/ com/sun/imageio/ javax/ com/sun/java/swing/ META-INF/services/ com/sun/java/util/jar/pack/ com/sun/corba/ com/sun/jndi/ ! rt.jar org/w3c/ com/sun/imageio/ javax/ sunw/util/ java/ sun/ ... </PRE> * <p> A few notes about the design of the meta-index: * * <UL> * * <LI> It contains entries for multiple jar files. This is * intentional, to reduce the number of disk accesses that need to be * performed during startup. * * <LI> It is only intended to act as a fast reject mechanism to * prevent application and other classes from forcing all jar files on * the boot and extension class paths to be opened. It is not intended * as a precise index of the contents of the jar. * * <LI> It should be as small as possible to reduce the amount of time * required to parse it during startup. For example, adding on the * secondary package element to java/ and javax/ packages * ("javax/swing/", for example) causes the meta-index to grow * significantly. This is why substrings of the packages have been * chosen as the principal contents. * * <LI> It is versioned, and optional, to prevent strong dependencies * between the JVM and JDK. It is also potentially applicable to more * than just the boot and extension class paths. * * <LI> Precisely speaking, it plays different role in JVM and J2SE * side. On the JVM side, meta-index file is used to speed up locating the * class files only while on the J2SE side, meta-index file is used to speed * up the resources file & class file. * To help the JVM and J2SE code to better utilize the information in meta-index * file, we mark the jar file differently. Here is the current rule we use. * For jar file containing only class file, we put '!' before the jar file name; * for jar file containing only resources file, we put '@' before the jar file name; * for jar file containing both resources and class file, we put '#' before the * jar name. * Notice the fact that every jar file contains at least the manifest file, so when * we say "jar file containing only class file", we don't include that file. * * </UL> * * <p> To avoid changing the behavior of the current application * loader and other loaders, the current MetaIndex implementation in * the JDK requires that the directory containing the meta-index be * registered with the MetaIndex class before construction of the * associated URLClassPath. This prevents the need for automatic * searching for the meta-index in the URLClassPath code and potential * changes in behavior for non-core ClassLoaders. * * This class depends on make/tools/MetaIndex/BuildMetaIndex.java and * is used principally by sun.misc.URLClassPath. */ public class MetaIndex { // Maps jar file names in registered directories to meta-indices private static volatile Map<File, MetaIndex> jarMap; // List of contents of this meta-index private String[] contents; // Indicate whether the coresponding jar file is a pure class jar file or not private boolean isClassOnlyJar; //---------------------------------------------------------------------- // Registration of directories (which can cause parsing of the // meta-index file if it is present), and fetching of parsed // meta-indices // jarMap is not strictly thread-safe when the meta index mechanism // is extended for user-provided jar files in future. public static MetaIndex forJar(File jar) { return getJarMap().get(jar); } // 'synchronized' is added to protect the jarMap from being modified // by multiple threads. public static synchronized void registerDirectory(File dir) { // Note that this does not currently check to see whether the // directory has previously been registered, since the meta-index // in a particular directory creates multiple entries in the // jarMap. If this mechanism is extended beyond the boot and // extension class paths (for example, automatically searching for // meta-index files in directories containing jars which have been // explicitly opened) then this code should be generalized. // // This method must be called from a privileged context. File indexFile = new File(dir, "meta-index"); if (indexFile.exists()) { try { BufferedReader reader = new BufferedReader(new FileReader(indexFile)); String line = null; String curJarName = null; boolean isCurJarContainClassOnly = false; List<String> contents = new ArrayList<String>(); Map<File, MetaIndex> map = getJarMap(); /* Convert dir into canonical form. */ dir = dir.getCanonicalFile(); /* Note: The first line should contain the version of * the meta-index file. We have to match the right version * before trying to parse this file. */ line = reader.readLine(); if (line == null || !line.equals("% VERSION 2")) { reader.close(); return; } while ((line = reader.readLine()) != null) { switch (line.charAt(0)) { case '!': case '#': case '@': { // Store away current contents, if any if ((curJarName != null) && (contents.size() > 0)) { map.put(new File(dir, curJarName), new MetaIndex(contents, isCurJarContainClassOnly)); contents.clear(); } // Fetch new current jar file name curJarName = line.substring(2); if (line.charAt(0) == '!') { isCurJarContainClassOnly = true; } else if (isCurJarContainClassOnly) { isCurJarContainClassOnly = false; } break; } case '%': break; default: { contents.add(line); } } } // Store away current contents, if any if ((curJarName != null) && (contents.size() > 0)) { map.put(new File(dir, curJarName), new MetaIndex(contents, isCurJarContainClassOnly)); } reader.close(); } catch (IOException e) { // Silently fail for now (similar behavior to elsewhere in // extension and core loaders) } } } //---------------------------------------------------------------------- // Public APIs // public boolean mayContain(String entry) { // Ask non-class file from class only jar returns false // This check is important to avoid some class only jar // files such as rt.jar are opened for resource request. if (isClassOnlyJar && !entry.endsWith(".class")){ return false; } String[] conts = contents; for (int i = 0; i < conts.length; i++) { if (entry.startsWith(conts[i])) { return true; } } return false; } //---------------------------------------------------------------------- // Implementation only below this point // @IllegalArgumentException if entries is null. private MetaIndex(List<String> entries, boolean isClassOnlyJar) throws IllegalArgumentException { if (entries == null) { throw new IllegalArgumentException(); } contents = entries.toArray(new String[0]); this.isClassOnlyJar = isClassOnlyJar; } private static Map<File, MetaIndex> getJarMap() { if (jarMap == null) { synchronized (MetaIndex.class) { if (jarMap == null) { jarMap = new HashMap<File, MetaIndex>(); } } } assert jarMap != null; return jarMap; } }