summaryrefslogtreecommitdiff
path: root/gnu/java/net/loader/JarURLLoader.java
blob: 69237908e8251b8ecff812ca846520e50224e90b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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<String> 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<String> 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;
  }
}