0001: /*
0002: * TclClassLoader.java --
0003: *
0004: * Implements the Class Loader for dynamically loading
0005: * Tcl packages. When attempting to resolve and load a
0006: * new Package the loader looks in four places to find
0007: * the class. In order they are:
0008: *
0009: * 1) The unique cache, "classes", inside the TclClassLoader.
0010: * 2) Using the system class loader (via the context class loader).
0011: * 3) Any paths passed into the constructor via the pathList variable.
0012: * 4) Any path in the interps env(TCL_CLASSPATH) variable.
0013: *
0014: * The class will be found if it is any of the above paths
0015: * or if it is in a jar file located in one of the paths.
0016: *
0017: * TclClassLoader.java --
0018: *
0019: * A class that helps filter directory listings when
0020: * for jar/zip files during the class resolution stage.
0021: *
0022: * Copyright (c) 1997 by Sun Microsystems, Inc.
0023: *
0024: * See the file "license.terms" for information on usage and redistribution
0025: * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
0026: *
0027: * RCS: @(#) $Id: TclClassLoader.java,v 1.15 2006/09/11 20:45:30 mdejong Exp $
0028: */
0029:
0030: package tcl.lang;
0031:
0032: import java.util.*;
0033: import java.util.zip.*;
0034: import java.io.*;
0035: import java.net.URL;
0036:
0037: class TclClassLoader extends ClassLoader {
0038:
0039: // Cache of classes loaded by this class loader. Typically, a
0040: // class loader is defined on a per-interp basis, so this
0041: // will cache instances of class data for each access in
0042: // the interp. Different interpreters require different
0043: // caches since the same class name could be loaded from
0044: // two different locations in different interps.
0045:
0046: private HashMap class_cache = new HashMap();
0047:
0048: // Each instance can have a list of additional paths to search. This
0049: // needs to be stored on a per instance basis because classes may be
0050: // resolved at later times. classpath is passed into the constructor,
0051: // and loadpath is extracted from the env(TCL_CLASSPATH) interp variable.
0052:
0053: private String[] classpath = null;
0054: private String[] loadpath = null;
0055: private String cached_tclclasspath = null;
0056:
0057: // Used only for error reporting when something went wrong with a class
0058: // that was loaded out of a jar and we want to know which jar. Will
0059: // be null unless the last searched class was found in a jar.
0060:
0061: private String lastSearchedClassFile = null;
0062: private String lastSearchedJarFile = null;
0063:
0064: // Pointer to parent class loader. This value will never be null.
0065:
0066: private ClassLoader parent;
0067:
0068: // Pointer to interp, non-null when the value of env(TCL_CLASSPATH)
0069: // should be used and checked for updates.
0070:
0071: private Interp interp = null;
0072:
0073: /*
0074: *----------------------------------------------------------------------
0075: *
0076: * TclClassLoader --
0077: *
0078: * TclClassLoader searches a possible -classpath path and the
0079: * env(TCL_CLASSPATH) path for classes and resources to load.
0080: * A TclClassLoader is defined on a per-interp basis, if a
0081: * specific command needs to search additional paths then
0082: * that search is done in a TclClassLoader() that has
0083: * the interp class loader as a parent. Note that a TclClassLoader
0084: * will always have a non-null parent.
0085: *
0086: * The list of paths in pathList and env(TCL_CLASSPATH) can be relative
0087: * to the current interp dir. The full path names are resolved,
0088: * before they are stored.
0089: *
0090: * Results:
0091: * None.
0092: *
0093: * Side effects:
0094: * Creates a new TclClassLoader object.
0095: *
0096: *----------------------------------------------------------------------
0097: */
0098:
0099: TclClassLoader(Interp interp, // Used to get env(TCL_CLASSPATH) and current
0100: // working dir
0101: TclObject pathList, // List of additional paths to search
0102: ClassLoader parent) // parent ClassLoader
0103: {
0104: super (parent);
0105: if (parent == null) {
0106: throw new TclRuntimeError(
0107: "parent ClassLoader can't be null");
0108: }
0109: this .parent = parent;
0110: this .interp = interp;
0111: init(interp, pathList);
0112: }
0113:
0114: /*
0115: *----------------------------------------------------------------------
0116: *
0117: * init --
0118: *
0119: * TclClassLoader stores the values to classpath and env(TCL_CLASSPATH)
0120: * on a per object basis. This is necessary because classes
0121: * may not be loaded immediately, but classpath and loadpath
0122: * may change over time, or from object to to object.
0123: *
0124: * The list of paths in pathList and env(TCL_CLASSPATH) can be relative
0125: * to the current interp dir. The full path names are resolved,
0126: * before they are stored.
0127: *
0128: * Results:
0129: *
0130: *
0131: * Side effects:
0132: *
0133: *
0134: *----------------------------------------------------------------------
0135: */
0136:
0137: private void init(Interp interp, // Used to get env(TCL_CLASSPATH) and current
0138: // working dir
0139: TclObject pathList) // List of additional paths to search
0140: {
0141: TclObject[] elem;
0142: int i;
0143:
0144: try {
0145: boolean searchTclClasspath = true;
0146:
0147: // A TclClassLoader that is a child of the interp class loader
0148: // will search only on a passed in -classpath not env(TCL_CLASSPATH).
0149:
0150: if (parent instanceof TclClassLoader) {
0151: if (pathList == null) {
0152: throw new TclRuntimeError(
0153: "TclClassLoader is a child of the interp "
0154: + "class loader but it does not have a -classpath to search");
0155: }
0156: searchTclClasspath = false;
0157: }
0158:
0159: if (pathList != null) {
0160: elem = TclList.getElements(interp, pathList);
0161: classpath = new String[elem.length];
0162: for (i = 0; i < elem.length; i++) {
0163: classpath[i] = absolutePath(interp, elem[i]
0164: .toString());
0165: }
0166: }
0167:
0168: if (searchTclClasspath) {
0169: checkTclClasspath();
0170: }
0171: } catch (TclException e) {
0172: }
0173: }
0174:
0175: // Check the env(TCL_CLASSPATH) variable to see if it has changed since
0176: // the last invocation. In the init case, the loadpath array is null.
0177:
0178: private void checkTclClasspath() {
0179: final boolean debug = false;
0180: TclObject[] elems = null;
0181:
0182: if (debug) {
0183: System.out.println("checkTclClasspath()");
0184: }
0185:
0186: try {
0187: TclObject tobj = interp.getVar("env", "TCL_CLASSPATH",
0188: TCL.GLOBAL_ONLY);
0189: String current_tclclasspath = tobj.toString();
0190: if (debug) {
0191: System.out.println("current_tclclasspath is: "
0192: + current_tclclasspath);
0193: }
0194:
0195: // env(TCL_CLASSPATH) is set to ""
0196:
0197: if (current_tclclasspath.length() == 0) {
0198: if (debug) {
0199: System.out.println("env(TCL_CLASSPATH) is \"\"");
0200: }
0201:
0202: cached_tclclasspath = "";
0203: loadpath = null;
0204: return;
0205: }
0206:
0207: if (debug) {
0208: System.out.println("comparing \"" + cached_tclclasspath
0209: + "\" to \"" + current_tclclasspath + "\"");
0210: }
0211:
0212: if ((cached_tclclasspath == null)
0213: || !current_tclclasspath
0214: .equals(cached_tclclasspath)) {
0215: // env(TCL_CLASSPATH) has changed, reset cache and reparse
0216:
0217: if (debug) {
0218: System.out.println("resetting cache");
0219: }
0220:
0221: cached_tclclasspath = current_tclclasspath;
0222: elems = TclList.getElements(interp, tobj);
0223: }
0224: } catch (TclException e) {
0225: // env(TCL_CLASSPATH) not set
0226:
0227: if (debug) {
0228: System.out
0229: .println("env(TCL_CLASSPATH) not set, TclException was: "
0230: + e.getMessage());
0231: }
0232:
0233: interp.resetResult();
0234: cached_tclclasspath = null;
0235: loadpath = null;
0236: return;
0237: }
0238:
0239: if (elems == null) {
0240: // env(TCL_CLASSPATH) is the same value it was before
0241:
0242: if (debug) {
0243: System.out.println("env(TCL_CLASSPATH) unchanged");
0244: }
0245: return;
0246: } else {
0247: // env(TCL_CLASSPATH) was changed, reparse path
0248:
0249: if (debug) {
0250: System.out
0251: .println("env(TCL_CLASSPATH) changed, reparsing");
0252: }
0253:
0254: loadpath = new String[elems.length];
0255: for (int i = 0; i < elems.length; i++) {
0256: loadpath[i] = absolutePath(interp, elems[i].toString());
0257: }
0258: }
0259: }
0260:
0261: /*
0262: *----------------------------------------------------------------------
0263: *
0264: * loadClass --
0265: *
0266: * Resolves the specified name to a Class. This method differs
0267: * from the regular loadClass(String) because it always passes a
0268: * true resolveIt argument to loadClass(String, boolean).
0269: *
0270: * Results:
0271: * the resolved Class, or null if it was not found.
0272: *
0273: * Side effects:
0274: * ClassNotFoundException if the class loader cannot find
0275: * a definition for the class.
0276: *
0277: *----------------------------------------------------------------------
0278: */
0279:
0280: public Class loadClass(String className) // The name of the desired Class.
0281: throws ClassNotFoundException, // The class could not be found.
0282: PackageNameException // The class is in the java or tcl package
0283: // but it could not be loaded by system loader.
0284: {
0285: return loadClass(className, true);
0286: }
0287:
0288: /*
0289: *----------------------------------------------------------------------
0290: *
0291: * loadClass --
0292: *
0293: * Resolves the specified name to a Class. The method loadClass() is
0294: * called by the JavaLoadCmd and via Interp.loadClass().
0295: *
0296: * Results:
0297: * the resolved Class, or null if it was not found.
0298: *
0299: * Side effects:
0300: * ClassNotFoundException if the class loader cannot find
0301: * a definition for the class.
0302: *
0303: *----------------------------------------------------------------------
0304: */
0305:
0306: protected Class loadClass(String className, // The name of the desired Class.
0307: boolean resolveIt) // If true, then resolve all referenced classes.
0308: throws ClassNotFoundException, // The class could not be found.
0309: PackageNameException, // The classes package starts with java or tcl prefix.
0310: SecurityException // If something goes terribly wrong in defineClass().
0311: {
0312: Class result; // The Class that is loaded.
0313: byte[] classData = null; // The bytes that compose the class file.
0314:
0315: final boolean debug = false;
0316: final boolean printStack = false;
0317:
0318: if (debug) {
0319: System.out.println("loadClass " + className);
0320: }
0321:
0322: // Check our local cache of classes
0323:
0324: result = (Class) class_cache.get(className);
0325: if (result != null) {
0326: if (debug) {
0327: System.out.println("found class_cache entry for key "
0328: + className);
0329: }
0330: return result;
0331: } else {
0332: if (debug) {
0333: System.out.println("no class_cache entry for key "
0334: + className);
0335: }
0336: }
0337:
0338: // Resolve with parent ClassLoader to see if it can resolve the class.
0339: // The parent could be the system class loader, a thread context
0340: // class loader, or the TclClassLoader for a specific interp.
0341:
0342: try {
0343: if (debug) {
0344: if (parent == getSystemClassLoader()) {
0345: System.out
0346: .println("Parent ClassLoader is SystemClassLoader");
0347: } else if (parent == Thread.currentThread()
0348: .getContextClassLoader()) {
0349: System.out
0350: .println("Parent ClassLoader is ContextClassLoader");
0351: } else if (parent instanceof TclClassLoader) {
0352: System.out
0353: .println("Parent ClassLoader is interp TclClassLoader");
0354: } else {
0355: System.out.println("Parent ClassLoader is of type "
0356: + parent.getClass().toString());
0357: }
0358: System.out.println("parent attempting load of class \""
0359: + className + "\"");
0360: }
0361:
0362: result = Class.forName(className, resolveIt, parent);
0363:
0364: // Don't cache classes resolved by a parent class loader, we assume the
0365: // parent will do any needed caching.
0366:
0367: if (debug) {
0368: System.out.println("parent load worked for class \""
0369: + className + "\"");
0370: }
0371:
0372: return result;
0373: } catch (ClassNotFoundException e) {
0374: if (printStack) {
0375: e.printStackTrace(System.err);
0376: }
0377: } catch (IllegalArgumentException e) {
0378: if (printStack) {
0379: e.printStackTrace(System.err);
0380: }
0381: } catch (NoClassDefFoundError e) {
0382: if (printStack) {
0383: e.printStackTrace(System.err);
0384: }
0385: } catch (IncompatibleClassChangeError e) {
0386: if (printStack) {
0387: e.printStackTrace(System.err);
0388: }
0389: }
0390:
0391: if (debug) {
0392: System.out.println("parent load did not work for class \""
0393: + className + "\"");
0394: }
0395:
0396: // Protect against attempts to load a class that contains the 'java'
0397: // or 'tcl' prefix, but is not in the corresponding file structure.
0398:
0399: if ((className.startsWith("java."))
0400: || (className.startsWith("tcl."))) {
0401: throw new PackageNameException(
0402: "Java loader failed to load the class "
0403: + "and the TclClassLoader is not permitted to "
0404: + "load classes in the tcl or java package at runtime, "
0405: + "check your CLASSPATH.", className);
0406: }
0407:
0408: if (debug) {
0409: System.out
0410: .println("TclClassLoader attempting search for class \""
0411: + className + "\"");
0412: if (classpath != null) {
0413: System.out.println("classpath is defined");
0414: } else {
0415: System.out.println("classpath is null");
0416: }
0417: if (loadpath != null) {
0418: System.out.println("loadpath is defined");
0419: } else {
0420: System.out.println("loadpath is null");
0421: }
0422:
0423: }
0424:
0425: // Try to load class from -classpath if it exists
0426:
0427: if (classpath != null) {
0428: classData = getClassFromPath(classpath, className);
0429: }
0430: if (classData == null) {
0431: // -classpath does not exists or class was not found.
0432: // Check to see if env(TCL_CLASSPATH) was changed
0433: // since the last lookup and then search it for
0434: // possible matches.
0435:
0436: checkTclClasspath();
0437: if (loadpath != null) {
0438: classData = getClassFromPath(loadpath, className);
0439: }
0440: }
0441:
0442: if (classData == null) {
0443: throw new ClassNotFoundException(className);
0444: }
0445:
0446: // Define it (parse the class file)
0447:
0448: // we have to include this catch for java.lang.NoClassDefFoundError
0449: // because Sun seems to have changed the Spec for JDK 1.2
0450: try {
0451: result = defineClass(className, classData, 0,
0452: classData.length);
0453: } catch (NoClassDefFoundError err) {
0454: throw new ClassFormatError();
0455: } catch (ClassFormatError err) {
0456: // This exception can be generated when the className argument
0457: // does not match the actual name of the class. For instance
0458: // if we try to define Test.class with data from tester/Test.class
0459: // we will get this error. Sadly, there does not seem to be any
0460: // to find out the real name of the class without knowing the
0461: // format of the .class file and parsing it.
0462:
0463: StringBuffer buf = new StringBuffer(50);
0464: buf.append(err.getMessage());
0465: buf.append(". ");
0466: if (lastSearchedClassFile != null) {
0467: buf.append(lastSearchedClassFile);
0468: } else {
0469: buf.append(className);
0470: }
0471:
0472: if (lastSearchedJarFile != null) {
0473: buf.append(" loaded from ");
0474: buf.append(lastSearchedJarFile);
0475: }
0476:
0477: buf.append(": class name does not match");
0478: buf.append(" the name defined in the classfile");
0479:
0480: throw new ClassFormatError(buf.toString());
0481: }
0482:
0483: if (result == null) {
0484: throw new ClassFormatError();
0485: }
0486: if (resolveIt) {
0487: resolveClass(result);
0488: }
0489:
0490: // Store it in our local cache
0491:
0492: if (debug) {
0493: System.out.println("added class_cache entry for key "
0494: + className);
0495: }
0496:
0497: class_cache.put(className, result);
0498:
0499: return result;
0500: }
0501:
0502: /*
0503: *----------------------------------------------------------------------
0504: *
0505: * findResource --
0506: *
0507: * Resolves the specified resource name to a URL via
0508: * a search of env(TCL_CLASSPATH). This method is
0509: * invoked by getResource() when a resource has not
0510: * been found by the system loader or the parent loader.
0511: *
0512: * Results:
0513: * the resolved URL, or null if it was not found.
0514: *
0515: * Side effects:
0516: * None.
0517: *
0518: *----------------------------------------------------------------------
0519: */
0520:
0521: protected URL findResource(String resName) // The name of the desired resource.
0522: throws PackageNameException // In case resource starts with java or tcl prefix
0523: {
0524: final boolean debug = false;
0525: URL result = null;
0526:
0527: if (debug) {
0528: System.out.println("findResource " + resName);
0529: }
0530:
0531: // FIXME: Support for relative resources with dots between them
0532: // should be implemented and tested.
0533:
0534: // Only know how to resolve absolute resources via TCL_CLASSPATH.
0535:
0536: if (resName.length() == 0 || resName.charAt(0) != '/') {
0537: return null;
0538: }
0539: resName = resName.substring(1);
0540:
0541: // Can't load resources that start with "java/" or "tcl/",
0542: // these should have been resolved with the system loader.
0543:
0544: if ((resName.startsWith("java/"))
0545: || (resName.startsWith("tcl/"))) {
0546: throw new PackageNameException("Can't load resource \""
0547: + resName
0548: + "\" with java or tcl prefix via TCL_CLASSPATH",
0549: resName);
0550: }
0551:
0552: if (debug) {
0553: System.out
0554: .println("TclClassLoader attempting search for resource \""
0555: + resName + "\"");
0556: if (classpath != null) {
0557: System.out.println("classpath is defined");
0558: } else {
0559: System.out.println("classpath is null");
0560: }
0561: if (loadpath != null) {
0562: System.out.println("loadpath is defined");
0563: } else {
0564: System.out.println("loadpath is null");
0565: }
0566: }
0567:
0568: // Try to load URL from -classpath if it exists
0569:
0570: if (classpath != null) {
0571: result = getURLFromPath(classpath, resName);
0572: }
0573: if (result == null) {
0574: // -classpath does not exists or class was not found.
0575: // Check to see if env(TCL_CLASSPATH) was changed
0576: // since the last lookup and then search it for
0577: // possible matches.
0578:
0579: checkTclClasspath();
0580: if (loadpath != null) {
0581: result = getURLFromPath(loadpath, resName);
0582: }
0583: }
0584:
0585: return result;
0586:
0587: // FIXME: May also need to overload findResources() in order to
0588: // get the proper resource loading WRT parents working. This
0589: // would need to return a whole mess of URL objects, not sure
0590: // how wasteful that is.
0591: }
0592:
0593: /*
0594: *----------------------------------------------------------------------
0595: *
0596: * getResource --
0597: *
0598: * Attempt to resolve a resource using the parent class loader
0599: * and then the tcl class loader. This method seems to be
0600: * needed because the JDK is not behaving the way it should
0601: * as it is not loading resources from the parent.
0602: *
0603: * Results:
0604: * the resolved URL, or null if it was not found.
0605: *
0606: * Side effects:
0607: * None.
0608: *
0609: *----------------------------------------------------------------------
0610: */
0611:
0612: public URL getResource(String resName) // The name of the desired resource.
0613: {
0614: final boolean debug = false;
0615:
0616: URL res = null;
0617:
0618: if (debug) {
0619: System.out.println("TclClassLoader.getResource(): "
0620: + resName);
0621:
0622: if (parent == getSystemClassLoader()) {
0623: System.out.println("parent is SystemClassLoader");
0624: }
0625: if (Interp.class.getClassLoader() == getSystemClassLoader()) {
0626: System.out
0627: .println("interp loader is SystemClassLoader");
0628: }
0629: }
0630:
0631: // Resource searching is kind of tricky. Calling Class.getResource()
0632: // will search in the jar that the class was loaded from. This does
0633: // not appear to be very well documented and was found only via
0634: // testing the runtime impl. Note that this is not the same as
0635: // calling Interp.class.getClassLoader().getResource() which
0636: // will not search in the jar from the CLASSPATH.
0637:
0638: if (res == null) {
0639: // Search in tcljava.jar, jacl.jar, or tclblend.jar.
0640: res = Interp.class.getResource(resName);
0641:
0642: if (debug) {
0643: if (res == null) {
0644: System.out
0645: .println("did not find resource with jar visibility");
0646: } else {
0647: System.out
0648: .println("found resource with jar visibility");
0649: }
0650: }
0651: }
0652:
0653: if (res == null) {
0654: // Search in parent class loader
0655: res = parent.getResource(resName);
0656:
0657: if (debug) {
0658: if (res == null) {
0659: System.out
0660: .println("did not find resource in parent");
0661: } else {
0662: System.out.println("found resource in parent");
0663: }
0664: }
0665: }
0666:
0667: if (res == null) {
0668: // Search on env(TCL_CLASSPATH)
0669: res = findResource(resName);
0670:
0671: if (debug) {
0672: if (res == null) {
0673: System.out
0674: .println("did not find resource in tcl class loader");
0675: } else {
0676: System.out
0677: .println("found resource in tcl class loader");
0678: }
0679: }
0680: }
0681:
0682: return res;
0683: }
0684:
0685: /*
0686: *----------------------------------------------------------------------
0687: *
0688: * defineClass --
0689: *
0690: * Given an array of bytes that define a class, create the Class.
0691: * If the className is null, we are creating a lambda class.
0692: * Otherwise cache the className and definition in the loaders
0693: * cache.
0694: *
0695: * Results:
0696: * A Class object or null if it could not be defined.
0697: *
0698: * Side effects:
0699: * Cache the Class object in the classes Hashtable.
0700: *
0701: *----------------------------------------------------------------------
0702: */
0703:
0704: Class defineClass(String className, // Name of the class, possibly null.
0705: byte[] classData) // Binary data of the class structure.
0706: {
0707: Class result = null; // The Class object defined by classData.
0708:
0709: // Create a class from the array of bytes
0710:
0711: try {
0712: result = defineClass(null, classData, 0, classData.length);
0713: } catch (ClassFormatError ex) {
0714: // Don't allow this exception to terminate execution, but
0715: // print some debug info to stderr since this will likely
0716: // be cause by a compiler bug and we want to know about that.
0717:
0718: System.err.println("TclClassLoader.defineClass():");
0719: System.err.println(ex.getClass().getName() + ": "
0720: + ex.getMessage());
0721: } catch (LinkageError ex) {
0722: // Don't allow this exception to terminate execution, but
0723: // print some debug info to stderr since this will likely
0724: // be cause by a compiler bug and we want to know about that.
0725:
0726: System.err.println("TclClassLoader.defineClass():");
0727: System.err.println(ex.getClass().getName() + ": "
0728: + ex.getMessage());
0729: }
0730:
0731: if (result != null) {
0732: // If the name of the class is null, extract the className
0733: // from the Class object, and use that as the key.
0734:
0735: if (className == null) {
0736: className = result.getName();
0737: }
0738:
0739: // If a class was created, then store the class
0740: // in the loaders cache.
0741:
0742: class_cache.put(className, result);
0743: }
0744:
0745: return (result);
0746: }
0747:
0748: /*
0749: *----------------------------------------------------------------------
0750: *
0751: * getClassFromPath --
0752: *
0753: * At this point, the class wasn't found in the cache or by the
0754: * parent loader. Search through 'classpath' list and the
0755: * Tcl environment TCL_CLASSPATH to see if the class file can be
0756: * found and resolved. If ".jar" or ".zip" files are found,
0757: * search them for the class as well.
0758: *
0759: * Results:
0760: * an array of bytes that is the content of the className
0761: * file. null is returned if the class could not be
0762: * found or resolved (e.g. permissions error).
0763: *
0764: * Side effects:
0765: * None.
0766: *
0767: *----------------------------------------------------------------------
0768: */
0769:
0770: private byte[] getClassFromPath(String[] paths, String className) // the name of the class trying to be resolved
0771: {
0772: final boolean debug = false;
0773: int i = 0;
0774: byte[] classData = null; // The bytes that compose the class file.
0775: String curDir; // The directory to search for the class file.
0776: File file; // The class file.
0777: int total; // Total number of bytes read from the stream
0778:
0779: if (debug) {
0780: System.out.println("getClassFromPath for " + className);
0781: }
0782:
0783: // Search through the list of "paths" for the className.
0784: // ".jar" or ".zip" files found in the path will also be
0785: // searched. Yhe first occurence found is returned.
0786: lastSearchedClassFile = null;
0787: lastSearchedJarFile = null;
0788:
0789: if (paths != null) {
0790: // When the class being loaded implements other classes that are
0791: // not yet loaded, the TclClassLoader will recursively call this
0792: // procedure. However the format of the class name is
0793: // foo.bar.name and it needs to be foo/bar/name. Convert to
0794: // proper format.
0795:
0796: className = className.replace('.', '/') + ".class";
0797:
0798: for (i = 0; i < paths.length; i++) {
0799: curDir = paths[i].toString();
0800: try {
0801: if ((curDir.endsWith(".jar"))
0802: || (curDir.endsWith(".zip"))) {
0803: // If curDir points to a jar file, search it
0804: // for the class. If classData is not null
0805: // then the class was found in the jar file.
0806:
0807: classData = extractClassFromJar(curDir,
0808: className);
0809: if (classData != null) {
0810: return (classData);
0811: }
0812: } else {
0813: // If curDir and className point to an existing file,
0814: // then the class is found. Extract the bytes from
0815: // the file.
0816:
0817: file = new File(curDir, className);
0818: if (file.exists()) {
0819: FileInputStream fi = new FileInputStream(
0820: file);
0821: classData = new byte[fi.available()];
0822:
0823: total = fi.read(classData);
0824: while (total != classData.length) {
0825: total += fi.read(classData, total,
0826: (classData.length - total));
0827:
0828: }
0829:
0830: // Set this so we can get the full name of the
0831: // file we loaded the class from later
0832: lastSearchedClassFile = file.toString();
0833:
0834: return (classData);
0835: }
0836: }
0837: } catch (Exception e) {
0838: // No error thrown, because the class may be found
0839: // in subsequent paths.
0840: }
0841: }
0842: for (i = 0; i < paths.length; i++) {
0843: curDir = paths[i].toString();
0844: try {
0845: // The class was not found in the paths list.
0846: // Search all the directories in paths for
0847: // any jar files, in an attempt to locate
0848: // the class inside a jar file.
0849:
0850: classData = getClassFromJar(curDir, className);
0851:
0852: if (classData != null) {
0853: return classData;
0854: }
0855: } catch (Exception e) {
0856: // No error thrown, because the class may be found
0857: // in subsequent paths.
0858: }
0859: }
0860: }
0861:
0862: // No matching classes found.
0863:
0864: return null;
0865: }
0866:
0867: /*
0868: *----------------------------------------------------------------------
0869: *
0870: * getClassFromJar --
0871: *
0872: * Given a directory and a class to be found, get a list of
0873: * ".jar" or ".zip" files in the current directory. Call
0874: * extractClassFromJar to search the Jar file and extract
0875: * the class if a match is found.
0876: *
0877: * Results:
0878: * An array of bytes that is the content of the className
0879: * file. null is returned if the class could not be
0880: * resolved or found.
0881: *
0882: * Side effects:
0883: * None.
0884: *
0885: *----------------------------------------------------------------------
0886: */
0887:
0888: private byte[] getClassFromJar(String curDir, // An absoulte path for a directory to search
0889: String className) // The name of the class to extract from the jar file.
0890: throws IOException {
0891: byte[] result = null; // The bytes that compose the class file.
0892: String[] jarFiles; // The list of files in the curDir.
0893: JarFilenameFilter jarFilter; // Filter the jarFiles list by only
0894: // accepting ".jar" or ".zip"
0895:
0896: File file = new File(curDir);
0897:
0898: if (!file.isDirectory()) {
0899: return null;
0900: }
0901:
0902: jarFilter = new JarFilenameFilter();
0903: jarFiles = file.list(jarFilter);
0904:
0905: if (jarFiles == null) {
0906: return null;
0907: }
0908:
0909: for (int i = 0; i < jarFiles.length; i++) {
0910: result = extractClassFromJar(curDir + File.separatorChar
0911: + jarFiles[i], className);
0912: if (result != null) {
0913: break;
0914: }
0915: }
0916: return result;
0917: }
0918:
0919: /*
0920: *----------------------------------------------------------------------
0921: *
0922: * extractClassFromJar --
0923: *
0924: * Look inside the jar file, jarName, for a ZipEntry that
0925: * matches the className. If a match is found extract the
0926: * bytes from the input stream.
0927: *
0928: * Results:
0929: * An array of bytes that is the content of the className
0930: * file. null is returned if the class could not be
0931: * resolved or found.
0932: *
0933: * Side effects:
0934: * None.
0935: *
0936: *----------------------------------------------------------------------
0937: */
0938:
0939: private byte[] extractClassFromJar(String jarName, // An absoulte path for a jar file to search.
0940: String className) // The name of the class to extract from the jar file.
0941: throws IOException {
0942: final boolean debug = false;
0943: ZipInputStream zin; // The jar file input stream.
0944: ZipEntry entry; // A file contained in the jar file.
0945: byte[] result; // The bytes that compose the class file.
0946: int size; // Uncompressed size of the class file.
0947: int total; // Number of bytes read from class file.
0948:
0949: if (debug) {
0950: System.out.println("searching for " + className + " in "
0951: + jarName);
0952: }
0953:
0954: zin = new ZipInputStream(new FileInputStream(jarName));
0955:
0956: try {
0957: while ((entry = zin.getNextEntry()) != null) {
0958: // see if the current ZipEntry's name equals
0959: // the file we want to extract. If equal
0960: // get the extract and return the contents of the file.
0961:
0962: if (className.equals(entry.getName())) {
0963: size = getEntrySize(jarName, className);
0964: result = new byte[size];
0965: total = zin.read(result);
0966: while (total != size) {
0967: total += zin
0968: .read(result, total, (size - total));
0969: }
0970:
0971: // Set these so we can determine which
0972: // Jar a class was extracted from later
0973: lastSearchedClassFile = className;
0974: lastSearchedJarFile = jarName;
0975:
0976: if (debug) {
0977: System.out.println("class " + className
0978: + " found in " + jarName);
0979: }
0980:
0981: return result;
0982: }
0983: }
0984: if (debug) {
0985: System.out.println("class " + className
0986: + " not found in " + jarName);
0987: }
0988: return null;
0989: } finally {
0990: zin.close();
0991: }
0992: }
0993:
0994: /*
0995: *----------------------------------------------------------------------
0996: *
0997: * getEntrySize --
0998: *
0999: * For some reason, using ZipInputStreams, the ZipEntry returned
1000: * by getNextEntry() doesn't contain a valid uncompressed size, so
1001: * there is no way to determine how much to read. Using the
1002: * ZipFile object will return useful values for the size, but
1003: * the inputStream returned doesn't work. The solution was to use
1004: * both methods to ultimtely extract the class, which results in
1005: * an order n^2 algorithm. Hopefully this will change...
1006: *
1007: * Results:
1008: * The size of the uncompressed class file.
1009: *
1010: * Side effects:
1011: * None.
1012: *
1013: *----------------------------------------------------------------------
1014: */
1015:
1016: private int getEntrySize(String jarName, String className)
1017: throws IOException {
1018: ZipEntry entry; // A file contained in the jar file.
1019: ZipFile zip; // Used to get the enum of ZipEntries.
1020: Enumeration e; // List of the contents of the jar file.
1021:
1022: zip = new ZipFile(jarName);
1023: e = zip.entries();
1024:
1025: while (e.hasMoreElements()) {
1026: // see if the current ZipEntry's
1027: // name equals the file we want to extract.
1028:
1029: entry = (ZipEntry) e.nextElement();
1030: if (className.equals(entry.getName())) {
1031: zip.close();
1032: return ((int) entry.getSize());
1033: }
1034: }
1035: return (-1);
1036: }
1037:
1038: /*
1039: *----------------------------------------------------------------------
1040: *
1041: * absolutePath --
1042: *
1043: * Given a String, construct a File object. If it is not an absoulte
1044: * path, then prepend the interps current working directory, to the
1045: * dirName.
1046: *
1047: * Results:
1048: * The absolute path of dirName
1049: *
1050: * Side effects:
1051: * None.
1052: *
1053: *----------------------------------------------------------------------
1054: */
1055:
1056: private static String absolutePath(Interp interp, // the current Interp
1057: String dirName) // name of directory to be qualified
1058: {
1059: File dir;
1060: String newName;
1061:
1062: dir = new File(dirName);
1063: if (!dir.isAbsolute()) {
1064: newName = interp.getWorkingDir().toString()
1065: + System.getProperty("file.separator") + dirName;
1066: dir = new File(newName);
1067: }
1068: return (dir.toString());
1069: }
1070:
1071: /*
1072: *----------------------------------------------------------------------
1073: *
1074: * removeCache --
1075: *
1076: * Remove the given className from the internal cache.
1077: *
1078: * Results:
1079: * |>None.<|
1080: *
1081: * Side effects:
1082: * |>None.<|
1083: *
1084: *----------------------------------------------------------------------
1085: */
1086:
1087: void removeCache(String className) {
1088: // The cache could contain the key in the case where the load
1089: // worked but the object could not be instantiated.
1090:
1091: class_cache.remove(className);
1092: }
1093:
1094: /*
1095: *----------------------------------------------------------------------
1096: *
1097: * getURLFromPath --
1098: *
1099: * Given a resource name like "/testext/cmd.tcl" loop over
1100: * the classpath elements looking for a match to this resource
1101: * name. If ".jar" or ".zip" files are found,
1102: * search them for the resource as well.
1103: *
1104: * Results:
1105: * A URL object.
1106: *
1107: * Side effects:
1108: * None.
1109: *
1110: *----------------------------------------------------------------------
1111: */
1112:
1113: private URL getURLFromPath(String[] paths, String resName) // the name of the resource trying to be resolved
1114: {
1115: final boolean debug = false;
1116: int i = 0;
1117: URL url = null;
1118: String curDir; // The directory to search for the class file.
1119: File file; // The class file.
1120: int total; // Total number of bytes read from the stream
1121:
1122: if (debug) {
1123: System.out.println("getURLFromPath for " + resName);
1124: }
1125:
1126: // Search through the list of "paths" for the resName.
1127: // ".jar" or ".zip" files found in the path will also be
1128: // searched. Yhe first occurence found is returned.
1129: lastSearchedClassFile = null;
1130: lastSearchedJarFile = null;
1131:
1132: if (paths != null) {
1133: for (i = 0; i < paths.length; i++) {
1134: curDir = paths[i].toString();
1135: try {
1136: if ((curDir.endsWith(".jar"))
1137: || (curDir.endsWith(".zip"))) {
1138: // If curDir points to a jar file, search it
1139: // for the class. If classData is not null
1140: // then the class was found in the jar file.
1141:
1142: url = extractURLFromJar(curDir, resName);
1143: if (url != null) {
1144: return (url);
1145: }
1146: } else {
1147: // If curDir and url point to an existing file,
1148: // then the resource is found.
1149:
1150: file = new File(curDir, resName);
1151: if (file.exists()) {
1152: url = file.toURL();
1153: return (url);
1154: }
1155: }
1156: } catch (Exception e) {
1157: // No error thrown, because the class may be found
1158: // in subsequent paths.
1159: }
1160: }
1161: for (i = 0; i < paths.length; i++) {
1162: curDir = paths[i].toString();
1163: try {
1164: // The resource was not found in the paths list.
1165: // Search all the directories in paths for
1166: // any jar files, in an attempt to locate
1167: // the class inside a jar file.
1168:
1169: url = getURLFromJar(curDir, resName);
1170: if (url != null) {
1171: return (url);
1172: }
1173: } catch (Exception e) {
1174: // No error thrown, because the resource may be found
1175: // in subsequent paths.
1176: }
1177: }
1178: }
1179:
1180: // No matching resource found.
1181:
1182: return null;
1183: }
1184:
1185: /*
1186: *----------------------------------------------------------------------
1187: *
1188: * getURLFromJar --
1189: *
1190: * Given a directory and a resource to be found, get a list of
1191: * ".jar" or ".zip" files in the current directory. Call
1192: * extractURLFromJar to search the Jar file and extract
1193: * the resource if a match is found.
1194: *
1195: * Results:
1196: * A URL or null.
1197: *
1198: * Side effects:
1199: * None.
1200: *
1201: *----------------------------------------------------------------------
1202: */
1203:
1204: private URL getURLFromJar(String curDir, // An absoulte path for a directory to search
1205: String resName) // The name of the resource to extract from the jar file.
1206: throws IOException {
1207: URL result = null;
1208: String[] jarFiles; // The list of files in the curDir.
1209: JarFilenameFilter jarFilter; // Filter the jarFiles list by only
1210: // accepting ".jar" or ".zip"
1211:
1212: jarFilter = new JarFilenameFilter();
1213: jarFiles = (new File(curDir)).list(jarFilter);
1214:
1215: for (int i = 0; i < jarFiles.length; i++) {
1216: result = extractURLFromJar(curDir + File.separatorChar
1217: + jarFiles[i], resName);
1218: if (result != null) {
1219: break;
1220: }
1221: }
1222: return (result);
1223: }
1224:
1225: /*
1226: *----------------------------------------------------------------------
1227: *
1228: * extractURLFromJar --
1229: *
1230: * Look inside the jar file, jarName, for a ZipEntry that
1231: * matches the resName. If a match is found then
1232: * generate a URL object.
1233: *
1234: * Results:
1235: * A URL object, or null.
1236: *
1237: * Side effects:
1238: * None.
1239: *
1240: *----------------------------------------------------------------------
1241: */
1242:
1243: private URL extractURLFromJar(String jarName, // An absoulte path for a jar file to search.
1244: String resName) // The name of the resource to extract from the jar file.
1245: throws IOException {
1246: final boolean debug = false;
1247: ZipInputStream zin; // The jar file input stream.
1248: ZipEntry entry; // A file contained in the jar file.
1249: URL result;
1250: int size; // Uncompressed size of the class file.
1251: int total; // Number of bytes read from class file.
1252:
1253: if (debug) {
1254: System.out.println("searching for " + resName + " in "
1255: + jarName);
1256: }
1257:
1258: zin = new ZipInputStream(new FileInputStream(jarName));
1259:
1260: try {
1261: while ((entry = zin.getNextEntry()) != null) {
1262: // see if the current ZipEntry's name equals
1263: // the file we want to extract. If equal
1264: // get the extract and return the contents of the file.
1265:
1266: if (debug) {
1267: System.out.println("comparing " + resName + " to "
1268: + entry.getName());
1269: }
1270:
1271: if (resName.equals(entry.getName())) {
1272: File file = new File(jarName);
1273: URL fileURL = file.toURL();
1274: URL jarURL = new URL("jar:" + fileURL.toString()
1275: + "!/" + resName);
1276: if (debug) {
1277: System.out.println("match found: file is "
1278: + file);
1279: System.out.println("fileURL is " + fileURL);
1280: System.out.println("jarURL is " + jarURL);
1281: }
1282: return jarURL;
1283: }
1284: }
1285: if (debug) {
1286: System.out.println("resource " + resName
1287: + " not found in " + jarName);
1288: }
1289: return null;
1290: } finally {
1291: zin.close();
1292: }
1293: }
1294:
1295: } // end TclClassLoader
1296:
1297: /*
1298: *
1299: * TclClassLoader.java --
1300: *
1301: * A class that helps filter directory listings when
1302: * for jar/zip files during the class resolution stage.
1303: *
1304: */
1305:
1306: class JarFilenameFilter implements FilenameFilter {
1307:
1308: /*
1309: *----------------------------------------------------------------------
1310: *
1311: * accept --
1312: *
1313: * Used by the getClassFromJar method. When list returns a list
1314: * of files in a directory, the list will only be of jar or zip
1315: * files.
1316: *
1317: * Results:
1318: * True if the file ends with .jar or .zip
1319: *
1320: * Side effects:
1321: * None.
1322: *
1323: *----------------------------------------------------------------------
1324: */
1325:
1326: public boolean accept(File dir, String name) {
1327: if (name.endsWith(".jar") || name.endsWith(".zip")) {
1328: return (true);
1329: } else {
1330: return (false);
1331: }
1332: }
1333:
1334: } // end JarFilenameFilter
|