blob: 3aefe401db8577d82584d462f269167d902f1d82 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.misc;
27
28import java.util.Enumeration;
29import java.util.HashMap;
30import java.util.LinkedList;
31import java.util.Hashtable;
32import java.util.NoSuchElementException;
33import java.util.Stack;
34import java.util.Set;
35import java.util.HashSet;
36import java.util.StringTokenizer;
37import java.util.ArrayList;
38import java.util.Iterator;
39import java.util.jar.JarFile;
40import sun.misc.JarIndex;
41import sun.misc.InvalidJarIndexException;
42import sun.net.www.ParseUtil;
43import java.util.zip.ZipEntry;
44import java.util.jar.JarEntry;
45import java.util.jar.Manifest;
46import java.util.jar.Attributes;
47import java.util.jar.Attributes.Name;
48import java.net.JarURLConnection;
49import java.net.MalformedURLException;
50import java.net.URL;
51import java.net.URLConnection;
52import java.net.HttpURLConnection;
53import java.net.URLStreamHandler;
54import java.net.URLStreamHandlerFactory;
55import java.io.File;
56import java.io.FileInputStream;
57import java.io.FileNotFoundException;
58import java.io.InputStream;
59import java.io.DataOutputStream;
60import java.io.IOException;
61import java.security.AccessController;
62import java.security.AccessControlException;
63import java.security.CodeSigner;
64import java.security.Permission;
65import java.security.PrivilegedAction;
66import java.security.PrivilegedExceptionAction;
67import java.security.cert.Certificate;
68import sun.misc.FileURLMapper;
69
70/**
71 * This class is used to maintain a search path of URLs for loading classes
72 * and resources from both JAR files and directories.
73 *
74 * @author David Connelly
75 */
76public class URLClassPath {
77 final static String USER_AGENT_JAVA_VERSION = "UA-Java-Version";
78 final static String JAVA_VERSION;
79 private static final boolean DEBUG;
80
81 static {
82 JAVA_VERSION = java.security.AccessController.doPrivileged(
83 new sun.security.action.GetPropertyAction("java.version"));
84 DEBUG = (java.security.AccessController.doPrivileged(
85 new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.debug")) != null);
86 }
87
88 /* The original search path of URLs. */
89 private ArrayList path = new ArrayList();
90
91 /* The stack of unopened URLs */
92 Stack urls = new Stack();
93
94 /* The resulting search path of Loaders */
95 ArrayList loaders = new ArrayList();
96
97 /* Map of each URL opened to its corresponding Loader */
98 HashMap lmap = new HashMap();
99
100 /* The jar protocol handler to use when creating new URLs */
101 private URLStreamHandler jarHandler;
102
103 /**
104 * Creates a new URLClassPath for the given URLs. The URLs will be
105 * searched in the order specified for classes and resources. A URL
106 * ending with a '/' is assumed to refer to a directory. Otherwise,
107 * the URL is assumed to refer to a JAR file.
108 *
109 * @param urls the directory and JAR file URLs to search for classes
110 * and resources
111 * @param factory the URLStreamHandlerFactory to use when creating new URLs
112 */
113 public URLClassPath(URL[] urls, URLStreamHandlerFactory factory) {
114 for (int i = 0; i < urls.length; i++) {
115 path.add(urls[i]);
116 }
117 push(urls);
118 if (factory != null) {
119 jarHandler = factory.createURLStreamHandler("jar");
120 }
121 }
122
123 public URLClassPath(URL[] urls) {
124 this(urls, null);
125 }
126
127 /**
128 * Appends the specified URL to the search path of directory and JAR
129 * file URLs from which to load classes and resources.
130 * <p>
131 * If the URL specified is null or is already in the list of
132 * URLs, then invoking this method has no effect.
133 */
134 public void addURL(URL url) {
135 synchronized (urls) {
136 if (url == null || path.contains(url))
137 return;
138
139 urls.add(0, url);
140 path.add(url);
141 }
142 }
143
144 /**
145 * Returns the original search path of URLs.
146 */
147 public URL[] getURLs() {
148 synchronized (urls) {
149 return (URL[])path.toArray(new URL[path.size()]);
150 }
151 }
152
153 /**
154 * Finds the resource with the specified name on the URL search path
155 * or null if not found or security check fails.
156 *
157 * @param name the name of the resource
158 * @param check whether to perform a security check
159 * @return a <code>URL</code> for the resource, or <code>null</code>
160 * if the resource could not be found.
161 */
162 public URL findResource(String name, boolean check) {
163 Loader loader;
164 for (int i = 0; (loader = getLoader(i)) != null; i++) {
165 URL url = loader.findResource(name, check);
166 if (url != null) {
167 return url;
168 }
169 }
170 return null;
171 }
172
173 /**
174 * Finds the first Resource on the URL search path which has the specified
175 * name. Returns null if no Resource could be found.
176 *
177 * @param name the name of the Resource
178 * @param check whether to perform a security check
179 * @return the Resource, or null if not found
180 */
181 public Resource getResource(String name, boolean check) {
182 if (DEBUG) {
183 System.err.println("URLClassPath.getResource(\"" + name + "\")");
184 }
185
186 Loader loader;
187 for (int i = 0; (loader = getLoader(i)) != null; i++) {
188 Resource res = loader.getResource(name, check);
189 if (res != null) {
190 return res;
191 }
192 }
193 return null;
194 }
195
196 /**
197 * Finds all resources on the URL search path with the given name.
198 * Returns an enumeration of the URL objects.
199 *
200 * @param name the resource name
201 * @return an Enumeration of all the urls having the specified name
202 */
203 public Enumeration findResources(final String name,
204 final boolean check) {
205 return new Enumeration() {
206 private int index = 0;
207 private URL url = null;
208
209 private boolean next() {
210 if (url != null) {
211 return true;
212 } else {
213 Loader loader;
214 while ((loader = getLoader(index++)) != null) {
215 url = loader.findResource(name, check);
216 if (url != null) {
217 return true;
218 }
219 }
220 return false;
221 }
222 }
223
224 public boolean hasMoreElements() {
225 return next();
226 }
227
228 public Object nextElement() {
229 if (!next()) {
230 throw new NoSuchElementException();
231 }
232 URL u = url;
233 url = null;
234 return u;
235 }
236 };
237 }
238
239 public Resource getResource(String name) {
240 return getResource(name, true);
241 }
242
243 /**
244 * Finds all resources on the URL search path with the given name.
245 * Returns an enumeration of the Resource objects.
246 *
247 * @param name the resource name
248 * @return an Enumeration of all the resources having the specified name
249 */
250 public Enumeration getResources(final String name,
251 final boolean check) {
252 return new Enumeration() {
253 private int index = 0;
254 private Resource res = null;
255
256 private boolean next() {
257 if (res != null) {
258 return true;
259 } else {
260 Loader loader;
261 while ((loader = getLoader(index++)) != null) {
262 res = loader.getResource(name, check);
263 if (res != null) {
264 return true;
265 }
266 }
267 return false;
268 }
269 }
270
271 public boolean hasMoreElements() {
272 return next();
273 }
274
275 public Object nextElement() {
276 if (!next()) {
277 throw new NoSuchElementException();
278 }
279 Resource r = res;
280 res = null;
281 return r;
282 }
283 };
284 }
285
286 public Enumeration getResources(final String name) {
287 return getResources(name, true);
288 }
289
290 /*
291 * Returns the Loader at the specified position in the URL search
292 * path. The URLs are opened and expanded as needed. Returns null
293 * if the specified index is out of range.
294 */
295 private synchronized Loader getLoader(int index) {
296 // Expand URL search path until the request can be satisfied
297 // or the URL stack is empty.
298 while (loaders.size() < index + 1) {
299 // Pop the next URL from the URL stack
300 URL url;
301 synchronized (urls) {
302 if (urls.empty()) {
303 return null;
304 } else {
305 url = (URL)urls.pop();
306 }
307 }
308 // Skip this URL if it already has a Loader. (Loader
309 // may be null in the case where URL has not been opened
310 // but is referenced by a JAR index.)
311 if (lmap.containsKey(url)) {
312 continue;
313 }
314 // Otherwise, create a new Loader for the URL.
315 Loader loader;
316 try {
317 loader = getLoader(url);
318 // If the loader defines a local class path then add the
319 // URLs to the list of URLs to be opened.
320 URL[] urls = loader.getClassPath();
321 if (urls != null) {
322 push(urls);
323 }
324 } catch (IOException e) {
325 // Silently ignore for now...
326 continue;
327 }
328 // Finally, add the Loader to the search path.
329 loaders.add(loader);
330 lmap.put(url, loader);
331 }
332 return (Loader)loaders.get(index);
333 }
334
335 /*
336 * Returns the Loader for the specified base URL.
337 */
338 private Loader getLoader(final URL url) throws IOException {
339 try {
340 return (Loader)java.security.AccessController.doPrivileged
341 (new java.security.PrivilegedExceptionAction() {
342 public Object run() throws IOException {
343 String file = url.getFile();
344 if (file != null && file.endsWith("/")) {
345 if ("file".equals(url.getProtocol())) {
346 return new FileLoader(url);
347 } else {
348 return new Loader(url);
349 }
350 } else {
351 return new JarLoader(url, jarHandler, lmap);
352 }
353 }
354 });
355 } catch (java.security.PrivilegedActionException pae) {
356 throw (IOException)pae.getException();
357 }
358 }
359
360 /*
361 * Pushes the specified URLs onto the list of unopened URLs.
362 */
363 private void push(URL[] us) {
364 synchronized (urls) {
365 for (int i = us.length - 1; i >= 0; --i) {
366 urls.push(us[i]);
367 }
368 }
369 }
370
371 /**
372 * Convert class path specification into an array of file URLs.
373 *
374 * The path of the file is encoded before conversion into URL
375 * form so that reserved characters can safely appear in the path.
376 */
377 public static URL[] pathToURLs(String path) {
378 StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
379 URL[] urls = new URL[st.countTokens()];
380 int count = 0;
381 while (st.hasMoreTokens()) {
382 File f = new File(st.nextToken());
383 try {
384 f = new File(f.getCanonicalPath());
385 } catch (IOException x) {
386 // use the non-canonicalized filename
387 }
388 try {
389 urls[count++] = ParseUtil.fileToEncodedURL(f);
390 } catch (IOException x) { }
391 }
392
393 if (urls.length != count) {
394 URL[] tmp = new URL[count];
395 System.arraycopy(urls, 0, tmp, 0, count);
396 urls = tmp;
397 }
398 return urls;
399 }
400
401 /*
402 * Check whether the resource URL should be returned.
403 * Return null on security check failure.
404 * Called by java.net.URLClassLoader.
405 */
406 public URL checkURL(URL url) {
407 try {
408 check(url);
409 } catch (Exception e) {
410 return null;
411 }
412
413 return url;
414 }
415
416 /*
417 * Check whether the resource URL should be returned.
418 * Throw exception on failure.
419 * Called internally within this file.
420 */
421 static void check(URL url) throws IOException {
422 SecurityManager security = System.getSecurityManager();
423 if (security != null) {
424 URLConnection urlConnection = url.openConnection();
425 Permission perm = urlConnection.getPermission();
426 if (perm != null) {
427 try {
428 security.checkPermission(perm);
429 } catch (SecurityException se) {
430 // fallback to checkRead/checkConnect for pre 1.2
431 // security managers
432 if ((perm instanceof java.io.FilePermission) &&
433 perm.getActions().indexOf("read") != -1) {
434 security.checkRead(perm.getName());
435 } else if ((perm instanceof
436 java.net.SocketPermission) &&
437 perm.getActions().indexOf("connect") != -1) {
438 URL locUrl = url;
439 if (urlConnection instanceof JarURLConnection) {
440 locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
441 }
442 security.checkConnect(locUrl.getHost(),
443 locUrl.getPort());
444 } else {
445 throw se;
446 }
447 }
448 }
449 }
450 }
451
452 /**
453 * Inner class used to represent a loader of resources and classes
454 * from a base URL.
455 */
456 private static class Loader {
457 private final URL base;
458
459 /*
460 * Creates a new Loader for the specified URL.
461 */
462 Loader(URL url) {
463 base = url;
464 }
465
466 /*
467 * Returns the base URL for this Loader.
468 */
469 URL getBaseURL() {
470 return base;
471 }
472
473 URL findResource(final String name, boolean check) {
474 URL url;
475 try {
476 url = new URL(base, ParseUtil.encodePath(name, false));
477 } catch (MalformedURLException e) {
478 throw new IllegalArgumentException("name");
479 }
480
481 try {
482 if (check) {
483 URLClassPath.check(url);
484 }
485
486 /*
487 * For a HTTP connection we use the HEAD method to
488 * check if the resource exists.
489 */
490 URLConnection uc = url.openConnection();
491 if (uc instanceof HttpURLConnection) {
492 HttpURLConnection hconn = (HttpURLConnection)uc;
493 hconn.setRequestMethod("HEAD");
494 if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
495 return null;
496 }
497 } else {
498 // our best guess for the other cases
499 InputStream is = url.openStream();
500 is.close();
501 }
502 return url;
503 } catch (Exception e) {
504 return null;
505 }
506 }
507
508 Resource getResource(final String name, boolean check) {
509 final URL url;
510 try {
511 url = new URL(base, ParseUtil.encodePath(name, false));
512 } catch (MalformedURLException e) {
513 throw new IllegalArgumentException("name");
514 }
515 final URLConnection uc;
516 try {
517 if (check) {
518 URLClassPath.check(url);
519 }
520 uc = url.openConnection();
521 InputStream in = uc.getInputStream();
522 } catch (Exception e) {
523 return null;
524 }
525 return new Resource() {
526 public String getName() { return name; }
527 public URL getURL() { return url; }
528 public URL getCodeSourceURL() { return base; }
529 public InputStream getInputStream() throws IOException {
530 return uc.getInputStream();
531 }
532 public int getContentLength() throws IOException {
533 return uc.getContentLength();
534 }
535 };
536 }
537
538 /*
539 * Returns the Resource for the specified name, or null if not
540 * found or the caller does not have the permission to get the
541 * resource.
542 */
543 Resource getResource(final String name) {
544 return getResource(name, true);
545 }
546
547 /*
548 * Returns the local class path for this loader, or null if none.
549 */
550 URL[] getClassPath() throws IOException {
551 return null;
552 }
553 }
554
555 /*
556 * Inner class used to represent a Loader of resources from a JAR URL.
557 */
558 static class JarLoader extends Loader {
559 private JarFile jar;
560 private URL csu;
561 private JarIndex index;
562 private MetaIndex metaIndex;
563 private URLStreamHandler handler;
564 private HashMap lmap;
565
566 /*
567 * Creates a new JarLoader for the specified URL referring to
568 * a JAR file.
569 */
570 JarLoader(URL url, URLStreamHandler jarHandler, HashMap loaderMap)
571 throws IOException
572 {
573 super(new URL("jar", "", -1, url + "!/", jarHandler));
574 csu = url;
575 handler = jarHandler;
576 lmap = loaderMap;
577
578 if (!isOptimizable(url)) {
579 ensureOpen();
580 } else {
581 String fileName = url.getFile();
582 if (fileName != null) {
583 fileName = ParseUtil.decode(fileName);
584 File f = new File(fileName);
585 metaIndex = MetaIndex.forJar(f);
586 // If the meta index is found but the file is not
587 // installed, set metaIndex to null. A typical
588 // senario is charsets.jar which won't be installed
589 // when the user is running in certain locale environment.
590 // The side effect of null metaIndex will cause
591 // ensureOpen get called so that IOException is thrown.
592 if (metaIndex != null && !f.exists()) {
593 metaIndex = null;
594 }
595 }
596
597 // metaIndex is null when either there is no such jar file
598 // entry recorded in meta-index file or such jar file is
599 // missing in JRE. See bug 6340399.
600 if (metaIndex == null) {
601 ensureOpen();
602 }
603 }
604 }
605
606 JarFile getJarFile () {
607 return jar;
608 }
609
610 private boolean isOptimizable(URL url) {
611 return "file".equals(url.getProtocol());
612 }
613
614 private void ensureOpen() throws IOException {
615 if (jar == null) {
616 try {
617 java.security.AccessController.doPrivileged(
618 new java.security.PrivilegedExceptionAction() {
619 public Object run() throws IOException {
620 if (DEBUG) {
621 System.err.println("Opening " + csu);
622 Thread.dumpStack();
623 }
624
625 jar = getJarFile(csu);
626 index = JarIndex.getJarIndex(jar, metaIndex);
627 if (index != null) {
628 String[] jarfiles = index.getJarFiles();
629 // Add all the dependent URLs to the lmap so that loaders
630 // will not be created for them by URLClassPath.getLoader(int)
631 // if the same URL occurs later on the main class path. We set
632 // Loader to null here to avoid creating a Loader for each
633 // URL until we actually need to try to load something from them.
634 for(int i = 0; i < jarfiles.length; i++) {
635 try {
636 URL jarURL = new URL(csu, jarfiles[i]);
637 // If a non-null loader already exists, leave it alone.
638 if (!lmap.containsKey(jarURL)) {
639 lmap.put(jarURL, null);
640 }
641 } catch (MalformedURLException e) {
642 continue;
643 }
644 }
645 }
646 return null;
647 }
648 }
649 );
650 } catch (java.security.PrivilegedActionException pae) {
651 throw (IOException)pae.getException();
652 }
653 }
654 }
655
656 private JarFile getJarFile(URL url) throws IOException {
657 // Optimize case where url refers to a local jar file
658 if (isOptimizable(url)) {
659 FileURLMapper p = new FileURLMapper (url);
660 if (!p.exists()) {
661 throw new FileNotFoundException(p.getPath());
662 }
663 return new JarFile (p.getPath());
664 }
665 URLConnection uc = getBaseURL().openConnection();
666 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
667 return ((JarURLConnection)uc).getJarFile();
668 }
669
670 /*
671 * Returns the index of this JarLoader if it exists.
672 */
673 JarIndex getIndex() {
674 try {
675 ensureOpen();
676 } catch (IOException e) {
677 throw (InternalError) new InternalError().initCause(e);
678 }
679 return index;
680 }
681
682 /*
683 * Creates the resource and if the check flag is set to true, checks if
684 * is its okay to return the resource.
685 */
686 Resource checkResource(final String name, boolean check,
687 final JarEntry entry) {
688
689 final URL url;
690 try {
691 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
692 if (check) {
693 URLClassPath.check(url);
694 }
695 } catch (MalformedURLException e) {
696 return null;
697 // throw new IllegalArgumentException("name");
698 } catch (IOException e) {
699 return null;
700 } catch (AccessControlException e) {
701 return null;
702 }
703
704 return new Resource() {
705 public String getName() { return name; }
706 public URL getURL() { return url; }
707 public URL getCodeSourceURL() { return csu; }
708 public InputStream getInputStream() throws IOException
709 { return jar.getInputStream(entry); }
710 public int getContentLength()
711 { return (int)entry.getSize(); }
712 public Manifest getManifest() throws IOException
713 { return jar.getManifest(); };
714 public Certificate[] getCertificates()
715 { return entry.getCertificates(); };
716 public CodeSigner[] getCodeSigners()
717 { return entry.getCodeSigners(); };
718 };
719 }
720
721
722 /*
723 * Returns true iff atleast one resource in the jar file has the same
724 * package name as that of the specified resource name.
725 */
726 boolean validIndex(final String name) {
727 String packageName = name;
728 int pos;
729 if((pos = name.lastIndexOf("/")) != -1) {
730 packageName = name.substring(0, pos);
731 }
732
733 String entryName;
734 ZipEntry entry;
735 Enumeration enum_ = jar.entries();
736 while (enum_.hasMoreElements()) {
737 entry = (ZipEntry)enum_.nextElement();
738 entryName = entry.getName();
739 if((pos = entryName.lastIndexOf("/")) != -1)
740 entryName = entryName.substring(0, pos);
741 if (entryName.equals(packageName)) {
742 return true;
743 }
744 }
745 return false;
746 }
747
748 /*
749 * Returns the URL for a resource with the specified name
750 */
751 URL findResource(final String name, boolean check) {
752 Resource rsc = getResource(name, check);
753 if (rsc != null) {
754 return rsc.getURL();
755 }
756 return null;
757 }
758
759 /*
760 * Returns the JAR Resource for the specified name.
761 */
762 Resource getResource(final String name, boolean check) {
763 if (metaIndex != null) {
764 if (!metaIndex.mayContain(name)) {
765 return null;
766 }
767 }
768
769 try {
770 ensureOpen();
771 } catch (IOException e) {
772 throw (InternalError) new InternalError().initCause(e);
773 }
774 final JarEntry entry = jar.getJarEntry(name);
775 if (entry != null)
776 return checkResource(name, check, entry);
777
778 if (index == null)
779 return null;
780
781 HashSet visited = new HashSet();
782 return getResource(name, check, visited);
783 }
784
785 /*
786 * Version of getResource() that tracks the jar files that have been
787 * visited by linking through the index files. This helper method uses
788 * a HashSet to store the URLs of jar files that have been searched and
789 * uses it to avoid going into an infinite loop, looking for a
790 * non-existent resource
791 */
792 Resource getResource(final String name, boolean check,
793 Set visited) {
794
795 Resource res;
796 Object[] jarFiles;
797 boolean done = false;
798 int count = 0;
799 LinkedList jarFilesList = null;
800
801 /* If there no jar files in the index that can potential contain
802 * this resource then return immediately.
803 */
804 if((jarFilesList = index.get(name)) == null)
805 return null;
806
807 do {
808 jarFiles = jarFilesList.toArray();
809 int size = jarFilesList.size();
810 /* loop through the mapped jar file list */
811 while(count < size) {
812 String jarName = (String)jarFiles[count++];
813 JarLoader newLoader;
814 final URL url;
815
816 try{
817 url = new URL(csu, jarName);
818 if ((newLoader = (JarLoader)lmap.get(url)) == null) {
819 /* no loader has been set up for this jar file
820 * before
821 */
822 newLoader = (JarLoader)
823 AccessController.doPrivileged(
824 new PrivilegedExceptionAction() {
825 public Object run() throws IOException {
826 return new JarLoader(url, handler,
827 lmap);
828 }
829 });
830
831 /* this newly opened jar file has its own index,
832 * merge it into the parent's index, taking into
833 * account the relative path.
834 */
835 JarIndex newIndex = newLoader.getIndex();
836 if(newIndex != null) {
837 int pos = jarName.lastIndexOf("/");
838 newIndex.merge(this.index, (pos == -1 ?
839 null : jarName.substring(0, pos + 1)));
840 }
841
842 /* put it in the global hashtable */
843 lmap.put(url, newLoader);
844 }
845 } catch (java.security.PrivilegedActionException pae) {
846 continue;
847 } catch (MalformedURLException e) {
848 continue;
849 }
850
851
852 /* Note that the addition of the url to the list of visited
853 * jars incorporates a check for presence in the hashmap
854 */
855 boolean visitedURL = !visited.add(url);
856 if (!visitedURL) {
857 try {
858 newLoader.ensureOpen();
859 } catch (IOException e) {
860 throw (InternalError) new InternalError().initCause(e);
861 }
862 final JarEntry entry = newLoader.jar.getJarEntry(name);
863 if (entry != null) {
864 return newLoader.checkResource(name, check, entry);
865 }
866
867 /* Verify that at least one other resource with the
868 * same package name as the lookedup resource is
869 * present in the new jar
870 */
871 if (!newLoader.validIndex(name)) {
872 /* the mapping is wrong */
873 throw new InvalidJarIndexException("Invalid index");
874 }
875 }
876
877 /* If newLoader is the current loader or if it is a
878 * loader that has already been searched or if the new
879 * loader does not have an index then skip it
880 * and move on to the next loader.
881 */
882 if (visitedURL || newLoader == this ||
883 newLoader.getIndex() == null) {
884 continue;
885 }
886
887 /* Process the index of the new loader
888 */
889 if((res = newLoader.getResource(name, check, visited))
890 != null) {
891 return res;
892 }
893 }
894 // Get the list of jar files again as the list could have grown
895 // due to merging of index files.
896 jarFilesList = index.get(name);
897
898 // If the count is unchanged, we are done.
899 } while(count < jarFilesList.size());
900 return null;
901 }
902
903
904 /*
905 * Returns the JAR file local class path, or null if none.
906 */
907 URL[] getClassPath() throws IOException {
908 if (index != null) {
909 return null;
910 }
911
912 if (metaIndex != null) {
913 return null;
914 }
915
916 ensureOpen();
917 parseExtensionsDependencies();
918 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary
919 Manifest man = jar.getManifest();
920 if (man != null) {
921 Attributes attr = man.getMainAttributes();
922 if (attr != null) {
923 String value = attr.getValue(Name.CLASS_PATH);
924 if (value != null) {
925 return parseClassPath(csu, value);
926 }
927 }
928 }
929 }
930 return null;
931 }
932
933 /*
934 * parse the standard extension dependencies
935 */
936 private void parseExtensionsDependencies() throws IOException {
937 ExtensionDependency.checkExtensionsDependencies(jar);
938 }
939
940 /*
941 * Parses value of the Class-Path manifest attribute and returns
942 * an array of URLs relative to the specified base URL.
943 */
944 private URL[] parseClassPath(URL base, String value)
945 throws MalformedURLException
946 {
947 StringTokenizer st = new StringTokenizer(value);
948 URL[] urls = new URL[st.countTokens()];
949 int i = 0;
950 while (st.hasMoreTokens()) {
951 String path = st.nextToken();
952 urls[i] = new URL(base, path);
953 i++;
954 }
955 return urls;
956 }
957 }
958
959 /*
960 * Inner class used to represent a loader of classes and resources
961 * from a file URL that refers to a directory.
962 */
963 private static class FileLoader extends Loader {
964 private File dir;
965
966 FileLoader(URL url) throws IOException {
967 super(url);
968 if (!"file".equals(url.getProtocol())) {
969 throw new IllegalArgumentException("url");
970 }
971 String path = url.getFile().replace('/', File.separatorChar);
972 path = ParseUtil.decode(path);
973 dir = new File(path);
974 }
975
976 /*
977 * Returns the URL for a resource with the specified name
978 */
979 URL findResource(final String name, boolean check) {
980 Resource rsc = getResource(name, check);
981 if (rsc != null) {
982 return rsc.getURL();
983 }
984 return null;
985 }
986
987 Resource getResource(final String name, boolean check) {
988 final URL url;
989 try {
990 URL normalizedBase = new URL(getBaseURL(), ".");
991 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
992
993 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
994 // requested resource had ../..'s in path
995 return null;
996 }
997
998 if (check)
999 URLClassPath.check(url);
1000 final File file =
1001 new File(dir, name.replace('/', File.separatorChar));
1002 if (file.exists()) {
1003 return new Resource() {
1004 public String getName() { return name; };
1005 public URL getURL() { return url; };
1006 public URL getCodeSourceURL() { return getBaseURL(); };
1007 public InputStream getInputStream() throws IOException
1008 { return new FileInputStream(file); };
1009 public int getContentLength() throws IOException
1010 { return (int)file.length(); };
1011 };
1012 }
1013 } catch (Exception e) {
1014 return null;
1015 }
1016 return null;
1017 }
1018 }
1019}