package gnu.java.net.loader; import gnu.java.net.IndexListParser; import java.io.IOException; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; /** * A <code>JarURLLoader</code> is a type of <code>URLLoader</code> * only loading from jar url. */ public final class JarURLLoader extends URLLoader { // True if we've initialized -- i.e., tried open the jar file. boolean initialized; // The jar file for this url. JarFile jarfile; // Base jar: url for all resources loaded from jar. final URL baseJarURL; // The "Class-Path" attribute of this Jar's manifest. ArrayList<URLLoader> classPath; // If not null, a mapping from INDEX.LIST for this jar only. // This is a set of all prefixes and top-level files that // ought to be available in this jar. Set indexSet; // This constructor is used internally. It purposely does not open // the jar file -- it defers this until later. This allows us to // implement INDEX.LIST lazy-loading semantics. private JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache, URLStreamHandlerFactory factory, URL baseURL, URL absoluteUrl, Set indexSet) { super(classloader, cache, factory, baseURL, absoluteUrl); URL newBaseURL = null; try { // Cache url prefix for all resources in this jar url. String base = baseURL.toExternalForm() + "!/"; newBaseURL = new URL("jar", "", -1, base, cache.get(factory, "jar")); } catch (MalformedURLException ignore) { // Ignore. } this.baseJarURL = newBaseURL; this.classPath = null; this.indexSet = indexSet; } // This constructor is used by URLClassLoader. It will immediately // try to read the jar file, in case we've found an index or a class-path // setting. FIXME: it would be nice to defer this as well, but URLClassLoader // makes this hard. public JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache, URLStreamHandlerFactory factory, URL baseURL, URL absoluteUrl) { this(classloader, cache, factory, baseURL, absoluteUrl, null); initialize(); } private void initialize() { JarFile jarfile = null; try { jarfile = ((JarURLConnection) baseJarURL.openConnection()).getJarFile(); Manifest manifest; Attributes attributes; String classPathString; IndexListParser parser = new IndexListParser(jarfile, baseJarURL, baseURL); LinkedHashMap<URL, Set<String>> indexMap = parser.getHeaders(); if (indexMap != null) { // Note that the index also computes // the resulting Class-Path -- there are jars out there // where the index lists some required jars which do // not appear in the Class-Path attribute in the manifest. this.classPath = new ArrayList<URLLoader>(); Iterator<Map.Entry<URL, Set<String>>> it = indexMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<URL, Set<String>> entry = it.next(); URL subURL = entry.getKey(); Set<String> prefixes = entry.getValue(); if (subURL.equals(baseURL)) this.indexSet = prefixes; else { JarURLLoader subLoader = new JarURLLoader(classloader, cache, factory, subURL, subURL, prefixes); // Note that we don't care if the sub-loader itself has an // index or a class-path -- only the top-level jar // file gets this treatment; its index should cover // everything. this.classPath.add(subLoader); } } } else if ((manifest = jarfile.getManifest()) != null && (attributes = manifest.getMainAttributes()) != null && ((classPathString = attributes.getValue(Attributes.Name.CLASS_PATH)) != null)) { this.classPath = new ArrayList<URLLoader>(); StringTokenizer st = new StringTokenizer(classPathString, " "); while (st.hasMoreElements ()) { String e = st.nextToken (); try { URL subURL = new URL(baseURL, e); // We've seen at least one jar file whose Class-Path // attribute includes the original jar. If we process // that normally we end up with infinite recursion. if (subURL.equals(baseURL)) continue; JarURLLoader subLoader = new JarURLLoader(classloader, cache, factory, subURL, subURL); this.classPath.add(subLoader); ArrayList<URLLoader> extra = subLoader.getClassPath(); if (extra != null) this.classPath.addAll(extra); } catch (java.net.MalformedURLException xx) { // Give up } } } } catch (IOException ioe) { /* ignored */ } this.jarfile = jarfile; this.initialized = true; } /** get resource with the name "name" in the jar url */ public Resource getResource(String name) { if (name.startsWith("/")) name = name.substring(1); if (indexSet != null) { // Trust the index. String basename = name; int offset = basename.lastIndexOf('/'); if (offset != -1) basename = basename.substring(0, offset); if (! indexSet.contains(basename)) return null; // FIXME: if the index claim to hold the resource, and jar file // doesn't have it, we're supposed to throw an exception. However, // in our model this is tricky to implement, as another URLLoader from // the same top-level jar may have an overlapping index entry. } if (! initialized) initialize(); if (jarfile == null) return null; JarEntry je = jarfile.getJarEntry(name); if (je != null) return new JarURLResource(this, name, je); else return null; } public Manifest getManifest() { try { return (jarfile == null) ? null : jarfile.getManifest(); } catch (IOException ioe) { return null; } } public ArrayList<URLLoader> getClassPath() { return classPath; } }