| 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 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 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(); |
| Iterator it = indexMap.entrySet().iterator(); |
| while (it.hasNext()) |
| { |
| Map.Entry entry = (Map.Entry) it.next(); |
| URL subURL = (URL) entry.getKey(); |
| Set prefixes = (Set) 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(); |
| 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 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 getClassPath() |
| { |
| return classPath; |
| } |
| } |