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