1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.classloader.internal

File ResourceLoader.java

 

Coverage histogram

../../../../img/srcFileCovDistChart6.png
72% of files have more coverage

Code metrics

100
275
40
4
859
582
111
0.4
6.88
10
2.78

Classes

Class Line # Actions
ResourceLoader 87 139 0% 56 89
0.557213955.7%
ResourceLoader.JarInfo 385 97 0% 35 60
0.5973154359.7%
ResourceLoader.JarResourceHandle 638 12 0% 10 5
0.7727272577.3%
ResourceLoader.ResourceEnumeration 789 27 0% 10 23
0.465116346.5%
 

Contributing tests

This file is covered by 83 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.classloader.internal;
21   
22    import java.io.BufferedReader;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.io.InputStreamReader;
26    import java.net.HttpURLConnection;
27    import java.net.MalformedURLException;
28    import java.net.URI;
29    import java.net.URISyntaxException;
30    import java.net.URL;
31    import java.net.URLConnection;
32    import java.net.URLStreamHandler;
33    import java.security.Permission;
34    import java.security.cert.Certificate;
35    import java.util.ArrayList;
36    import java.util.Arrays;
37    import java.util.Collections;
38    import java.util.Enumeration;
39    import java.util.HashMap;
40    import java.util.HashSet;
41    import java.util.LinkedHashMap;
42    import java.util.List;
43    import java.util.Map;
44    import java.util.NoSuchElementException;
45    import java.util.Set;
46    import java.util.StringTokenizer;
47    import java.util.jar.Attributes;
48    import java.util.jar.JarEntry;
49    import java.util.jar.JarFile;
50    import java.util.jar.Manifest;
51   
52    import edu.emory.mathcs.util.classloader.ResourceHandle;
53    import edu.emory.mathcs.util.classloader.ResourceUtils;
54   
55    /**
56    * This class aids in accessing remote resources referred by URLs. The URLs are resolved into {@link ResourceHandle
57    * resource handles} which can be used to access the resources directly and uniformly, regardless of the URL type. The
58    * resource loader is particularly useful when dealing with resources fetched from JAR files. It maintains the cache of
59    * opened JAR files (so that so that subsequent requests for resources coming from the same base Jar file can be handled
60    * efficiently). It fully supports JAR class-path (references from a JAR file to other JAR files) and JAR index (JAR
61    * containing information about content of other JARs). The caching policy of downloaded JAR files can be customized via
62    * the constructor parameter <code>jarHandler</code>; the default policy is to use separate cache per each
63    * ResourceLoader instance.
64    * <p>
65    * This class is particularly useful when implementing custom class loaders. It provides bottom-level resource fetching
66    * functionality. By using one of the loader methods which accepts an array of URLs, it is straightforward to implement
67    * class-path searching similar to that of {@link java.net.URLClassLoader}, with JAR dependencies (Class-Path) properly
68    * resolved and with JAR indexes properly handled.
69    * <p>
70    * This class provides two set of methods: <i>get</i> methods that return {@link ResourceHandle}s (or their
71    * enumerations) and <i>find</i> methods that return URLs (or their enumerations). If the resource is not found, null
72    * (or empty enumeration) is returned. Resource handles represent a connection to the resource and they should be closed
73    * when done processing, just like input streams. In contrast, find methods return URLs that can be used to open
74    * multiple connections to the resource. In typical class loader applications, when a single retrieval is sufficient, it
75    * is preferable to use <i>get</i> methods since they pose slightly smaller communication overhead.
76    * <p>
77    * Originally written by Dawid Kurzyniec and released to the public domain, as explained at
78    * http://creativecommons.org/licenses/publicdomain
79    * </p>
80    * <p>
81    * Source: http://dcl.mathcs.emory.edu/php/loadPage.php?content=util/features.html#classloading
82    * </p>
83    *
84    * @version $Id: 4860a98008dbab06404613185155f048c832302b $
85    * @since 2.0.1
86    */
 
87    public class ResourceLoader
88    {
89    private static final String JAR_INDEX_ENTRY_NAME = "META-INF/INDEX.LIST";
90   
91    private URLStreamHandler jarHandler;
92   
93    private Map<String, JarInfo> url2jarInfo = new HashMap<>();
94   
95    /**
96    * Constructs new ResourceLoader with specified JAR file handler which can implement custom JAR caching policy.
97    *
98    * @param jarHandler JAR file handler
99    */
 
100  114642 toggle public ResourceLoader(URLStreamHandler jarHandler)
101    {
102  114830 this.jarHandler = jarHandler;
103    }
104   
105    /**
106    * Gets resource with given name at the given source URL. If the URL points to a directory, the name is the file
107    * path relative to this directory. If the URL points to a JAR file, the name identifies an entry in that JAR file.
108    * If the URL points to a JAR file, the resource is not found in that JAR file, and the JAR file has Class-Path
109    * attribute, the JAR files identified in the Class-Path are also searched for the resource.
110    *
111    * @param source the source URL
112    * @param name the resource name
113    * @return handle representing the resource, or null if not found
114    */
 
115  0 toggle public ResourceHandle getResource(URL source, String name)
116    {
117  0 return getResource(source, name, new HashSet<>(), null);
118    }
119   
120    /**
121    * Gets resource with given name at the given search path. The path is searched iteratively, one URL at a time. If
122    * the URL points to a directory, the name is the file path relative to this directory. If the URL points to the JAR
123    * file, the name identifies an entry in that JAR file. If the URL points to the JAR file, the resource is not found
124    * in that JAR file, and the JAR file has Class-Path attribute, the JAR files identified in the Class-Path are also
125    * searched for the resource.
126    *
127    * @param sources the source URL path
128    * @param name the resource name
129    * @return handle representing the resource, or null if not found
130    */
 
131  97031 toggle public ResourceHandle getResource(URL[] sources, String name)
132    {
133  97031 Set<URL> visited = new HashSet<>();
134  97031 for (URL source : sources) {
135  227414 ResourceHandle h = getResource(source, name, visited, null);
136  227414 if (h != null) {
137  596 return h;
138    }
139    }
140  96435 return null;
141    }
142   
143    /**
144    * Gets all resources with given name at the given source URL. If the URL points to a directory, the name is the
145    * file path relative to this directory. If the URL points to a JAR file, the name identifies an entry in that JAR
146    * file. If the URL points to a JAR file, the resource is not found in that JAR file, and the JAR file has
147    * Class-Path attribute, the JAR files identified in the Class-Path are also searched for the resource.
148    * <p>
149    * The search is lazy, that is, "find next resource" operation is triggered by calling
150    * {@link Enumeration#hasMoreElements}.
151    *
152    * @param source the source URL
153    * @param name the resource name
154    * @return enumeration of resource handles representing the resources
155    */
 
156  0 toggle public Enumeration<ResourceHandle> getResources(URL source, String name)
157    {
158  0 return new ResourceEnumeration<>(new URL[] { source }, name, false);
159    }
160   
161    /**
162    * Gets all resources with given name at the given search path. If the URL points to a directory, the name is the
163    * file path relative to this directory. If the URL points to a JAR file, the name identifies an entry in that JAR
164    * file. If the URL points to a JAR file, the resource is not found in that JAR file, and the JAR file has
165    * Class-Path attribute, the JAR files identified in the Class-Path are also searched for the resource.
166    * <p>
167    * The search is lazy, that is, "find next resource" operation is triggered by calling
168    * {@link Enumeration#hasMoreElements}.
169    *
170    * @param sources the source URL path
171    * @param name the resource name
172    * @return enumeration of resource handles representing the resources
173    */
 
174  0 toggle public Enumeration<ResourceHandle> getResources(URL[] sources, String name)
175    {
176  0 return new ResourceEnumeration<>(sources.clone(), name, false);
177    }
178   
 
179  227414 toggle private ResourceHandle getResource(final URL source, String name, Set<URL> visitedJars, Set<URL> skip)
180    {
181  227414 name = ResourceUtils.canonizePath(name);
182  227414 if (isDir(source)) {
183    // plain resource
184  0 final URL url;
185  0 try {
186    // escape spaces etc. to make sure url is well-formed
187  0 URI relUri = new URI(null, null, null, -1, name, null, null);
188  0 url = new URL(source, relUri.getRawPath());
189    } catch (URISyntaxException e) {
190  0 throw new IllegalArgumentException("Illegal resource name: " + name);
191    } catch (MalformedURLException e) {
192  0 return null;
193    }
194   
195  0 if (skip != null && skip.contains(url)) {
196  0 return null;
197    }
198  0 final URLConnection conn;
199  0 try {
200  0 conn = url.openConnection();
201  0 conn.getInputStream();
202    } catch (IOException e) {
203  0 return null;
204    }
205  0 final String finalName = name;
206  0 return new ResourceHandle()
207    {
 
208  0 toggle @Override
209    public String getName()
210    {
211  0 return finalName;
212    }
213   
 
214  0 toggle @Override
215    public URL getURL()
216    {
217  0 return url;
218    }
219   
 
220  0 toggle @Override
221    public URL getCodeSourceURL()
222    {
223  0 return source;
224    }
225   
 
226  0 toggle @Override
227    public InputStream getInputStream() throws IOException
228    {
229  0 return conn.getInputStream();
230    }
231   
 
232  0 toggle @Override
233    public int getContentLength()
234    {
235  0 return conn.getContentLength();
236    }
237   
 
238  0 toggle @Override
239    public void close()
240    {
241  0 try {
242  0 getInputStream().close();
243    } catch (IOException e) {
244    }
245    }
246    };
247    } else {
248    // we deal with a JAR file here
249  227414 try {
250  227414 return getJarInfo(source).getResource(name, visitedJars, skip);
251    } catch (MalformedURLException e) {
252  0 return null;
253    }
254    }
255    }
256   
257    /**
258    * Fined resource with given name at the given source URL. If the URL points to a directory, the name is the file
259    * path relative to this directory. If the URL points to a JAR file, the name identifies an entry in that JAR file.
260    * If the URL points to a JAR file, the resource is not found in that JAR file, and the JAR file has Class-Path
261    * attribute, the JAR files identified in the Class-Path are also searched for the resource.
262    *
263    * @param source the source URL
264    * @param name the resource name
265    * @return URL of the resource, or null if not found
266    */
 
267  0 toggle public URL findResource(URL source, String name)
268    {
269  0 return findResource(source, name, new HashSet<>(), null);
270    }
271   
272    /**
273    * Finds resource with given name at the given search path. The path is searched iteratively, one URL at a time. If
274    * the URL points to a directory, the name is the file path relative to this directory. If the URL points to the JAR
275    * file, the name identifies an entry in that JAR file. If the URL points to the JAR file, the resource is not found
276    * in that JAR file, and the JAR file has Class-Path attribute, the JAR files identified in the Class-Path are also
277    * searched for the resource.
278    *
279    * @param sources the source URL path
280    * @param name the resource name
281    * @return URL of the resource, or null if not found
282    */
 
283  133491 toggle public URL findResource(URL[] sources, String name)
284    {
285  133492 Set<URL> visited = new HashSet<>();
286  133491 for (URL source : sources) {
287  412136 URL url = findResource(source, name, visited, null);
288  412138 if (url != null) {
289  37 return url;
290    }
291    }
292  133455 return null;
293    }
294   
295    /**
296    * Finds all resources with given name at the given source URL. If the URL points to a directory, the name is the
297    * file path relative to this directory. If the URL points to a JAR file, the name identifies an entry in that JAR
298    * file. If the URL points to a JAR file, the resource is not found in that JAR file, and the JAR file has
299    * Class-Path attribute, the JAR files identified in the Class-Path are also searched for the resource.
300    * <p>
301    * The search is lazy, that is, "find next resource" operation is triggered by calling
302    * {@link Enumeration#hasMoreElements}.
303    *
304    * @param source the source URL
305    * @param name the resource name
306    * @return enumeration of URLs of the resources
307    */
 
308  0 toggle public Enumeration<URL> findResources(URL source, String name)
309    {
310  0 return new ResourceEnumeration<>(new URL[] { source }, name, true);
311    }
312   
313    /**
314    * Finds all resources with given name at the given search path. If the URL points to a directory, the name is the
315    * file path relative to this directory. If the URL points to a JAR file, the name identifies an entry in that JAR
316    * file. If the URL points to a JAR file, the resource is not found in that JAR file, and the JAR file has
317    * Class-Path attribute, the JAR files identified in the Class-Path are also searched for the resource.
318    * <p>
319    * The search is lazy, that is, "find next resource" operation is triggered by calling
320    * {@link Enumeration#hasMoreElements}.
321    *
322    * @param sources the source URL path
323    * @param name the resource name
324    * @return enumeration of URLs of the resources
325    */
 
326  96634 toggle public Enumeration<URL> findResources(URL[] sources, String name)
327    {
328  96635 return new ResourceEnumeration<>(sources.clone(), name, true);
329    }
330   
 
331  424503 toggle private URL findResource(final URL source, String name, Set<URL> visitedJars, Set<URL> skip)
332    {
333  424504 URL url;
334  424504 name = ResourceUtils.canonizePath(name);
335  424501 if (isDir(source)) {
336    // plain resource
337  0 try {
338  0 url = new URL(source, name);
339    } catch (MalformedURLException e) {
340  0 return null;
341    }
342  0 if (skip != null && skip.contains(url)) {
343  0 return null;
344    }
345  0 final URLConnection conn;
346  0 try {
347  0 conn = url.openConnection();
348  0 if (conn instanceof HttpURLConnection) {
349  0 HttpURLConnection httpConn = (HttpURLConnection) conn;
350  0 httpConn.setRequestMethod("HEAD");
351  0 if (httpConn.getResponseCode() >= 400) {
352  0 return null;
353    }
354    } else {
355  0 conn.getInputStream().close();
356    }
357    } catch (IOException e) {
358  0 return null;
359    }
360  0 return url;
361    } else {
362    // we deal with a JAR file here
363  424501 try {
364  424501 ResourceHandle rh = getJarInfo(source).getResource(name, visitedJars, skip);
365  424503 return (rh != null) ? rh.getURL() : null;
366    } catch (MalformedURLException e) {
367  0 return null;
368    }
369    }
370    }
371   
372    /**
373    * Test whether given URL points to a directory. URL is deemed to point to a directory if has non-null "file"
374    * component ending with "/".
375    *
376    * @param url the URL to test
377    * @return true if the URL points to a directory, false otherwise
378    */
 
379  651917 toggle protected static boolean isDir(URL url)
380    {
381  651917 String file = url.getFile();
382  651918 return file != null && file.endsWith("/");
383    }
384   
 
385    private static class JarInfo
386    {
387    private ResourceLoader loader;
388   
389    private URL source; // "real" jar file path
390   
391    private URL base; // "jar:{base}!/"
392   
393    private JarFile jar;
394   
395    private boolean resolved;
396   
397    private Permission perm;
398   
399    private URL[] classPath;
400   
401    private String[] index;
402   
403    private Map<String, URL[]> package2url;
404   
 
405  269 toggle JarInfo(ResourceLoader loader, URL source) throws MalformedURLException
406    {
407  269 this.loader = loader;
408  269 this.source = source;
409  269 this.base = new URL("jar", "", -1, source + "!/", loader.jarHandler);
410    }
411   
 
412  651918 toggle ResourceHandle getResource(String name, Set<URL> visited, Set<URL> skip)
413    {
414  651918 visited.add(this.source);
415  651918 URL url;
416  651918 try {
417    // escape spaces etc. to make sure url is well-formed
418  651918 URI relUri = new URI(null, null, null, -1, name, null, null);
419  651917 url = new URL(this.base, relUri.getRawPath());
420    } catch (URISyntaxException e) {
421  0 throw new IllegalArgumentException("Illegal resource name: " + name);
422    } catch (MalformedURLException e) {
423  0 return null;
424    }
425  651918 try {
426  651917 JarFile jfile = getJarFileIfPossiblyContains(name);
427  651917 if (jfile != null) {
428  651917 JarEntry jentry = this.jar.getJarEntry(name);
429  651917 if (jentry != null && (skip == null || !skip.contains(url))) {
430  633 return new JarResourceHandle(jfile, jentry, url, this.source);
431    }
432    }
433    } catch (IOException e) {
434  1 return null;
435    }
436   
437    // not in here, but check also the dependencies
438  651284 URL[] dependencies;
439  651284 synchronized (this) {
440  651284 if (this.package2url != null) {
441  3809 int idx = name.lastIndexOf("/");
442  3809 String prefix = (idx > 0) ? name.substring(0, idx) : name;
443  3809 dependencies = this.package2url.get(prefix);
444    } else {
445    // classpath might be null only if it was a dependency of
446    // an indexed JAR with out-of-date index (the index brought
447    // us here but resource was not found in the JAR). But this
448    // (out-of-sync index) should be captured by
449    // getJarFileIfPossiblyContains.
450  527 assert this.classPath != null;
451  647475 dependencies = this.classPath;
452    }
453    }
454   
455  651284 if (dependencies == null) {
456  3809 return null;
457    }
458   
459  647475 for (URL cpUrl : dependencies) {
460  0 if (visited.contains(cpUrl)) {
461  0 continue;
462    }
463  0 JarInfo depJInfo;
464  0 try {
465  0 depJInfo = this.loader.getJarInfo(cpUrl);
466  0 ResourceHandle rh = depJInfo.getResource(name, visited, skip);
467  0 if (rh != null) {
468  0 return rh;
469    }
470    } catch (MalformedURLException e) {
471    // continue with other URLs
472    }
473    }
474   
475    // not found
476  647475 return null;
477    }
478   
 
479  11 toggle synchronized void setIndex(List<String> newIndex)
480    {
481  11 if (this.jar != null) {
482    // already loaded; no need for index
483  0 return;
484    }
485  11 if (this.index != null) {
486    // verification - previously declared content must remain there
487  0 Set<String> violating = new HashSet<>(Arrays.asList(this.index));
488  0 violating.removeAll(newIndex);
489  0 if (!violating.isEmpty()) {
490  0 throw new RuntimeException("Invalid JAR index: "
491    + "the following entries were previously declared, but "
492    + "they are not present in the new index: " + violating.toString());
493    }
494    }
495  11 this.index = newIndex.toArray(new String[newIndex.size()]);
496  11 Arrays.sort(this.index);
497    }
498   
 
499  651915 toggle public JarFile getJarFileIfPossiblyContains(String name) throws IOException
500    {
501  651918 Map<URL, List<String>> indexes;
502  651918 synchronized (this) {
503  651918 if (this.jar != null) {
504    // make sure we would be allowed to load it ourselves
505  651660 SecurityManager security = System.getSecurityManager();
506  651660 if (security != null) {
507  0 security.checkPermission(this.perm);
508    }
509   
510    // other thread may still be updating indexes of dependent
511    // JAR files
512  651660 try {
513  651660 while (!this.resolved) {
514  0 wait();
515    }
516    } catch (InterruptedException e) {
517  0 throw new IOException("Interrupted");
518    }
519  651660 return this.jar;
520    }
521   
522  258 if (this.index != null) {
523    // we may be able to respond negatively w/o loading the JAR
524  0 int pos = name.lastIndexOf('/');
525  0 if (pos > 0) {
526  0 name = name.substring(0, pos);
527    }
528  0 if (Arrays.binarySearch(this.index, name) < 0) {
529  0 return null;
530    }
531    }
532   
533    // load the JAR
534  258 URLConnection connection = this.base.openConnection();
535  258 this.perm = connection.getPermission();
536   
537  258 JarFile jar;
538   
539  258 if (connection instanceof org.xwiki.classloader.internal.JarURLConnection) {
540  6 jar = ((org.xwiki.classloader.internal.JarURLConnection) connection).getJarFile();
541    } else {
542  252 jar = ((java.net.JarURLConnection) connection).getJarFile();
543    }
544   
545    // conservatively check if index is accurate, that is, does not
546    // contain entries which are not in the JAR file
547  257 if (this.index != null) {
548  0 Set<String> indices = new HashSet<>(Arrays.asList(this.index));
549  0 Enumeration<JarEntry> entries = jar.entries();
550  0 while (entries.hasMoreElements()) {
551  0 JarEntry entry = entries.nextElement();
552  0 String indexEntry = entry.getName();
553    // for non-top, find the package name
554  0 int pos = indexEntry.lastIndexOf('/');
555  0 if (pos > 0) {
556  0 indexEntry = indexEntry.substring(0, pos);
557    }
558  0 indices.remove(indexEntry);
559    }
560  0 if (!indices.isEmpty()) {
561  0 throw new RuntimeException("Invalid JAR index: the following entries not found in JAR: "
562    + indices);
563    }
564    }
565  257 this.jar = jar;
566   
567  257 this.classPath = parseClassPath(jar, this.source);
568   
569  257 indexes = parseJarIndex(this.source, jar);
570  257 indexes.remove(this.source.toExternalForm());
571   
572  257 if (!indexes.isEmpty()) {
573  11 this.package2url = package2url(indexes);
574    }
575    }
576    // just loaded the JAR - need to resolve the index
577  257 try {
578  257 for (Map.Entry<URL, List<String>> entry : indexes.entrySet()) {
579  11 URL url = entry.getKey();
580  11 if (url.toExternalForm().equals(this.source.toExternalForm())) {
581  0 continue;
582    }
583  11 List<String> index = entry.getValue();
584  11 this.loader.getJarInfo(url).setIndex(index);
585    }
586    } finally {
587  257 synchronized (this) {
588  257 this.resolved = true;
589  257 notifyAll();
590    }
591    }
592  257 return this.jar;
593    }
594    }
595   
 
596  11 toggle private static Map<String, URL[]> package2url(Map<URL, List<String>> indexes)
597    {
598  11 Map<String, List<URL>> prefix2url = new HashMap<>();
599  11 for (Map.Entry<URL, List<String>> entry : indexes.entrySet()) {
600  11 URL url = entry.getKey();
601  11 for (String prefix : entry.getValue()) {
602  66 List<URL> prefixList = prefix2url.get(prefix);
603  66 if (prefixList == null) {
604  66 prefixList = new ArrayList<>();
605  66 prefix2url.put(prefix, prefixList);
606    }
607  66 prefixList.add(url);
608    }
609    }
610   
611  11 Map<String, URL[]> result = new HashMap<>(prefix2url.size());
612   
613    // replace lists with arrays
614  11 for (Map.Entry<String, List<URL>> entry : prefix2url.entrySet()) {
615  66 List<URL> list = entry.getValue();
616  66 result.put(entry.getKey(), list.toArray(new URL[list.size()]));
617    }
618  11 return result;
619    }
620   
 
621  651928 toggle private JarInfo getJarInfo(URL url) throws MalformedURLException
622    {
623  651928 JarInfo jinfo;
624  651928 synchronized (this.url2jarInfo) {
625    // fix: no longer use url.equals, since it distinguishes between
626    // "" and null in the host part of file URLs. The ""-type urls are
627    // correct but "null"-type ones come from file.toURI().toURL()
628    // on 1.4.1. (It is fixed in 1.4.2)
629  651929 jinfo = this.url2jarInfo.get(url.toExternalForm());
630  651929 if (jinfo == null) {
631  269 jinfo = new JarInfo(this, url);
632  269 this.url2jarInfo.put(url.toExternalForm(), jinfo);
633    }
634    }
635  651929 return jinfo;
636    }
637   
 
638    private static class JarResourceHandle extends ResourceHandle
639    {
640    final JarFile jar;
641   
642    final JarEntry jentry;
643   
644    final URL url;
645   
646    final URL codeSource;
647   
 
648  633 toggle JarResourceHandle(JarFile jar, JarEntry jentry, URL url, URL codeSource)
649    {
650  633 this.jar = jar;
651  633 this.jentry = jentry;
652  633 this.url = url;
653  633 this.codeSource = codeSource;
654    }
655   
 
656  0 toggle @Override
657    public String getName()
658    {
659  0 return this.jentry.getName();
660    }
661   
 
662  37 toggle @Override
663    public URL getURL()
664    {
665  37 return this.url;
666    }
667   
 
668  596 toggle @Override
669    public URL getCodeSourceURL()
670    {
671  596 return this.codeSource;
672    }
673   
 
674  596 toggle @Override
675    public InputStream getInputStream() throws IOException
676    {
677  596 return this.jar.getInputStream(this.jentry);
678    }
679   
 
680  596 toggle @Override
681    public int getContentLength()
682    {
683  596 return (int) this.jentry.getSize();
684    }
685   
 
686  594 toggle @Override
687    public Manifest getManifest() throws IOException
688    {
689  594 return this.jar.getManifest();
690    }
691   
 
692  0 toggle @Override
693    public Attributes getAttributes() throws IOException
694    {
695  0 return this.jentry.getAttributes();
696    }
697   
 
698  596 toggle @Override
699    public Certificate[] getCertificates()
700    {
701  596 return this.jentry.getCertificates();
702    }
703   
 
704  0 toggle @Override
705    public void close()
706    {
707    }
708    }
709   
 
710  257 toggle private static Map<URL, List<String>> parseJarIndex(URL cxt, JarFile jar) throws IOException
711    {
712  257 JarEntry entry = jar.getJarEntry(JAR_INDEX_ENTRY_NAME);
713  257 if (entry == null) {
714  246 return Collections.emptyMap();
715    }
716  11 InputStream is = jar.getInputStream(entry);
717  11 BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
718   
719  11 Map<URL, List<String>> result = new LinkedHashMap<>();
720   
721  11 String line;
722   
723    // skip version-info
724  11 do {
725  22 line = reader.readLine();
726  22 } while (line != null && line.trim().length() > 0);
727   
728  11 URL currentURL;
729  11 List<String> currentList = null;
730  11 while (true) {
731    // skip the blank line
732  22 line = reader.readLine();
733  22 if (line == null) {
734  11 return result;
735    }
736   
737  11 currentURL = new URL(cxt, line);
738  11 currentList = new ArrayList<>();
739  11 result.put(currentURL, currentList);
740   
741  11 while (true) {
742  77 line = reader.readLine();
743  77 if (line == null || line.trim().length() == 0) {
744  11 break;
745    }
746  66 currentList.add(line);
747    }
748    }
749    }
750   
 
751  257 toggle private static URL[] parseClassPath(JarFile jar, URL source) throws IOException
752    {
753  257 Manifest man = jar.getManifest();
754  257 if (man == null) {
755  13 return new URL[0];
756    }
757  244 Attributes attr = man.getMainAttributes();
758  244 if (attr == null) {
759  0 return new URL[0];
760    }
761  244 String cp = attr.getValue(Attributes.Name.CLASS_PATH);
762  244 if (cp == null) {
763  244 return new URL[0];
764    }
765  0 StringTokenizer tokenizer = new StringTokenizer(cp);
766  0 List<URL> cpList = new ArrayList<>();
767  0 URI sourceURI = URI.create(source.toString());
768  0 while (tokenizer.hasMoreTokens()) {
769  0 String token = tokenizer.nextToken();
770  0 try {
771  0 try {
772  0 URI uri = new URI(token);
773  0 if (!uri.isAbsolute()) {
774  0 uri = sourceURI.resolve(uri);
775    }
776  0 cpList.add(uri.toURL());
777    } catch (URISyntaxException e) {
778    // tolerate malformed URIs for backward-compatibility
779  0 URL url = new URL(source, token);
780  0 cpList.add(url);
781    }
782    } catch (MalformedURLException e) {
783  0 throw new IOException(e.getMessage());
784    }
785    }
786  0 return cpList.toArray(new URL[cpList.size()]);
787    }
788   
 
789    private class ResourceEnumeration<T> implements Enumeration<T>
790    {
791    final URL[] urls;
792   
793    final String name;
794   
795    final boolean findOnly;
796   
797    int idx;
798   
799    T next;
800   
801    Set<URL> previousURLs = new HashSet<>();
802   
 
803  96624 toggle ResourceEnumeration(URL[] urls, String name, boolean findOnly)
804    {
805  96630 this.urls = urls;
806  96611 this.name = name;
807  96617 this.findOnly = findOnly;
808  96617 this.idx = 0;
809    }
810   
 
811  5309 toggle @Override
812    public boolean hasMoreElements()
813    {
814  5309 fetchNext();
815  5309 return this.next != null;
816    }
817   
 
818  0 toggle @Override
819    public T nextElement()
820    {
821  0 fetchNext();
822  0 if (this.next == null) {
823  0 throw new NoSuchElementException();
824    }
825   
826  0 T nextElement = this.next;
827  0 this.next = null;
828   
829  0 return nextElement;
830    }
831   
 
832  5309 toggle @SuppressWarnings("unchecked")
833    private void fetchNext()
834    {
835  5309 if (this.next != null) {
836  0 return;
837    }
838  17675 while (this.idx < this.urls.length) {
839  12366 if (this.findOnly) {
840  12366 URL url = findResource(this.urls[this.idx], this.name, new HashSet<>(), this.previousURLs);
841  12366 if (url != null) {
842  0 this.previousURLs.add(url);
843  0 this.next = (T) url;
844  0 return;
845    }
846    } else {
847  0 ResourceHandle h =
848    getResource(this.urls[this.idx], this.name, new HashSet<>(), this.previousURLs);
849  0 if (h != null) {
850  0 this.previousURLs.add(h.getURL());
851  0 this.next = (T) h;
852  0 return;
853    }
854    }
855  12366 this.idx++;
856    }
857    }
858    }
859    }