1 package gnu.java.net.loader;
3 import gnu.java.net.IndexListParser;
5 import java.io.IOException;
6 import java.net.JarURLConnection;
7 import java.net.MalformedURLException;
9 import java.net.URLClassLoader;
10 import java.net.URLStreamHandlerFactory;
11 import java.util.ArrayList;
12 import java.util.Iterator;
13 import java.util.LinkedHashMap;
16 import java.util.StringTokenizer;
17 import java.util.jar.Attributes;
18 import java.util.jar.JarEntry;
19 import java.util.jar.JarFile;
20 import java.util.jar.Manifest;
23 * A <code>JarURLLoader</code> is a type of <code>URLLoader</code>
24 * only loading from jar url.
26 public final class JarURLLoader extends URLLoader
28 // True if we've initialized -- i.e., tried open the jar file.
30 // The jar file for this url.
32 // Base jar: url for all resources loaded from jar.
34 // The "Class-Path" attribute of this Jar's manifest.
35 ArrayList<URLLoader> classPath;
36 // If not null, a mapping from INDEX.LIST for this jar only.
37 // This is a set of all prefixes and top-level files that
38 // ought to be available in this jar.
41 // This constructor is used internally. It purposely does not open
42 // the jar file -- it defers this until later. This allows us to
43 // implement INDEX.LIST lazy-loading semantics.
44 private JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache,
45 URLStreamHandlerFactory factory,
46 URL baseURL, URL absoluteUrl,
49 super(classloader, cache, factory, baseURL, absoluteUrl);
51 URL newBaseURL = null;
54 // Cache url prefix for all resources in this jar url.
55 String base = baseURL.toExternalForm() + "!/";
56 newBaseURL = new URL("jar", "", -1, base, cache.get(factory, "jar"));
58 catch (MalformedURLException ignore)
62 this.baseJarURL = newBaseURL;
63 this.classPath = null;
64 this.indexSet = indexSet;
67 // This constructor is used by URLClassLoader. It will immediately
68 // try to read the jar file, in case we've found an index or a class-path
69 // setting. FIXME: it would be nice to defer this as well, but URLClassLoader
71 public JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache,
72 URLStreamHandlerFactory factory,
73 URL baseURL, URL absoluteUrl)
75 this(classloader, cache, factory, baseURL, absoluteUrl, null);
79 private void initialize()
81 JarFile jarfile = null;
85 ((JarURLConnection) baseJarURL.openConnection()).getJarFile();
88 Attributes attributes;
89 String classPathString;
91 IndexListParser parser = new IndexListParser(jarfile, baseJarURL,
93 LinkedHashMap<URL, Set<String>> indexMap = parser.getHeaders();
96 // Note that the index also computes
97 // the resulting Class-Path -- there are jars out there
98 // where the index lists some required jars which do
99 // not appear in the Class-Path attribute in the manifest.
100 this.classPath = new ArrayList<URLLoader>();
101 Iterator<Map.Entry<URL, Set<String>>> it = indexMap.entrySet().iterator();
104 Map.Entry<URL, Set<String>> entry = it.next();
105 URL subURL = entry.getKey();
106 Set<String> prefixes = entry.getValue();
107 if (subURL.equals(baseURL))
108 this.indexSet = prefixes;
111 JarURLLoader subLoader = new JarURLLoader(classloader,
116 // Note that we don't care if the sub-loader itself has an
117 // index or a class-path -- only the top-level jar
118 // file gets this treatment; its index should cover
120 this.classPath.add(subLoader);
124 else if ((manifest = jarfile.getManifest()) != null
125 && (attributes = manifest.getMainAttributes()) != null
127 = attributes.getValue(Attributes.Name.CLASS_PATH))
130 this.classPath = new ArrayList<URLLoader>();
131 StringTokenizer st = new StringTokenizer(classPathString, " ");
132 while (st.hasMoreElements ())
134 String e = st.nextToken ();
137 URL subURL = new URL(baseURL, e);
138 // We've seen at least one jar file whose Class-Path
139 // attribute includes the original jar. If we process
140 // that normally we end up with infinite recursion.
141 if (subURL.equals(baseURL))
143 JarURLLoader subLoader = new JarURLLoader(classloader,
146 this.classPath.add(subLoader);
147 ArrayList<URLLoader> extra = subLoader.getClassPath();
149 this.classPath.addAll(extra);
151 catch (java.net.MalformedURLException xx)
158 catch (IOException ioe)
163 this.jarfile = jarfile;
164 this.initialized = true;
167 /** get resource with the name "name" in the jar url */
168 public Resource getResource(String name)
170 if (name.startsWith("/"))
171 name = name.substring(1);
172 if (indexSet != null)
175 String basename = name;
176 int offset = basename.lastIndexOf('/');
178 basename = basename.substring(0, offset);
179 if (! indexSet.contains(basename))
181 // FIXME: if the index claim to hold the resource, and jar file
182 // doesn't have it, we're supposed to throw an exception. However,
183 // in our model this is tricky to implement, as another URLLoader from
184 // the same top-level jar may have an overlapping index entry.
192 JarEntry je = jarfile.getJarEntry(name);
194 return new JarURLResource(this, name, je);
199 public Manifest getManifest()
203 return (jarfile == null) ? null : jarfile.getManifest();
205 catch (IOException ioe)
211 public ArrayList<URLLoader> getClassPath()