001: package abbot.script;
002:
003: import java.awt.*;
004: import java.util.EmptyStackException;
005: import javax.swing.SwingUtilities;
006: import abbot.Log;
007: import abbot.i18n.Strings;
008: import abbot.util.*;
009:
010: /**
011: * A custom class loader which installs itself as if it were the application
012: * class loader. A classpath of null is equivalent to the system property
013: * java.class.path.<p>
014: * The class loader may optionally load a class <i>before</i> the parent class
015: * loader gets a chance to look for the class (instead of the default
016: * behavior, which always delegates to the parent class loader first). This
017: * behavior enables the class to be reloaded simply by using a new instance of
018: * this class loader with each launch of the app.<p>
019: * This class mimics the behavior of sun.misc.Launcher$AppClassLoader as much
020: * as possible.<p>
021: * Bootstrap classes are always delegated to the bootstrap loader, with the
022: * exception of the sun.applet package, which should never be delegated, since
023: * it does not work properly unless it is reloaded.<p>
024: * The parent of this class loader will be the normal, default AppClassLoader
025: * (specifically, the class loader which loaded this class will be used).
026: */
027: public class AppClassLoader extends NonDelegatingClassLoader {
028:
029: /** A new event queue installed for the lifetime of this class loader. */
030: private AppEventQueue eventQueue;
031: /** This class loader is used to load bootstrap classes that must be
032: * preloaded.
033: */
034: private BootstrapClassLoader bootstrapLoader;
035: /** For use in checking if a class is in the framework class path. */
036: private NonDelegatingClassLoader extensionsLoader;
037: /** Whether the framework itself is being tested. */
038: private boolean frameworkIsUnderTest = false;
039:
040: /** Old class loader context for the thread where this loader was
041: * installed.
042: */
043: private ClassLoader oldClassLoader = null;
044: private Thread installedThread = null;
045: private String oldClassPath = null;
046:
047: private class InstallationLock {
048: }
049:
050: private InstallationLock lock = new InstallationLock();
051:
052: /** Constructs an AppClassLoader using the current classpath (as found in
053: java.class.path).
054: */
055: public AppClassLoader() {
056: this (null);
057: }
058:
059: /**
060: * Constructs a AppClassLoader with a custom classpath, indicating
061: * whether the class loader should delegate to its parent class loader
062: * prior to searching for a given resource.<p>
063: * The class path argument may use either a colon or semicolon to separate
064: * its elements.<p>
065: */
066: public AppClassLoader(String classPath) {
067: super (classPath, AppClassLoader.class.getClassLoader());
068: bootstrapLoader = new BootstrapClassLoader();
069: // Use this one to look up extensions; we want to reload them, but may
070: // need to look in the framework class path for them.
071: // Make sure it *only* loads extensions, though, and defers all other
072: // lookups to its parent.
073: extensionsLoader = new NonDelegatingClassLoader(System
074: .getProperty("java.class.path"), AppClassLoader.class
075: .getClassLoader()) {
076: protected boolean shouldDelegate(String name) {
077: return !isExtension(name);
078: }
079: };
080: // Don't want to open a whole new can of class loading worms!
081: /*
082: // If we're testing the framework itself, then absolutely DO NOT
083: // delegate those classes.
084: if (getClassPath().indexOf("abbot.jar") != -1) {
085: frameworkIsUnderTest = true;
086: }
087: */
088: }
089:
090: public boolean isEventDispatchThread() {
091: return (eventQueue != null && Thread.currentThread() == eventQueue.thread)
092: || (eventQueue == null && SwingUtilities
093: .isEventDispatchThread());
094: }
095:
096: /** Should the parent class loader try to load this class first? */
097: // FIXME we should only need the delegate flag if stuff in the classpath
098: // is also found on the system classpath, e.g. the framework itself
099: // Maybe just set it internally in case the classpaths overlap?
100: protected boolean shouldDelegate(String name) {
101: return bootstrapLoader.shouldDelegate(name)
102: && !isExtension(name)
103: && !(frameworkIsUnderTest && isFrameworkClass(name));
104: }
105:
106: private boolean isFrameworkClass(String name) {
107: return name.startsWith("abbot.")
108: || name.startsWith("junit.extensions.abbot.");
109: }
110:
111: private boolean isExtension(String name) {
112: return name.startsWith("abbot.tester.extensions.")
113: || name.startsWith("abbot.script.parsers.extensions.");
114: }
115:
116: /**
117: * Finds and loads the class with the specified name from the search
118: * path. If the class is a bootstrap class and must be preloaded, use our
119: * own bootstrap loader. If it is an extension class, use our own
120: * extensions loader.
121: *
122: * @param name the name of the class
123: * @return the resulting class
124: * @exception ClassNotFoundException if the class could not be found
125: */
126: public Class findClass(String name) throws ClassNotFoundException {
127: if (isBootstrapClassRequiringReload(name)) {
128: try {
129: return bootstrapLoader.findClass(name);
130: } catch (ClassNotFoundException cnf) {
131: Log.warn(cnf);
132: }
133: }
134:
135: // Look for extensions first in the framework class path (with a
136: // special loader), then in the app class path.
137: // Extensions *must* have the same class loader as the corresponding
138: // custom components
139: try {
140: return super .findClass(name);
141: } catch (ClassNotFoundException cnf) {
142: if (isExtension(name)) {
143: return extensionsLoader.findClass(name);
144: }
145: throw cnf;
146: }
147: }
148:
149: /** Ensure that everything else subsequently loaded on the same thread or
150: * any subsequently spawned threads uses the given class loader. Also
151: * ensure that classes loaded by the event dispatch thread and threads it
152: * spawns use the given class loader.
153: */
154: public void install() {
155:
156: if (SwingUtilities.isEventDispatchThread()) {
157: throw new IllegalStateException(Strings
158: .get("appcl.invalid_state"));
159: }
160:
161: if (installedThread != null) {
162: String msg = Strings.get("appcl.already_installed",
163: new Object[] { installedThread });
164: throw new IllegalStateException(msg);
165: }
166:
167: // Change the effective classpath, but make sure it's available if
168: // someone needs to access it.
169: oldClassPath = System.getProperty("java.class.path");
170: System.setProperty("abbot.class.path", oldClassPath);
171: System.setProperty("java.class.path", getClassPath());
172: Log.debug("java.class.path set to "
173: + System.getProperty("java.class.path"));
174:
175: // Install our own handler for catching exceptions on the event
176: // dispatch thread.
177: try {
178: new EventExceptionHandler().install();
179: } catch (Exception e) {
180: // ignore any exceptions, since they're not fatal
181: }
182:
183: eventQueue = new AppEventQueue();
184: eventQueue.install();
185:
186: Thread current = Thread.currentThread();
187: installedThread = current;
188: oldClassLoader = installedThread.getContextClassLoader();
189: installedThread.setContextClassLoader(this );
190: }
191:
192: public boolean isInstalled() {
193: synchronized (lock) {
194: return eventQueue != null;
195: }
196: }
197:
198: /** Reverse the effects of install. Has no effect if the class loader
199: * has not been installed on any thread.
200: */
201: public void uninstall() {
202: // Ensure that no two threads attempt to uninstall
203: synchronized (lock) {
204: if (eventQueue != null) {
205: eventQueue.uninstall();
206: eventQueue = null;
207: }
208: if (installedThread != null) {
209: installedThread.setContextClassLoader(oldClassLoader);
210: oldClassLoader = null;
211: installedThread = null;
212: System.setProperty("java.class.path", oldClassPath);
213: oldClassPath = null;
214: }
215: }
216: }
217:
218: private class AppEventQueue extends EventQueue {
219:
220: private Thread thread;
221:
222: /** Ensure the class loader for the event dispatch thread is the right
223: one.
224: */
225: public void install() {
226: Runnable installer = new Runnable() {
227: public void run() {
228: Toolkit.getDefaultToolkit().getSystemEventQueue()
229: .push(AppEventQueue.this );
230: }
231: };
232: // Avoid deadlock with the event queue, in case it has the tree
233: // lock (pickens).
234: AWT.invokeAndWait(installer);
235: Runnable threadTagger = new Runnable() {
236: public void run() {
237: thread = Thread.currentThread();
238: thread.setContextClassLoader(AppClassLoader.this );
239: thread.setName(thread.getName()
240: + " (AppClassLoader)");
241: }
242: };
243: AWT.invokeAndWait(threadTagger);
244: }
245:
246: /** Pop this and any subsequently pushed event queues. */
247: public void uninstall() {
248: Log.debug("Uninstalling AppEventQueue");
249: try {
250: pop();
251: thread = null;
252: } catch (EmptyStackException ese) {
253: }
254: Log.debug("AppEventQueue uninstalled");
255: }
256:
257: public String toString() {
258: return "Abbot AUT Event Queue (thread=" + thread + ")";
259: }
260: }
261:
262: /** List of bootstrap classes we most definitely want to be loaded by this
263: * class loader, rather than any parent, or the bootstrap loader.
264: */
265: private String[] mustReloadPrefixes = { "sun.applet.", // need the whole package, not just AppletViewer/Main
266: };
267:
268: /** Does the given class absolutely need to be preloaded? */
269: private boolean isBootstrapClassRequiringReload(String name) {
270: for (int i = 0; i < mustReloadPrefixes.length; i++) {
271: if (name.startsWith(mustReloadPrefixes[i]))
272: return true;
273: }
274: return false;
275: }
276:
277: /** Returns the path to the primary JRE classes, not including any
278: * extensions. This is primarily needed for loading
279: * sun.applet.AppletViewer/Main, since most other classes in the bootstrap
280: * path should <i>only</i> be loaded by the bootstrap loader.
281: */
282: private static String getBootstrapPath() {
283: return System.getProperty("sun.boot.class.path");
284: }
285:
286: /** Provide access to bootstrap classes that we need to be able to
287: * reload.
288: */
289: private class BootstrapClassLoader extends NonDelegatingClassLoader {
290: public BootstrapClassLoader() {
291: super (getBootstrapPath(), null);
292: }
293:
294: protected boolean shouldDelegate(String name) {
295: // Exclude all bootstrap classes, except for those we know we
296: // *must* be reloaded on each run in order to have function
297: // properly (e.g. applet)
298: return !isBootstrapClassRequiringReload(name)
299: && !"abbot.script.AppletSecurityManager"
300: .equals(name);
301: }
302: }
303:
304: public String toString() {
305: return super .toString() + " (java.class.path="
306: + System.getProperty("java.class.path") + ")";
307: }
308: }
|