001: package com.dawidweiss.util.lang;
002:
003: /*
004: * This class slightly modified to allow optional resolving classes from this classloader
005: * before system/ parent class loader.
006: */
007:
008: /*
009: * Copyright (c) 1997-1999 The Java Apache Project. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions
013: * are met:
014: *
015: * 1. Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * 2. Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in
020: * the documentation and/or other materials provided with the
021: * distribution.
022: *
023: * 3. All advertising materials mentioning features or use of this
024: * software must display the following acknowledgment:
025: * "This product includes software developed by the Java Apache
026: * Project for use in the Apache JServ servlet engine project
027: * <http://java.apache.org/>."
028: *
029: * 4. The names "Apache JServ", "Apache JServ Servlet Engine" and
030: * "Java Apache Project" must not be used to endorse or promote products
031: * derived from this software without prior written permission.
032: *
033: * 5. Products derived from this software may not be called "Apache JServ"
034: * nor may "Apache" nor "Apache JServ" appear in their names without
035: * prior written permission of the Java Apache Project.
036: *
037: * 6. Redistributions of any form whatsoever must retain the following
038: * acknowledgment:
039: * "This product includes software developed by the Java Apache
040: * Project for use in the Apache JServ servlet engine project
041: * <http://java.apache.org/>."
042: *
043: * THIS SOFTWARE IS PROVIDED BY THE JAVA APACHE PROJECT "AS IS" AND ANY
044: * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
045: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
046: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JAVA APACHE PROJECT OR
047: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
048: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
049: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
050: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
051: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
052: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
053: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
054: * OF THE POSSIBILITY OF SUCH DAMAGE.
055: *
056: * This software consists of voluntary contributions made by many
057: * individuals on behalf of the Java Apache Group. For more information
058: * on the Java Apache Project and the Apache JServ Servlet Engine project,
059: * please see <http://java.apache.org/>.
060: */
061:
062: import java.util.Hashtable;
063: import java.util.Vector;
064: import java.util.Enumeration;
065: import java.io.*;
066: import java.net.URL;
067: import java.util.zip.ZipFile;
068: import java.util.zip.ZipException;
069: import java.util.zip.ZipEntry;
070:
071: /**
072: * A class loader that loads classes from directories and/or zip-format
073: * file such as JAR file. It tracks the modification time of the classes
074: * it loads to permit reloading through re-instantiation.
075: * <P>
076: * When the classloader reports its creator that one of the classes it
077: * has loaded has changed on disk, it should discard the classloader
078: * and create a new instance using <CODE>reinstantiate</CODE>.
079: * The classes are then reloaded into the new classloader as required.
080: *
081: * <P>The classloader can also load resources, which are a means
082: * for packaging application data such as images within a jar file
083: * or directory.
084: *
085: * <P>The classloader always first tries to load classes and resources
086: * from the system, and uses it's own path if that fails. This is also
087: * done if an empty repository is passed at construction.
088: *
089: * <P><B>How autoreload works:</B></P>
090: *
091: * <P>The Java VM considers two classes the same if they have the same
092: * fully-qualified name <B>and</B> if they were loaded from the same
093: * <CODE>ClassLoader</CODE>.
094: *
095: * <P>There is no way for a classloader to 'undefine' a class once it
096: * has been loaded. However, the servlet engine can discard a
097: * classloader and the classes it contains, causing the
098: *
099: * <P>The <CODE>JServServletManager</CODE> creates a new instance of
100: * the classloader each time it detects that any of the loaded classes
101: * have changed.
102: *
103: * <P>Before terminating, all servlets are destroyed.
104: *
105: * <P>According to the Java Language Specification (JLS), classes may
106: * be garbage-collected when there are no longer any instances of that
107: * class and the <CODE>java.lang.Class</CODE> object is finalizable.
108: * It is intended that this be the case when a <CODE>JServClassLoader</CODE>
109: * is discarded.
110: *
111: * <P>Many VM releases did not implement class garbage collection
112: * properly. In such a VM, the memory usage will continue to grow if
113: * autoreloading is enable. Running the VM with
114: * <CODE>-verbosegc</CODE> (or the corresponding option for
115: * non-Javasoft VMs) may give some debugging information.
116: *
117: * <P>It is important that the <CODE>destroy</CODE> method be
118: * implemented properly, as servlets may be destroyed and
119: * reinitialized several times in the life of a VM.
120: *
121: * @author Dawid Weiss (modifications)
122: * @author Francis J. Lacoste
123: * @author Martin Pool
124: * @author Jim Heintz
125: * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
126: * @see java.lang.ClassLoader
127: */
128: public class AdaptiveClassLoader extends ClassLoader {
129:
130: /**
131: * Generation counter, incremented for each classloader as they are
132: * created.
133: */
134: static private int generationCounter = 0;
135:
136: /**
137: * Generation number of the classloader, used to distinguish between
138: * different instances.
139: */
140: private int generation;
141:
142: /**
143: * Cache of the loaded classes. This contains ClassCacheEntry keyed
144: * by class names.
145: */
146: private Hashtable cache;
147:
148: /**
149: * Save our class loader for chaining, and speed purposes.
150: */
151: private ClassLoader myParentClassLoader;
152:
153: /**
154: * The classpath which this classloader searches for class definitions.
155: * Each element of the vector should be either a directory, a .zip
156: * file, or a .jar file.
157: * <p>
158: * It may be empty when only system classes are controlled.
159: */
160: private Vector repository;
161:
162: /**
163: * Order of class resolving - if true, parent class loader is checked after
164: * this class loader attempted to load a class.
165: */
166: private boolean parentClassLoaderLast;
167:
168: /**
169: * Private class used to maintain information about the classes that
170: * we loaded.
171: */
172: private static class ClassCacheEntry {
173:
174: /**
175: * The actual loaded class
176: */
177: Class loadedClass;
178:
179: /**
180: * The file from which this class was loaded; or null if
181: * it was loaded from the system.
182: */
183: File origin;
184:
185: /**
186: * The time at which the class was loaded from the origin
187: * file, in ms since the epoch.
188: */
189: long lastModified;
190:
191: /**
192: * Check whether this class was loaded from the system.
193: */
194: public boolean isSystemClass() {
195: return origin == null;
196: }
197: }
198:
199: //------------------------------------------------------- Constructors
200:
201: /**
202: * Creates a new class loader that will load classes from specified
203: * class repositories.
204: *
205: * @param classRepository An set of File classes indicating
206: * directories and/or zip/jar files. It may be empty when
207: * only system classes are loaded.
208: * @param parentClassLoaderLast if set to true, classes are resolved using
209: * this classloader before attempting the parent classloader. This
210: * feature can be used for dynamic reloading of classes, however
211: * it slows down performance and requires additional memory.
212: * @throws java.lang.IllegalArgumentException if the objects contained
213: * in the vector are not a file instance or the file is not
214: * a valid directory or a zip/jar file.
215: */
216: public AdaptiveClassLoader(Vector classRepository,
217: boolean parentClassLoaderLast)
218: throws IllegalArgumentException {
219: this (classRepository, null, parentClassLoaderLast);
220: }
221:
222: /**
223: * Creates a new class loader that will load classes from specified
224: * class repositories.
225: *
226: * @param classRepository An set of File classes indicating
227: * directories and/or zip/jar files. It may be empty when
228: * only system classes are loaded.
229: * @param chainedClassLoader A class loader to attempt to load classes
230: * as resources thru before falling back on the default system
231: * loaders.
232: * @param parentClassLoaderLast if set to true, classes are resolved using
233: * this classloader before attempting the parent classloader. This
234: * feature can be used for dynamic reloading of classes, however
235: * it slows down performance and requires additional memory.
236: * @throws java.lang.IllegalArgumentException if the objects contained
237: * in the vector are not a file instance or the file is not
238: * a valid directory or a zip/jar file.
239: */
240: public AdaptiveClassLoader(Vector classRepository,
241: ClassLoader chainedClassLoader,
242: boolean parentClassLoaderLast)
243: throws IllegalArgumentException {
244: this .parentClassLoaderLast = parentClassLoaderLast;
245: myParentClassLoader = chainedClassLoader;
246:
247: // Create the cache of loaded classes
248: cache = new Hashtable();
249:
250: // Verify that all the repository are valid.
251: Enumeration e = classRepository.elements();
252: while (e.hasMoreElements()) {
253: Object o = e.nextElement();
254: File file;
255: File[] files;
256: int i;
257:
258: // Check to see if element is a File instance.
259: try {
260: file = (File) o;
261: } catch (ClassCastException objectIsNotFile) {
262: throw new IllegalArgumentException("Object " + o
263: + "is not a valid \"File\" instance");
264: }
265:
266: // Check to see if we have proper access.
267: if (!file.exists()) {
268: throw new IllegalArgumentException("Repository "
269: + file.getAbsolutePath() + " doesn't exist!");
270: } else if (!file.canRead()) {
271: throw new IllegalArgumentException(
272: "Do not have read access for file "
273: + file.getAbsolutePath());
274: }
275:
276: // Check that it is a directory or zip/jar file
277: if (!(file.isDirectory() || isZipOrJarArchive(file))) {
278: throw new IllegalArgumentException(
279: file.getAbsolutePath()
280: + " is not a directory or zip/jar file"
281: + " or if it's a zip/jar file then it is corrupted.");
282: }
283: }
284:
285: // Store the class repository for use
286: this .repository = classRepository;
287:
288: // Increment and store generation counter
289: this .generation = generationCounter++;
290: }
291:
292: //------------------------------------------------------- Methods
293:
294: /**
295: * Test if a file is a ZIP or JAR archive.
296: *
297: * @param file the file to be tested.
298: * @return true if the file is a ZIP/JAR archive, false otherwise.
299: */
300: private boolean isZipOrJarArchive(File file) {
301: boolean isArchive = true;
302: ZipFile zipFile = null;
303:
304: try {
305: zipFile = new ZipFile(file);
306: } catch (ZipException zipCurrupted) {
307: isArchive = false;
308: } catch (IOException anyIOError) {
309: isArchive = false;
310: } finally {
311: if (zipFile != null) {
312: try {
313: zipFile.close();
314: } catch (IOException ignored) {
315: }
316: }
317: }
318:
319: return isArchive;
320: }
321:
322: /**
323: * Check to see if a given class should be reloaded because of a
324: * modification to the original class.
325: *
326: * @param classname The name of the class to check for modification.
327: */
328: public synchronized boolean shouldReload(String classname) {
329:
330: ClassCacheEntry entry = (ClassCacheEntry) cache.get(classname);
331:
332: if (entry == null) {
333: // class wasn't even loaded
334: return false;
335: } else if (entry.isSystemClass()) {
336: // System classes cannot be reloaded
337: return false;
338: } else {
339: boolean reload = (entry.origin.lastModified() != entry.lastModified);
340: return reload;
341: }
342: }
343:
344: /**
345: * Check whether the classloader should be reinstantiated.
346: * <P>
347: * The classloader must be replaced if there is any class whose
348: * origin file has changed since it was last loaded.
349: */
350: public synchronized boolean shouldReload() {
351:
352: // Check whether any class has changed
353: Enumeration e = cache.elements();
354: while (e.hasMoreElements()) {
355: ClassCacheEntry entry = (ClassCacheEntry) e.nextElement();
356:
357: if (entry.isSystemClass())
358: continue;
359:
360: // XXX: Because we want the classloader to be an accurate
361: // reflection of the contents of the repository, we also
362: // reload if a class origin file is now missing. This
363: // probably makes things a bit more fragile, but is OK in
364: // a servlet development situation. <mbp@pharos.com.au>
365:
366: long msOrigin = entry.origin.lastModified();
367:
368: if (msOrigin == 0) {
369: // class no longer exists
370: return true;
371: }
372:
373: if (msOrigin != entry.lastModified) {
374: // class is modified
375: return true;
376: }
377: }
378:
379: // No changes, no need to reload
380: return false;
381: }
382:
383: /**
384: * Re-instantiate this class loader.
385: * <p>
386: * This method creates a new instance
387: * of the class loader that will load classes form the same path
388: * as this one.
389: */
390: public AdaptiveClassLoader reinstantiate() {
391: return new AdaptiveClassLoader(repository, myParentClassLoader,
392: parentClassLoaderLast);
393: }
394:
395: //------------------------------------ Implementation of Classloader
396:
397: /*
398: * XXX: The javadoc for java.lang.ClassLoader says that the
399: * ClassLoader should cache classes so that it can handle repeated
400: * requests for the same class. On the other hand, the JLS seems
401: * to imply that each classloader is only asked to load each class
402: * once. Is this a contradiction?
403: *
404: * Perhaps the second call only applies to classes which have been
405: * garbage-collected?
406: */
407:
408: /**
409: * Resolves the specified name to a Class. The method loadClass()
410: * is called by the virtual machine. As an abstract method,
411: * loadClass() must be defined in a subclass of ClassLoader.
412: *
413: * @param name the name of the desired Class.
414: * @param resolve true if the Class needs to be resolved;
415: * false if the virtual machine just wants to determine
416: * whether the class exists or not
417: * @return the resulting Class.
418: * @exception ClassNotFoundException if the class loader cannot
419: * find a the requested class.
420: */
421: protected synchronized Class loadClass(String name, boolean resolve)
422: throws ClassNotFoundException {
423: // The class object that will be returned.
424: Class c = null;
425:
426: // Use the cached value, if this class is already loaded into
427: // this classloader.
428: ClassCacheEntry entry = (ClassCacheEntry) cache.get(name);
429:
430: if (entry != null) {
431: // Class found in our cache
432: c = entry.loadedClass;
433: if (resolve)
434: resolveClass(c);
435: return c;
436: }
437:
438: if (!securityAllowsClass(name)) {
439: return loadSystemClass(name, resolve);
440: }
441:
442: if (parentClassLoaderLast == false) {
443: // Attempt to load the class from the system
444: try {
445: c = loadSystemClass(name, resolve);
446: if (c != null) {
447: if (resolve)
448: resolveClass(c);
449: return c;
450: }
451: } catch (Exception e) {
452: c = null;
453: }
454: }
455:
456: // Try to load it from each repository
457: Enumeration repEnum = repository.elements();
458:
459: // Cache entry.
460: ClassCacheEntry classCache = new ClassCacheEntry();
461: while (repEnum.hasMoreElements()) {
462: byte[] classData = null;
463:
464: File file = (File) repEnum.nextElement();
465: try {
466: if (file.isDirectory()) {
467: classData = loadClassFromDirectory(file, name,
468: classCache);
469: } else {
470: classData = loadClassFromZipfile(file, name,
471: classCache);
472: }
473: } catch (IOException ioe) {
474: // Error while reading in data, consider it as not found
475: classData = null;
476: }
477:
478: if (classData != null) {
479: // Define the class
480: c = defineClass(name, classData, 0, classData.length);
481: // Cache the result;
482: classCache.loadedClass = c;
483: // Origin is set by the specific loader
484: classCache.lastModified = classCache.origin
485: .lastModified();
486: cache.put(name, classCache);
487:
488: // Resolve it if necessary
489: if (resolve)
490: resolveClass(c);
491:
492: return c;
493: }
494: }
495:
496: if (parentClassLoaderLast == true) {
497: // Attempt to load the class from the system
498: try {
499: c = loadSystemClass(name, resolve);
500: if (c != null) {
501: if (resolve)
502: resolveClass(c);
503: return c;
504: }
505: } catch (Exception e) {
506: c = null;
507: }
508: }
509:
510: // If not found in any repository
511: throw new ClassNotFoundException(name);
512: }
513:
514: /**
515: * Load a class using the system classloader.
516: *
517: * @exception ClassNotFoundException if the class loader cannot
518: * find a the requested class.
519: * @exception NoClassDefFoundError if the class loader cannot
520: * find a definition for the class.
521: */
522: protected Class loadSystemClass(String name, boolean resolve)
523: throws NoClassDefFoundError, ClassNotFoundException {
524: if (myParentClassLoader != null)
525: return myParentClassLoader.loadClass(name);
526: Class c = findSystemClass(name);
527: // Throws if not found.
528:
529: // Add cache entry
530: ClassCacheEntry cacheEntry = new ClassCacheEntry();
531: cacheEntry.origin = null;
532: cacheEntry.loadedClass = c;
533: cacheEntry.lastModified = Long.MAX_VALUE;
534: cache.put(name, cacheEntry);
535:
536: if (resolve)
537: resolveClass(c);
538:
539: return c;
540: }
541:
542: /**
543: * Checks whether a classloader is allowed to define a given class,
544: * within the security manager restrictions.
545: */
546: // XXX: Should we perhaps also not allow classes to be dynamically
547: // loaded from org.apache.jserv.*? Would it introduce security
548: // problems if people could override classes here?
549: // <mbp@humbug.org.au 1998-07-29>
550: private boolean securityAllowsClass(String className) {
551: try {
552: SecurityManager security = System.getSecurityManager();
553:
554: if (security == null) {
555: // if there's no security manager then all classes
556: // are allowed to be loaded
557: return true;
558: }
559:
560: int lastDot = className.lastIndexOf('.');
561: // Check if we are allowed to load the class' package
562: security.checkPackageDefinition((lastDot > -1) ? className
563: .substring(0, lastDot) : "");
564: // Throws if not allowed
565: return true;
566: } catch (SecurityException e) {
567: return false;
568: }
569: }
570:
571: /**
572: * Tries to load the class from a directory.
573: *
574: * @param dir The directory that contains classes.
575: * @param name The classname
576: * @param cache The cache entry to set the file if successful.
577: */
578: private byte[] loadClassFromDirectory(File dir, String name,
579: ClassCacheEntry cache) throws IOException {
580: // Translate class name to file name
581: String classFileName = name.replace('.', File.separatorChar)
582: + ".class";
583:
584: // Check for garbage input at beginning of file name
585: // i.e. ../ or similar
586: if (!Character.isJavaIdentifierStart(classFileName.charAt(0))) {
587: // Find real beginning of class name
588: int start = 1;
589: while (!Character.isJavaIdentifierStart(classFileName
590: .charAt(start++)))
591: ;
592: classFileName = classFileName.substring(start);
593: }
594:
595: File classFile = new File(dir, classFileName);
596:
597: if (classFile.exists()) {
598: cache.origin = classFile;
599: InputStream in = new FileInputStream(classFile);
600: try {
601: return loadBytesFromStream(in, (int) classFile.length());
602: } finally {
603: in.close();
604: }
605: } else {
606: // Not found
607: return null;
608: }
609: }
610:
611: /**
612: * Tries to load the class from a zip file.
613: *
614: * @param file The zipfile that contains classes.
615: * @param name The classname
616: * @param cache The cache entry to set the file if successful.
617: */
618: private byte[] loadClassFromZipfile(File file, String name,
619: ClassCacheEntry cache) throws IOException {
620: // Translate class name to file name
621: String classFileName = name.replace('.', '/') + ".class";
622:
623: ZipFile zipfile = new ZipFile(file);
624:
625: try {
626: ZipEntry entry = zipfile.getEntry(classFileName);
627: if (entry != null) {
628: cache.origin = file;
629: return loadBytesFromStream(zipfile
630: .getInputStream(entry), (int) entry.getSize());
631: } else {
632: // Not found
633: return null;
634: }
635: } finally {
636: zipfile.close();
637: }
638: }
639:
640: /**
641: * Loads all the bytes of an InputStream.
642: */
643: private byte[] loadBytesFromStream(InputStream in, int length)
644: throws IOException {
645: byte[] buf = new byte[length];
646: int nRead, count = 0;
647:
648: while ((length > 0)
649: && ((nRead = in.read(buf, count, length)) != -1)) {
650: count += nRead;
651: length -= nRead;
652: }
653:
654: return buf;
655: }
656:
657: /**
658: * Get an InputStream on a given resource. Will return null if no
659: * resource with this name is found.
660: * <p>
661: * The JServClassLoader translate the resource's name to a file
662: * or a zip entry. It looks for the resource in all its repository
663: * entry.
664: *
665: * @see java.lang.Class#getResourceAsStream(String)
666: * @param name the name of the resource, to be used as is.
667: * @return an InputStream on the resource, or null if not found.
668: */
669: public InputStream getResourceAsStream(String name) {
670: // Try to load it from the system class
671: InputStream s = null;
672: if (myParentClassLoader != null) {
673: s = myParentClassLoader.getResourceAsStream(name);
674: }
675: if (s == null) {
676: s = getSystemResourceAsStream(name);
677: }
678:
679: if (s == null) {
680: // Try to find it from every repository
681: Enumeration repEnum = repository.elements();
682: while (repEnum.hasMoreElements()) {
683: File file = (File) repEnum.nextElement();
684: if (file.isDirectory()) {
685: s = loadResourceFromDirectory(file, name);
686: } else if (name.endsWith(".initArgs")) {
687: String parentFile = file.getParent();
688: if (parentFile != null) {
689: File dir = new File(parentFile);
690: s = loadResourceFromDirectory(dir, name);
691: }
692: } else {
693: s = loadResourceFromZipfile(file, name);
694: }
695:
696: if (s != null) {
697: break;
698: }
699: }
700: }
701: return s;
702: }
703:
704: /**
705: * Loads resource from a directory.
706: */
707: private InputStream loadResourceFromDirectory(File dir, String name) {
708: // Name of resources are always separated by /
709: String fileName = name.replace('/', File.separatorChar);
710: File resFile = new File(dir, fileName);
711:
712: if (resFile.exists()) {
713: try {
714: return new FileInputStream(resFile);
715: } catch (FileNotFoundException shouldnothappen) {
716: return null;
717: }
718: } else {
719: return null;
720: }
721: }
722:
723: /**
724: * Loads resource from a zip file
725: */
726: private InputStream loadResourceFromZipfile(File file, String name) {
727: ZipFile zipfile = null;
728: InputStream resourceStream = null;
729: try {
730: zipfile = new ZipFile(file);
731: ZipEntry entry = zipfile.getEntry(name);
732:
733: if (entry != null) {
734: long length = entry.getSize();
735: resourceStream = zipfile.getInputStream(entry);
736: byte[] data = loadBytesFromStream(resourceStream,
737: (int) length);
738: return new ByteArrayInputStream(data);
739: } else {
740: return null;
741: }
742: } catch (IOException e) {
743: return null;
744: } finally {
745: if (resourceStream != null) {
746: try {
747: resourceStream.close();
748: } catch (IOException ignored) {
749: }
750: }
751: if (zipfile != null) {
752: try {
753: zipfile.close();
754: } catch (IOException ignored) {
755: }
756: }
757: }
758: }
759:
760: /**
761: * Find a resource with a given name. The return is a URL to the
762: * resource. Doing a getContent() on the URL may return an Image,
763: * an AudioClip,or an InputStream.
764: * <p>
765: * This classloader looks for the resource only in the directory
766: * repository for this resource.
767: *
768: * @param name the name of the resource, to be used as is.
769: * @return an URL on the resource, or null if not found.
770: */
771: public URL getResource(String name) {
772:
773: if (name == null) {
774: return null;
775: }
776:
777: // First ask the primordial class loader to fetch it from the classpath
778:
779: URL u = null;
780: if (myParentClassLoader != null) {
781: u = myParentClassLoader.getResource(name);
782: }
783: if (u == null) {
784: u = getSystemResource(name);
785: }
786: if (u != null) {
787: return u;
788: }
789:
790: // We got here so we have to look for the resource in our list of repository elements
791: Enumeration repEnum = repository.elements();
792: while (repEnum.hasMoreElements()) {
793: File file = (File) repEnum.nextElement();
794: // Construct a file://-URL if the repository is a directory
795: if (file.isDirectory()) {
796: String fileName = name.replace('/', File.separatorChar);
797: File resFile = new File(file, fileName);
798: if (resFile.exists()) {
799: // Build a file:// URL form the file name
800: try {
801: return new URL("file", null, resFile
802: .getAbsolutePath());
803: } catch (java.net.MalformedURLException badurl) {
804: badurl.printStackTrace();
805: return null;
806: }
807: }
808: } else {
809: // a jar:-URL *could* change even between minor releases, but
810: // didn't between JVM's 1.1.6 and 1.3beta. Tested on JVM's from
811: // IBM, Blackdown, Microsoft, Sun @ Windows and Sun @ Solaris
812: try {
813: ZipFile zf = new ZipFile(file.getAbsolutePath());
814: ZipEntry ze = zf.getEntry(name);
815:
816: if (ze != null) {
817: try {
818: return new URL("jar:file:"
819: + file.getAbsolutePath() + "!/"
820: + name);
821: } catch (java.net.MalformedURLException badurl) {
822: badurl.printStackTrace();
823: return null;
824: }
825: }
826: } catch (IOException ioe) {
827: ioe.printStackTrace();
828: return null;
829: }
830: }
831: }
832:
833: // Not found
834: return null;
835: }
836:
837: /**
838: * Return the last modified time for a class in the
839: * ClassCache.
840: *
841: * @throws ClassNotFoundException if class is not found
842: */
843: public long lastModified(String name) throws ClassNotFoundException {
844: ClassCacheEntry entry = (ClassCacheEntry) cache.get(name);
845: if (entry == null) {
846: throw new ClassNotFoundException("Could not find class: "
847: + name);
848: } else {
849: return entry.lastModified;
850: }
851: }
852: }
|