001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.util.classloader;
038:
039: import java.util.Arrays;
040: import java.net.URL;
041: import java.io.IOException;
042: import java.io.InputStream;
043:
044: import edu.rice.cs.plt.io.IOUtil;
045: import edu.rice.cs.util.FileOps;
046:
047: /** A {@link ClassLoader} that works as the union of two classloaders, but always tries to delegate to the first of
048: * these. The purpose for this class is to ensure that classes loaded transitively due to some class's loading are
049: * also loaded with the right classloader. Here's the problem: Say that class A contains a reference to class B, but
050: * the specific B is unknown to clients of class A. Class A is loadable by the standard classloader, but class B needs
051: * to be loaded with a (known) custom classloader.
052: * <P>
053: * If A were loaded using the standard classloader, it would fail because this would cause the transitive loading of
054: * B to be done by the system loader as well. If A were loaded with the custom loader, the same thing would happen --
055: * the custom loader would delegate to the system loader to load A (since it doesn't load non-custom-loader-requiring
056: * classes), but this would associate class A with the system classloader. (Every class is associated with the loader
057: * that called {@link ClassLoader#defineClass} to define it in the JVM.) This association would make B be loaded by
058: * the standard loader!
059: * <P>
060: * To get around this problem, we use this class, which acts mostly as a union of two classloaders. The trick, however,
061: * is that the StickyClassLoader has itself associated with all classes it loads, even though the actual work is done
062: * by the two loaders it delegates to. (It does this by calling {@link ClassLoader#findResource} on the subordinate
063: * loaders to get the class data, but then by calling {@link ClassLoader#defineClass} itself to preserve the
064: * association.
065: *
066: * @version $Id: StickyClassLoader.java 4255 2007-08-28 19:17:37Z mgricken $
067: */
068: public class StickyClassLoader extends ClassLoader {
069: private final ClassLoader _newLoader;
070: private final String[] _classesToLoadWithOld;
071:
072: /** Creates a sticky class loader with the given primary and secondary loaders to join together. All classes will be
073: * attempted to be loaded with the primary loader, and the secondary will be used as a fallback.
074: * @param newLoader Primary loader
075: * @param oldLoader Secondary loader
076: */
077: public StickyClassLoader(final ClassLoader newLoader,
078: final ClassLoader oldLoader) {
079: this (newLoader, oldLoader, new String[0]);
080: }
081:
082: /** Creates a sticky class loader with the given primary and secondary loaders to join together. All classes will be
083: * attempted to be loaded with the primary loader
084: * (except for classes in <code>classesToLoadWithOld</code>),
085: * and the secondary will be used as a fallback.
086: *
087: * @param newLoader Primary loader
088: * @param oldLoader Secondary loader
089: * @param classesToLoadWithOld All class names in this array will be loaded only with the secondary classloader. This
090: * is vital to ensure that only one copy of some classes are loaded, since two differently loaded versions of
091: * a class act totally independently! (That is, they have different, incompatible types.) Often it'll be
092: * necessary to make key interfaces that are used between components get loaded via one standard classloader,
093: * to ensure that things can be cast to that interface.
094: */
095: public StickyClassLoader(final ClassLoader newLoader,
096: final ClassLoader oldLoader,
097: final String[] classesToLoadWithOld) {
098: super (oldLoader);
099: _newLoader = newLoader; // to be used only in getResource()!
100: _classesToLoadWithOld = new String[classesToLoadWithOld.length];
101: System.arraycopy(classesToLoadWithOld, 0,
102: _classesToLoadWithOld, 0, classesToLoadWithOld.length);
103: Arrays.sort(_classesToLoadWithOld);
104: }
105:
106: /** Gets the requested resource, looking first in the new loader and then in the old loader.
107: * @param name Name of resource to find
108: * @return URL of the resource if found/accessible, or null if not.
109: */
110: public URL getResource(String name) {
111: URL resource = _newLoader.getResource(name);
112: if (resource == null)
113: resource = getParent().getResource(name);
114:
115: //System.err.println("resource: " + name + " --> " + resource);
116: return resource;
117: }
118:
119: /** Loads the given class, delegating first to the new class loader and then second to the old class loader. The
120: * returned Class object will have its ClassLoader ({@link Class#getClassLoader}) set to be this. This is very
121: * important because it causes classes that are loaded due to this class being loaded (ancestor classes/interfaces,
122: * referenced classes, etc) to use the same loader. There are a few exceptions to this explanation:
123: * <OL>
124: * <LI>If the class is in java.* or javax.*, it will be loaded using {@link ClassLoader#getSystemClassLoader}. This
125: * is because only the system loader is allowed to load system classes! Also: sun.*.
126: * </LI>
127: * <LI>If the class name is in the list of classes to load with the old class loader (passed to constructor), the new
128: * loader is not considered when trying to load the class. This is useful to make sure that certain classes (or
129: * interfaces) only have one copy in the system, to ensure that you can cast to that
130: * class/interface regardless of which loader loaded the other class.
131: * </LI>
132: * </OL>
133: */
134: protected Class<?> loadClass(String name, boolean resolve)
135: throws ClassNotFoundException {
136: // check if it's already loaded in the JVM!
137: Class<?> clazz;
138: clazz = findLoadedClass(name);
139: if (clazz != null)
140: return clazz;
141:
142: if (name.startsWith("java.") || name.startsWith("javax.")
143: || name.startsWith("sun.")
144: || name.startsWith("com.sun.")
145: || name.startsWith("org.omg.")
146: || name.startsWith("sunw.")
147: || name.startsWith("org.w3c.dom.")
148: || name.startsWith("org.xml.sax.")
149: || name.startsWith("net.jini.")) {
150:
151: try {
152: clazz = getSystemClassLoader().loadClass(name);
153: } catch (ClassNotFoundException e) {
154: // It might be a non-system class, like javax.mail.*.
155: // Fall back on the secondary loader.
156: clazz = _loadWithSecondary(name);
157: }
158: } else if (Arrays.binarySearch(_classesToLoadWithOld, name) >= 0) {
159: // Don't fall back to secondary if this fails...
160: clazz = getParent().loadClass(name);
161: } else {
162: // Load with the secondary loader
163: clazz = _loadWithSecondary(name);
164: // If this fails don't fall back to loading with oldClassloader (or,
165: // equivalently, getParent()) cuz getResource() (called by _loadWithSecondary())
166: // calls getParent.getResource() if _newLoader.getResource() returns null
167: }
168:
169: if (resolve)
170: resolveClass(clazz);
171:
172: // System.out.println("Sticky loaded OK: " + name + " " + clazz + " loader=" + clazz.getClassLoader());
173: return clazz;
174: }
175:
176: /** Try to load the class with the given name with the secondary (new) loader. Uses getResource to find the class.
177: * @param name Name of the class to load.
178: */
179: protected Class _loadWithSecondary(String name)
180: throws ClassNotFoundException {
181: // we get the data using getResource because if we just delegate
182: // the call to loadClass on old or new loader, it will use that
183: // loader as the associated class loader for the class. that's bad.
184: // The method does not depend on the newLoader except indirectly
185: // in the call to getResource()
186: try {
187: String fileName = name.replace('.', '/') + ".class";
188:
189: URL resource = getResource(fileName); // only dependency on newLoader!
190: if (resource == null) {
191: throw new ClassNotFoundException("Resource not found: "
192: + fileName);
193: }
194:
195: InputStream in = resource.openStream();
196: try {
197: byte[] data = IOUtil.toByteArray(in);
198: return defineClass(name, data, 0, data.length);
199: } finally {
200: in.close();
201: }
202: } catch (IOException ioe) {
203: throw new ClassNotFoundException(ioe.toString());
204: }
205: }
206: }
|