0001: /*
0002: * Copyright 2000-2004 The Apache Software Foundation
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: *
0016: */
0017: package foo;
0018:
0019: import java.io.ByteArrayOutputStream;
0020: import java.io.File;
0021: import java.io.FileInputStream;
0022: import java.io.IOException;
0023: import java.io.InputStream;
0024: import java.net.MalformedURLException;
0025: import java.net.URL;
0026: import java.util.Collections;
0027: import java.util.Enumeration;
0028: import java.util.HashMap;
0029: import java.util.Hashtable;
0030: import java.util.Map;
0031: import java.util.StringTokenizer;
0032: import java.util.Vector;
0033: import java.util.jar.Attributes;
0034: import java.util.jar.JarFile;
0035: import java.util.jar.Manifest;
0036: import java.util.logging.Logger;
0037: import java.util.zip.ZipEntry;
0038: import java.util.zip.ZipFile;
0039:
0040: /**
0041: * Used to load classes within ant with a different classpath from
0042: * that used to start ant. Note that it is possible to force a class
0043: * into this loader even when that class is on the system classpath by
0044: * using the forceLoadClass method. Any subsequent classes loaded by that
0045: * class will then use this loader rather than the system class loader.
0046: *
0047: */
0048: public class ReloadableUrlClassLoader extends ClassLoader {
0049:
0050: private static final Logger logger = Logger
0051: .getLogger(ReloadableUrlClassLoader.class.getName());
0052:
0053: /**
0054: * An enumeration of all resources of a given name found within the
0055: * classpath of this class loader. This enumeration is used by the
0056: * ClassLoader.findResources method, which is in
0057: * turn used by the ClassLoader.getResources method.
0058: *
0059: * @see ReloadableUrlClassLoader#findResources(String)
0060: * @see ClassLoader#getResources(String)
0061: */
0062: private class ResourceEnumeration implements Enumeration<URL> {
0063: /**
0064: * The name of the resource being searched for.
0065: */
0066: private String resourceName;
0067:
0068: /**
0069: * The index of the next classpath element to search.
0070: */
0071: private int pathElementsIndex;
0072:
0073: /**
0074: * The URL of the next resource to return in the enumeration. If this
0075: * field is <code>null</code> then the enumeration has been completed,
0076: * i.e., there are no more elements to return.
0077: */
0078: private URL nextResource;
0079:
0080: /**
0081: * Constructs a new enumeration of resources of the given name found
0082: * within this class loader's classpath.
0083: *
0084: * @param name the name of the resource to search for.
0085: */
0086: ResourceEnumeration(String name) {
0087: this .resourceName = name;
0088: this .pathElementsIndex = 0;
0089: findNextResource();
0090: }
0091:
0092: /**
0093: * Indicates whether there are more elements in the enumeration to
0094: * return.
0095: *
0096: * @return <code>true</code> if there are more elements in the
0097: * enumeration; <code>false</code> otherwise.
0098: */
0099: public boolean hasMoreElements() {
0100: return (this .nextResource != null);
0101: }
0102:
0103: /**
0104: * Returns the next resource in the enumeration.
0105: *
0106: * @return the next resource in the enumeration
0107: */
0108: public URL nextElement() {
0109: URL ret = this .nextResource;
0110: findNextResource();
0111: return ret;
0112: }
0113:
0114: /**
0115: * Locates the next resource of the correct name in the classpath and
0116: * sets <code>nextResource</code> to the URL of that resource. If no
0117: * more resources can be found, <code>nextResource</code> is set to
0118: * <code>null</code>.
0119: */
0120: private void findNextResource() {
0121: URL url = null;
0122: while ((pathElementsIndex < pathComponents.size())
0123: && (url == null)) {
0124: File pathComponent = pathComponents
0125: .elementAt(pathElementsIndex);
0126: url = getResourceURL(pathComponent, this .resourceName);
0127: pathElementsIndex++;
0128: }
0129: this .nextResource = url;
0130: }
0131: }
0132:
0133: /**
0134: * The size of buffers to be used in this classloader.
0135: */
0136: private static final int BUFFER_SIZE = 8192;
0137:
0138: /**
0139: * The components of the classpath that the classloader searches
0140: * for classes.
0141: */
0142: private Vector<File> pathComponents = new Vector<File>();
0143:
0144: /**
0145: * Indicates whether the parent class loader should be
0146: * consulted before trying to load with this class loader.
0147: */
0148: private boolean parentFirst = true;
0149:
0150: /**
0151: * These are the package roots that are to be loaded by the parent class
0152: * loader regardless of whether the parent class loader is being searched
0153: * first or not.
0154: */
0155: private Vector<String> systemPackages = new Vector<String>();
0156:
0157: /**
0158: * These are the package roots that are to be loaded by this class loader
0159: * regardless of whether the parent class loader is being searched first
0160: * or not.
0161: */
0162: private Vector<String> loaderPackages = new Vector<String>();
0163:
0164: /**
0165: * Whether or not this classloader will ignore the base
0166: * classloader if it can't find a class.
0167: *
0168: * @see #setIsolated(boolean)
0169: */
0170: private boolean ignoreBase = false;
0171:
0172: /**
0173: * The parent class loader, if one is given or can be determined.
0174: */
0175: private ClassLoader parent = null;
0176:
0177: /**
0178: * A hashtable of zip files opened by the classloader (File to ZipFile).
0179: */
0180: private Hashtable<File, ZipFile> zipFiles = new Hashtable<File, ZipFile>();
0181:
0182: /**
0183: * Create an Ant Class Loader
0184: */
0185: public ReloadableUrlClassLoader() {
0186: setParent(null);
0187: }
0188:
0189: /**
0190: * Creates an empty class loader. The classloader should be configured
0191: * with path elements to specify where the loader is to look for
0192: * classes.
0193: *
0194: * @param parent The parent classloader to which unsatisfied loading
0195: * attempts are delegated. May be <code>null</code>,
0196: * in which case the classloader which loaded this
0197: * class is used as the parent.
0198: * @param parentFirst If <code>true</code>, indicates that the parent
0199: * classloader should be consulted before trying to
0200: * load the a class through this loader.
0201: */
0202: public ReloadableUrlClassLoader(ClassLoader parent,
0203: boolean parentFirst) {
0204: setParent(parent);
0205: this .parentFirst = parentFirst;
0206: }
0207:
0208: /**
0209: * Set the parent for this class loader. This is the class loader to which
0210: * this class loader will delegate to load classes
0211: *
0212: * @param parent the parent class loader.
0213: */
0214: public void setParent(ClassLoader parent) {
0215: if (parent == null) {
0216: this .parent = getClass().getClassLoader();
0217: } else {
0218: this .parent = parent;
0219: }
0220: }
0221:
0222: /**
0223: * Control whether class lookup is delegated to the parent loader first
0224: * or after this loader. Use with extreme caution. Setting this to
0225: * false violates the class loader hierarchy and can lead to Linkage errors
0226: *
0227: * @param parentFirst if true, delegate initial class search to the parent
0228: * classloader.
0229: */
0230: public void setParentFirst(boolean parentFirst) {
0231: this .parentFirst = parentFirst;
0232: }
0233:
0234: /**
0235: * Adds an element to the classpath to be searched.
0236: *
0237: * @param pathElement The path element to add. Must not be
0238: * <code>null</code>.
0239: *
0240: * @exception IOException if the given path element cannot be resolved
0241: * against the project.
0242: */
0243: public void addPathElement(String pathElement) throws IOException {
0244: File pathComponent = new File(pathElement);
0245: addPathFile(pathComponent);
0246: }
0247:
0248: /**
0249: * Add a file to the path. This classloader reads the manifest, if
0250: * available, and adds any additional class path jars specified in the
0251: * manifest.
0252: *
0253: * @param pathComponent the file which is to be added to the path for
0254: * this class loader
0255: *
0256: * @throws IOException if data needed from the file cannot be read.
0257: */
0258: protected void addPathFile(File pathComponent) throws IOException {
0259: pathComponents.addElement(pathComponent);
0260:
0261: if (pathComponent.isDirectory()) {
0262: return;
0263: }
0264:
0265: String absPathPlusTimeAndLength = pathComponent
0266: .getAbsolutePath()
0267: + pathComponent.lastModified()
0268: + "-"
0269: + pathComponent.length();
0270: String classpath = pathMap.get(absPathPlusTimeAndLength);
0271: if (classpath == null) {
0272: ZipFile jarFile = null;
0273: InputStream manifestStream = null;
0274: try {
0275: jarFile = new ZipFile(pathComponent);
0276: manifestStream = jarFile.getInputStream(new ZipEntry(
0277: "META-INF/MANIFEST.MF"));
0278:
0279: if (manifestStream == null) {
0280: return;
0281: }
0282: Manifest manifest = new Manifest(manifestStream);
0283: classpath = manifest.getMainAttributes().getValue(
0284: "Class-Path");
0285:
0286: } finally {
0287: if (manifestStream != null) {
0288: manifestStream.close();
0289: }
0290: if (jarFile != null) {
0291: jarFile.close();
0292: }
0293: }
0294: if (classpath == null) {
0295: classpath = "";
0296: }
0297: pathMap.put(absPathPlusTimeAndLength, classpath);
0298: }
0299:
0300: if (!"".equals(classpath)) {
0301: URL baseURL = pathComponent.toURL();
0302: StringTokenizer st = new StringTokenizer(classpath);
0303: while (st.hasMoreTokens()) {
0304: String classpathElement = st.nextToken();
0305: URL libraryURL = new URL(baseURL, classpathElement);
0306: if (!libraryURL.getProtocol().equals("file")) {
0307: logger
0308: .fine("Skipping jar library "
0309: + classpathElement
0310: + " since only relative URLs are supported by this"
0311: + " loader");
0312: continue;
0313: }
0314: File libraryFile = new File(libraryURL.getFile());
0315: if (libraryFile.exists() && !isInPath(libraryFile)) {
0316: addPathFile(libraryFile);
0317: }
0318: }
0319: }
0320: }
0321:
0322: /**
0323: * Returns the classpath this classloader will consult.
0324: *
0325: * @return the classpath used for this classloader, with elements
0326: * separated by the path separator for the system.
0327: */
0328: public String getClasspath() {
0329: StringBuffer sb = new StringBuffer();
0330: boolean firstPass = true;
0331: Enumeration componentEnum = pathComponents.elements();
0332: while (componentEnum.hasMoreElements()) {
0333: if (!firstPass) {
0334: sb.append(System.getProperty("path.separator"));
0335: } else {
0336: firstPass = false;
0337: }
0338: sb.append(((File) componentEnum.nextElement())
0339: .getAbsolutePath());
0340: }
0341: return sb.toString();
0342: }
0343:
0344: /**
0345: * Sets whether this classloader should run in isolated mode. In
0346: * isolated mode, classes not found on the given classpath will
0347: * not be referred to the parent class loader but will cause a
0348: * ClassNotFoundException.
0349: *
0350: * @param isolated Whether or not this classloader should run in
0351: * isolated mode.
0352: */
0353: public synchronized void setIsolated(boolean isolated) {
0354: ignoreBase = isolated;
0355: }
0356:
0357: /**
0358: * Adds a package root to the list of packages which must be loaded on the
0359: * parent loader.
0360: *
0361: * All subpackages are also included.
0362: *
0363: * @param packageRoot The root of all packages to be included.
0364: * Should not be <code>null</code>.
0365: */
0366: public void addSystemPackageRoot(String packageRoot) {
0367: systemPackages.addElement(packageRoot
0368: + (packageRoot.endsWith(".") ? "" : "."));
0369: }
0370:
0371: /**
0372: * Adds a package root to the list of packages which must be loaded using
0373: * this loader.
0374: *
0375: * All subpackages are also included.
0376: *
0377: * @param packageRoot The root of all packages to be included.
0378: * Should not be <code>null</code>.
0379: */
0380: public void addLoaderPackageRoot(String packageRoot) {
0381: loaderPackages.addElement(packageRoot
0382: + (packageRoot.endsWith(".") ? "" : "."));
0383: }
0384:
0385: /**
0386: * Loads a class through this class loader even if that class is available
0387: * on the parent classpath.
0388: *
0389: * This ensures that any classes which are loaded by the returned class
0390: * will use this classloader.
0391: *
0392: * @param classname The name of the class to be loaded.
0393: * Must not be <code>null</code>.
0394: *
0395: * @return the required Class object
0396: *
0397: * @exception ClassNotFoundException if the requested class does not exist
0398: * on this loader's classpath.
0399: */
0400: public Class forceLoadClass(String classname)
0401: throws ClassNotFoundException {
0402: logger.finer("force loading " + classname);
0403:
0404: Class theClass = findLoadedClass(classname);
0405:
0406: if (theClass == null) {
0407: theClass = findClass(classname);
0408: }
0409:
0410: return theClass;
0411: }
0412:
0413: /**
0414: * Loads a class through this class loader but defer to the parent class
0415: * loader.
0416: *
0417: * This ensures that instances of the returned class will be compatible
0418: * with instances which have already been loaded on the parent
0419: * loader.
0420: *
0421: * @param classname The name of the class to be loaded.
0422: * Must not be <code>null</code>.
0423: *
0424: * @return the required Class object
0425: *
0426: * @exception ClassNotFoundException if the requested class does not exist
0427: * on this loader's classpath.
0428: */
0429: public Class forceLoadSystemClass(String classname)
0430: throws ClassNotFoundException {
0431: logger.finer("force system loading " + classname);
0432:
0433: Class theClass = findLoadedClass(classname);
0434:
0435: if (theClass == null) {
0436: theClass = findBaseClass(classname);
0437: }
0438:
0439: return theClass;
0440: }
0441:
0442: /**
0443: * Returns a stream to read the requested resource name.
0444: *
0445: * @param name The name of the resource for which a stream is required.
0446: * Must not be <code>null</code>.
0447: *
0448: * @return a stream to the required resource or <code>null</code> if the
0449: * resource cannot be found on the loader's classpath.
0450: */
0451: public InputStream getResourceAsStream(String name) {
0452:
0453: InputStream resourceStream;
0454: if (isParentFirst(name)) {
0455: resourceStream = loadBaseResource(name);
0456: if (resourceStream != null) {
0457: logger.finer("ResourceStream for " + name
0458: + " loaded from parent loader");
0459:
0460: } else {
0461: resourceStream = loadResource(name);
0462: if (resourceStream != null) {
0463: logger.finer("ResourceStream for " + name
0464: + " loaded from ant loader");
0465: }
0466: }
0467: } else {
0468: resourceStream = loadResource(name);
0469: if (resourceStream != null) {
0470: logger.finer("ResourceStream for " + name
0471: + " loaded from ant loader");
0472:
0473: } else {
0474: resourceStream = loadBaseResource(name);
0475: if (resourceStream != null) {
0476: logger.finer("ResourceStream for " + name
0477: + " loaded from parent loader");
0478: }
0479: }
0480: }
0481:
0482: if (resourceStream == null) {
0483: logger.finer("Couldn't load ResourceStream for " + name);
0484: }
0485:
0486: return resourceStream;
0487: }
0488:
0489: /**
0490: * Returns a stream to read the requested resource name from this loader.
0491: *
0492: * @param name The name of the resource for which a stream is required.
0493: * Must not be <code>null</code>.
0494: *
0495: * @return a stream to the required resource or <code>null</code> if
0496: * the resource cannot be found on the loader's classpath.
0497: */
0498: private InputStream loadResource(String name) {
0499: // we need to search the components of the path to see if we can
0500: // find the class we want.
0501: InputStream stream = null;
0502:
0503: Enumeration e = pathComponents.elements();
0504: while (e.hasMoreElements() && stream == null) {
0505: File pathComponent = (File) e.nextElement();
0506: stream = getResourceStream(pathComponent, name);
0507: }
0508: return stream;
0509: }
0510:
0511: /**
0512: * Finds a system resource (which should be loaded from the parent
0513: * classloader).
0514: *
0515: * @param name The name of the system resource to load.
0516: * Must not be <code>null</code>.
0517: *
0518: * @return a stream to the named resource, or <code>null</code> if
0519: * the resource cannot be found.
0520: */
0521: private InputStream loadBaseResource(String name) {
0522: if (parent == null) {
0523: return getSystemResourceAsStream(name);
0524: } else {
0525: return parent.getResourceAsStream(name);
0526: }
0527: }
0528:
0529: /**
0530: * Returns an inputstream to a given resource in the given file which may
0531: * either be a directory or a zip file.
0532: *
0533: * @param file the file (directory or jar) in which to search for the
0534: * resource. Must not be <code>null</code>.
0535: * @param resourceName The name of the resource for which a stream is
0536: * required. Must not be <code>null</code>.
0537: *
0538: * @return a stream to the required resource or <code>null</code> if
0539: * the resource cannot be found in the given file.
0540: */
0541: private InputStream getResourceStream(File file, String resourceName) {
0542: try {
0543: if (!file.exists()) {
0544: return null;
0545: }
0546:
0547: if (file.isDirectory()) {
0548: File resource = new File(file, resourceName);
0549:
0550: if (resource.exists()) {
0551: return new FileInputStream(resource);
0552: }
0553: } else {
0554: // is the zip file in the cache
0555: ZipFile zipFile = zipFiles.get(file);
0556: if (zipFile == null) {
0557: zipFile = new ZipFile(file);
0558: zipFiles.put(file, zipFile);
0559: }
0560: ZipEntry entry = zipFile.getEntry(resourceName);
0561: if (entry != null) {
0562: return zipFile.getInputStream(entry);
0563: }
0564: }
0565: } catch (Exception e) {
0566: logger.fine("Ignoring Exception " + e.getClass().getName()
0567: + ": " + e.getMessage() + " reading resource "
0568: + resourceName + " from " + file);
0569: }
0570:
0571: return null;
0572: }
0573:
0574: /**
0575: * Tests whether or not the parent classloader should be checked for
0576: * a resource before this one. If the resource matches both the
0577: * "use parent classloader first" and the "use this classloader first"
0578: * lists, the latter takes priority.
0579: *
0580: * @param resourceName The name of the resource to check.
0581: * Must not be <code>null</code>.
0582: *
0583: * @return whether or not the parent classloader should be checked for a
0584: * resource before this one is.
0585: */
0586: private boolean isParentFirst(String resourceName) {
0587: // default to the global setting and then see
0588: // if this class belongs to a package which has been
0589: // designated to use a specific loader first
0590: // (this one or the parent one)
0591:
0592: // XXX - shouldn't this always return false in isolated mode?
0593:
0594: boolean useParentFirst = parentFirst;
0595:
0596: for (Enumeration e = systemPackages.elements(); e
0597: .hasMoreElements();) {
0598: String packageName = (String) e.nextElement();
0599: if (resourceName.startsWith(packageName)) {
0600: useParentFirst = true;
0601: break;
0602: }
0603: }
0604:
0605: for (Enumeration e = loaderPackages.elements(); e
0606: .hasMoreElements();) {
0607: String packageName = (String) e.nextElement();
0608: if (resourceName.startsWith(packageName)) {
0609: useParentFirst = false;
0610: break;
0611: }
0612: }
0613:
0614: return useParentFirst;
0615: }
0616:
0617: /**
0618: * Finds the resource with the given name. A resource is
0619: * some data (images, audio, text, etc) that can be accessed by class
0620: * code in a way that is independent of the location of the code.
0621: *
0622: * @param name The name of the resource for which a stream is required.
0623: * Must not be <code>null</code>.
0624: *
0625: * @return a URL for reading the resource, or <code>null</code> if the
0626: * resource could not be found or the caller doesn't have
0627: * adequate privileges to get the resource.
0628: */
0629: public URL getResource(String name) {
0630: // we need to search the components of the path to see if
0631: // we can find the class we want.
0632: URL url = null;
0633: if (isParentFirst(name)) {
0634: url = (parent == null) ? super .getResource(name) : parent
0635: .getResource(name);
0636: }
0637:
0638: if (url != null) {
0639: logger.finer("Resource " + name
0640: + " loaded from parent loader");
0641:
0642: } else {
0643: // try and load from this loader if the parent either didn't find
0644: // it or wasn't consulted.
0645: Enumeration e = pathComponents.elements();
0646: while (e.hasMoreElements() && url == null) {
0647: File pathComponent = (File) e.nextElement();
0648: url = getResourceURL(pathComponent, name);
0649: if (url != null) {
0650: logger.finer("Resource " + name
0651: + " loaded from ant loader");
0652: }
0653: }
0654: }
0655:
0656: if (url == null && !isParentFirst(name)) {
0657: // this loader was first but it didn't find it - try the parent
0658:
0659: url = (parent == null) ? super .getResource(name) : parent
0660: .getResource(name);
0661: if (url != null) {
0662: logger.finer("Resource " + name
0663: + " loaded from parent loader");
0664: }
0665: }
0666:
0667: if (url == null) {
0668: logger.finer("Couldn't load Resource " + name);
0669: }
0670:
0671: return url;
0672: }
0673:
0674: /**
0675: * Returns an enumeration of URLs representing all the resources with the
0676: * given name by searching the class loader's classpath.
0677: *
0678: * @param name The resource name to search for.
0679: * Must not be <code>null</code>.
0680: * @return an enumeration of URLs for the resources
0681: * @exception IOException if I/O errors occurs (can't happen)
0682: */
0683: protected Enumeration<URL> findResources(String name)
0684: throws IOException {
0685: return new ResourceEnumeration(name);
0686: }
0687:
0688: /**
0689: * Returns the URL of a given resource in the given file which may
0690: * either be a directory or a zip file.
0691: *
0692: * @param file The file (directory or jar) in which to search for
0693: * the resource. Must not be <code>null</code>.
0694: * @param resourceName The name of the resource for which a stream
0695: * is required. Must not be <code>null</code>.
0696: *
0697: * @return a stream to the required resource or <code>null</code> if the
0698: * resource cannot be found in the given file object.
0699: */
0700: protected URL getResourceURL(File file, String resourceName) {
0701: try {
0702: if (!file.exists()) {
0703: return null;
0704: }
0705:
0706: if (file.isDirectory()) {
0707: File resource = new File(file, resourceName);
0708:
0709: if (resource.exists()) {
0710: try {
0711: return resource.toURL();
0712: } catch (MalformedURLException ex) {
0713: return null;
0714: }
0715: }
0716: } else {
0717: ZipFile zipFile = zipFiles.get(file);
0718: if (zipFile == null) {
0719: zipFile = new ZipFile(file);
0720: zipFiles.put(file, zipFile);
0721: }
0722:
0723: ZipEntry entry = zipFile.getEntry(resourceName);
0724: if (entry != null) {
0725: try {
0726: return new URL("jar:" + file.toURL() + "!/"
0727: + entry);
0728: } catch (MalformedURLException ex) {
0729: return null;
0730: }
0731: }
0732: }
0733: } catch (Exception e) {
0734: e.printStackTrace();
0735: }
0736:
0737: return null;
0738: }
0739:
0740: /**
0741: * Loads a class with this class loader.
0742: *
0743: * This class attempts to load the class in an order determined by whether
0744: * or not the class matches the system/loader package lists, with the
0745: * loader package list taking priority. If the classloader is in isolated
0746: * mode, failure to load the class in this loader will result in a
0747: * ClassNotFoundException.
0748: *
0749: * @param classname The name of the class to be loaded.
0750: * Must not be <code>null</code>.
0751: * @param resolve <code>true</code> if all classes upon which this class
0752: * depends are to be loaded.
0753: *
0754: * @return the required Class object
0755: *
0756: * @exception ClassNotFoundException if the requested class does not exist
0757: * on the system classpath (when not in isolated mode) or this loader's
0758: * classpath.
0759: */
0760: protected synchronized Class loadClass(String classname,
0761: boolean resolve) throws ClassNotFoundException {
0762: // 'sync' is needed - otherwise 2 threads can load the same class
0763: // twice, resulting in LinkageError: duplicated class definition.
0764: // findLoadedClass avoids that, but without sync it won't work.
0765:
0766: Class theClass = findLoadedClass(classname);
0767: if (theClass != null) {
0768: return theClass;
0769: }
0770:
0771: if (isParentFirst(classname)) {
0772: try {
0773: theClass = findBaseClass(classname);
0774: logger.finer("Class " + classname
0775: + " loaded from parent loader "
0776: + "(parentFirst)");
0777: } catch (ClassNotFoundException cnfe) {
0778: theClass = findClass(classname);
0779: logger.finer("Class " + classname
0780: + " loaded from ant loader " + "(parentFirst)");
0781: }
0782: } else {
0783: try {
0784: theClass = findClass(classname);
0785: logger.finer("Class " + classname
0786: + " loaded from ant loader");
0787: } catch (ClassNotFoundException cnfe) {
0788: if (ignoreBase) {
0789: throw cnfe;
0790: }
0791: theClass = findBaseClass(classname);
0792: logger.finer("Class " + classname
0793: + " loaded from parent loader");
0794: }
0795: }
0796:
0797: if (resolve) {
0798: resolveClass(theClass);
0799: }
0800:
0801: return theClass;
0802: }
0803:
0804: /**
0805: * Converts the class dot notation to a filesystem equivalent for
0806: * searching purposes.
0807: *
0808: * @param classname The class name in dot format (eg java.lang.Integer).
0809: * Must not be <code>null</code>.
0810: *
0811: * @return the classname in filesystem format (eg java/lang/Integer.class)
0812: */
0813: private String getClassFilename(String classname) {
0814: return classname.replace('.', '/') + ".class";
0815: }
0816:
0817: protected Class defineClassFromData(File container,
0818: byte[] classData, String className) throws IOException {
0819:
0820: definePackage(container, className);
0821: return defineClass(className, classData, 0, classData.length,
0822: null); // Project.class.getProtectionDomain());
0823:
0824: }
0825:
0826: /**
0827: * Get the manifest from the given jar, if it is indeed a jar and it has a
0828: * manifest
0829: *
0830: * @param container the File from which a manifest is required.
0831: *
0832: * @return the jar's manifest or null is the container is not a jar or it
0833: * has no manifest.
0834: *
0835: * @exception IOException if the manifest cannot be read.
0836: */
0837: private Manifest getJarManifest(File container) throws IOException {
0838: if (container.isDirectory()) {
0839: return null;
0840: }
0841: JarFile jarFile = null;
0842: try {
0843: jarFile = new JarFile(container);
0844: return jarFile.getManifest();
0845: } finally {
0846: if (jarFile != null) {
0847: jarFile.close();
0848: }
0849: }
0850: }
0851:
0852: /**
0853: * Define the package information associated with a class.
0854: *
0855: * @param container the file containing the class definition.
0856: * @param className the class name of for which the package information
0857: * is to be determined.
0858: *
0859: * @exception IOException if the package information cannot be read from the
0860: * container.
0861: */
0862: protected void definePackage(File container, String className)
0863: throws IOException {
0864: int classIndex = className.lastIndexOf('.');
0865: if (classIndex == -1) {
0866: return;
0867: }
0868:
0869: String packageName = className.substring(0, classIndex);
0870: if (getPackage(packageName) != null) {
0871: // already defined
0872: return;
0873: }
0874:
0875: // define the package now
0876: Manifest manifest = getJarManifest(container);
0877:
0878: if (manifest == null) {
0879: definePackage(packageName, null, null, null, null, null,
0880: null, null);
0881: } else {
0882: definePackage(container, packageName, manifest);
0883: }
0884: }
0885:
0886: /**
0887: * Define the package information when the class comes from a
0888: * jar with a manifest
0889: *
0890: * @param container the jar file containing the manifest
0891: * @param packageName the name of the package being defined.
0892: * @param manifest the jar's manifest
0893: */
0894: protected void definePackage(File container, String packageName,
0895: Manifest manifest) {
0896: String sectionName = packageName.replace('.', '/') + "/";
0897:
0898: String specificationTitle = null;
0899: String specificationVendor = null;
0900: String specificationVersion = null;
0901: String implementationTitle = null;
0902: String implementationVendor = null;
0903: String implementationVersion = null;
0904: String sealedString = null;
0905: URL sealBase = null;
0906:
0907: Attributes sectionAttributes = manifest
0908: .getAttributes(sectionName);
0909: if (sectionAttributes != null) {
0910: specificationTitle = sectionAttributes
0911: .getValue(Attributes.Name.SPECIFICATION_TITLE);
0912: specificationVendor = sectionAttributes
0913: .getValue(Attributes.Name.SPECIFICATION_VENDOR);
0914: specificationVersion = sectionAttributes
0915: .getValue(Attributes.Name.SPECIFICATION_VERSION);
0916: implementationTitle = sectionAttributes
0917: .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
0918: implementationVendor = sectionAttributes
0919: .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
0920: implementationVersion = sectionAttributes
0921: .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
0922: sealedString = sectionAttributes
0923: .getValue(Attributes.Name.SEALED);
0924: }
0925:
0926: Attributes mainAttributes = manifest.getMainAttributes();
0927: if (mainAttributes != null) {
0928: if (specificationTitle == null) {
0929: specificationTitle = mainAttributes
0930: .getValue(Attributes.Name.SPECIFICATION_TITLE);
0931: }
0932: if (specificationVendor == null) {
0933: specificationVendor = mainAttributes
0934: .getValue(Attributes.Name.SPECIFICATION_VENDOR);
0935: }
0936: if (specificationVersion == null) {
0937: specificationVersion = mainAttributes
0938: .getValue(Attributes.Name.SPECIFICATION_VERSION);
0939: }
0940: if (implementationTitle == null) {
0941: implementationTitle = mainAttributes
0942: .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
0943: }
0944: if (implementationVendor == null) {
0945: implementationVendor = mainAttributes
0946: .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
0947: }
0948: if (implementationVersion == null) {
0949: implementationVersion = mainAttributes
0950: .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
0951: }
0952: if (sealedString == null) {
0953: sealedString = mainAttributes
0954: .getValue(Attributes.Name.SEALED);
0955: }
0956: }
0957:
0958: if (sealedString != null
0959: && sealedString.equalsIgnoreCase("true")) {
0960: try {
0961: sealBase = new URL("file:" + container.getPath());
0962: } catch (MalformedURLException e) {
0963: // ignore
0964: }
0965: }
0966:
0967: definePackage(packageName, specificationTitle,
0968: specificationVersion, specificationVendor,
0969: implementationTitle, implementationVersion,
0970: implementationVendor, sealBase);
0971: }
0972:
0973: /**
0974: * Reads a class definition from a stream.
0975: *
0976: * @param stream The stream from which the class is to be read.
0977: * Must not be <code>null</code>.
0978: * @param classname The name of the class in the stream.
0979: * Must not be <code>null</code>.
0980: * @param container the file or directory containing the class.
0981: *
0982: * @return the Class object read from the stream.
0983: *
0984: * @exception IOException if there is a problem reading the class from the
0985: * stream.
0986: * @exception SecurityException if there is a security problem while
0987: * reading the class from the stream.
0988: */
0989: private Class getClassFromStream(InputStream stream,
0990: String classname, File container) throws IOException,
0991: SecurityException {
0992: ByteArrayOutputStream baos = new ByteArrayOutputStream();
0993: int bytesRead;
0994: byte[] buffer = new byte[BUFFER_SIZE];
0995:
0996: while ((bytesRead = stream.read(buffer, 0, BUFFER_SIZE)) != -1) {
0997: baos.write(buffer, 0, bytesRead);
0998: }
0999:
1000: byte[] classData = baos.toByteArray();
1001: return defineClassFromData(container, classData, classname);
1002: }
1003:
1004: /**
1005: * Searches for and load a class on the classpath of this class loader.
1006: *
1007: * @param name The name of the class to be loaded. Must not be
1008: * <code>null</code>.
1009: *
1010: * @return the required Class object
1011: *
1012: * @exception ClassNotFoundException if the requested class does not exist
1013: * on this loader's classpath.
1014: */
1015: public Class findClass(String name) throws ClassNotFoundException {
1016: logger.finer("Finding class " + name);
1017:
1018: return findClassInComponents(name);
1019: }
1020:
1021: /**
1022: * Indicate if the given file is in this loader's path
1023: *
1024: * @param component the file which is to be checked
1025: *
1026: * @return true if the file is in the class path
1027: */
1028: protected boolean isInPath(File component) {
1029: for (Enumeration e = pathComponents.elements(); e
1030: .hasMoreElements();) {
1031: File pathComponent = (File) e.nextElement();
1032: if (pathComponent.equals(component)) {
1033: return true;
1034: }
1035: }
1036: return false;
1037: }
1038:
1039: /**
1040: * Finds a class on the given classpath.
1041: *
1042: * @param name The name of the class to be loaded. Must not be
1043: * <code>null</code>.
1044: *
1045: * @return the required Class object
1046: *
1047: * @exception ClassNotFoundException if the requested class does not exist
1048: * on this loader's classpath.
1049: */
1050: private Class findClassInComponents(String name)
1051: throws ClassNotFoundException {
1052: // we need to search the components of the path to see if
1053: // we can find the class we want.
1054: InputStream stream = null;
1055: String classFilename = getClassFilename(name);
1056: try {
1057: Enumeration e = pathComponents.elements();
1058: while (e.hasMoreElements()) {
1059: File pathComponent = (File) e.nextElement();
1060: try {
1061: stream = getResourceStream(pathComponent,
1062: classFilename);
1063: if (stream != null) {
1064: logger.finer("Loaded from " + pathComponent
1065: + " " + classFilename);
1066: return getClassFromStream(stream, name,
1067: pathComponent);
1068: }
1069: } catch (SecurityException se) {
1070: throw se;
1071: } catch (IOException ioe) {
1072: // ioe.printStackTrace();
1073: logger.fine("Exception reading component "
1074: + pathComponent + " (reason: "
1075: + ioe.getMessage() + ")");
1076: }
1077: }
1078:
1079: throw new ClassNotFoundException(name);
1080: } finally {
1081: try {
1082: if (stream != null) {
1083: stream.close();
1084: }
1085: } catch (IOException e) {
1086: //ignore
1087: }
1088: }
1089: }
1090:
1091: /**
1092: * Finds a system class (which should be loaded from the same classloader
1093: * as the Ant core).
1094: *
1095: * For JDK 1.1 compatibility, this uses the findSystemClass method if
1096: * no parent classloader has been specified.
1097: *
1098: * @param name The name of the class to be loaded.
1099: * Must not be <code>null</code>.
1100: *
1101: * @return the required Class object
1102: *
1103: * @exception ClassNotFoundException if the requested class does not exist
1104: * on this loader's classpath.
1105: */
1106: private Class findBaseClass(String name)
1107: throws ClassNotFoundException {
1108: if (parent == null) {
1109: return findSystemClass(name);
1110: } else {
1111: return parent.loadClass(name);
1112: }
1113: }
1114:
1115: /**
1116: * Cleans up any resources held by this classloader. Any open archive
1117: * files are closed.
1118: */
1119: public synchronized void cleanup() {
1120: for (Enumeration e = zipFiles.elements(); e.hasMoreElements();) {
1121: ZipFile zipFile = (ZipFile) e.nextElement();
1122: try {
1123: zipFile.close();
1124: } catch (IOException ioe) {
1125: // ignore
1126: }
1127: }
1128: zipFiles = new Hashtable<File, ZipFile>();
1129: }
1130:
1131: /** Static map of jar file/time to manifiest class-path entries */
1132: private static Map<String, String> pathMap = Collections
1133: .synchronizedMap(new HashMap<String, String>());
1134: }
|