| 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; | |
| } | |
| } |