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.drjava.model.repl.newjvm;
038:
039: import java.util.LinkedList;
040: import java.util.ListIterator;
041: import java.util.ArrayList;
042: import java.util.Hashtable;
043: import java.util.Enumeration;
044: import java.util.List;
045: import java.util.Set;
046: import java.util.LinkedHashSet;
047: import java.io.*;
048:
049: import java.rmi.*;
050:
051: // NOTE: Do NOT import/use the config framework in this class!
052: // (This class runs in a different JVM, and will not share the config object)
053:
054: import edu.rice.cs.util.Log;
055: import edu.rice.cs.util.OutputStreamRedirector;
056: import edu.rice.cs.util.InputStreamRedirector;
057: import edu.rice.cs.util.StringOps;
058: import edu.rice.cs.util.UnexpectedException;
059: import edu.rice.cs.util.classloader.ClassFileError;
060: import edu.rice.cs.util.newjvm.*;
061: import edu.rice.cs.plt.iter.IterUtil;
062:
063: import edu.rice.cs.drjava.platform.PlatformFactory;
064: import edu.rice.cs.drjava.model.junit.JUnitModelCallback;
065: import edu.rice.cs.drjava.model.junit.JUnitTestManager;
066: import edu.rice.cs.drjava.model.junit.JUnitError;
067: import edu.rice.cs.drjava.model.repl.*;
068:
069: // For Windows focus fix
070: import javax.swing.JDialog;
071:
072: import koala.dynamicjava.parser.wrapper.*;
073: import koala.dynamicjava.parser.*;
074:
075: /** This is the main class for the interpreter JVM. All public methods except those involving remote calls (callbacks)
076: * synchronized (unless synchronization has no effect). This class is loaded in the Interpreter JVM, not the Main JVM.
077: * (Do not use DrJava's config framework here.)
078: * <p>
079: * Note that this class is specific to DynamicJava. It must be refactored to accommodate other interpreters.
080: * @version $Id: InterpreterJVM.java 4255 2007-08-28 19:17:37Z mgricken $
081: */
082: public class InterpreterJVM extends AbstractSlaveJVM implements
083: InterpreterJVMRemoteI, JUnitModelCallback {
084:
085: /** Singleton instance of this class. */
086: public static final InterpreterJVM ONLY = newInterpreterJVM();
087:
088: private static final Log _log = new Log("MasterSlave.txt", false);
089: private static final boolean printMessages = false;
090:
091: /** String to append to error messages when no stack trace is available. */
092: public static final String EMPTY_TRACE_TEXT = "";
093:
094: /** Metadata encapsulating the default interpreter. */
095: private final InterpreterData _defaultInterpreter;
096:
097: /** Maps names to interpreters with metadata. */
098: private final Hashtable<String, InterpreterData> _interpreters;
099:
100: /** The currently accumulated classpath for all Java interpreters. List contains unqiue entries. */
101: private final Set<File> _classPath;
102:
103: /** Responsible for running JUnit tests in this JVM. */
104: private final JUnitTestManager _junitTestManager;
105:
106: /** manages the classpath for all of DrJava */
107: private final ClassPathManager _classPathManager;
108:
109: /** Remote reference to the MainJVM class in DrJava's primary JVM. Assigned ONLY once. */
110: private volatile MainJVMRemoteI _mainJVM;
111:
112: /** The current interpreter. */
113: private volatile InterpreterData _activeInterpreter;
114:
115: // /** Busy flag. Used to prevent multiple interpretations from running simultaneously. */
116: // private volatile boolean interpretationInProgress = false;
117:
118: /** Interactions processor, currently a pre-processor **/
119: // private InteractionsProcessorI _interactionsProcessor;
120: /** Whether to display an error message if a reset fails. */
121: private volatile boolean _messageOnResetFailure;
122:
123: /** Private constructor; use the singleton ONLY instance. */
124: private InterpreterJVM() throws RemoteException {
125:
126: _classPath = new LinkedHashSet<File>();
127: _classPathManager = new ClassPathManager();
128: _defaultInterpreter = new InterpreterData(
129: new DynamicJavaAdapter(_classPathManager));
130: _interpreters = new Hashtable<String, InterpreterData>();
131: _junitTestManager = new JUnitTestManager(this );
132: _messageOnResetFailure = true;
133:
134: // _interactionsProcessor = new InteractionsProcessor();
135:
136: _quitSlaveThreadName = "Reset Interactions Thread";
137: _pollMasterThreadName = "Poll DrJava Thread";
138: _activeInterpreter = _defaultInterpreter;
139:
140: try {
141: _activeInterpreter.getInterpreter().interpret("0");
142: } catch (ExceptionReturnedException e) {
143: throw new edu.rice.cs.util.UnexpectedException(e);
144: }
145: }
146:
147: private static InterpreterJVM newInterpreterJVM() {
148: try {
149: return new InterpreterJVM();
150: } catch (Exception e) {
151: throw new UnexpectedException(e);
152: }
153: }
154:
155: private static void _dialog(String s) {
156: //javax.swing.JOptionPane.showMessageDialog(null, s);
157: _log.log(s);
158: }
159:
160: /** Actions to perform when this JVM is started (through its superclass, AbstractSlaveJVM). Contract from superclass
161: * mandates that this code does not synchronized on this across a remote call. This method has no synchronization
162: * because it can only be called once (part of the superclass contract) and _mainJVM is only assigned (once!) here. */
163: protected void handleStart(MasterRemote mainJVM) {
164: //_dialog("handleStart");
165: _mainJVM = (MainJVMRemoteI) mainJVM;
166:
167: // redirect stdin
168: System.setIn(new InputStreamRedirector() {
169: protected String _getInput() { // NOT synchronized on InterpreterJVM.this. _mainJVM is immutable.
170: try {
171: String s = _mainJVM.getConsoleInput();
172: // System.err.println("InterpreterJVM.getConsoleInput() = '" + s + "'");
173: return s;
174: } catch (RemoteException re) {
175: // blow up if no MainJVM found
176: _log.log("System.in: " + re.toString());
177: throw new IllegalStateException(
178: "Main JVM can't be reached for input.\n"
179: + re);
180: }
181: }
182: });
183:
184: // redirect stdout
185: System.setOut(new PrintStream(new OutputStreamRedirector() {
186: public void print(String s) { // NOT synchronized on InterpreterJVM.this. _mainJVM is immutable.
187: try {
188: //_log.logTime("out.print: " + s);
189: _mainJVM.systemOutPrint(s);
190: } catch (RemoteException re) {
191: // nothing to do
192: _log.log("System.out: " + re.toString());
193: }
194: }
195: }));
196:
197: // redirect stderr
198: System.setErr(new PrintStream(new OutputStreamRedirector() {
199: public void print(String s) { // NOT synchronized on InterpreterJVM.this. _mainJVM is immutable.
200: try {
201: //_log.logTime("err.print: " + s);
202: _mainJVM.systemErrPrint(s);
203: } catch (RemoteException re) {
204: // nothing to do
205: _log.log("System.err: " + re.toString());
206: }
207: }
208: }));
209:
210: /* On Windows, any frame or dialog opened from Interactions pane will appear *behind* DrJava's frame, unless a
211: * previous frame or dialog is shown here. Not sure what the difference is, but this hack seems to work. (I'd
212: * be happy to find a better solution, though.) Only necessary on Windows, since frames and dialogs on other
213: * platforms appear correctly in front of DrJava. */
214: if (PlatformFactory.ONLY.isWindowsPlatform()) {
215: JDialog d = new JDialog();
216: d.setSize(0, 0);
217: d.setVisible(true);
218: d.setVisible(false);
219: }
220: //_dialog("interpreter JVM started");
221: }
222:
223: /** Interprets the given string of source code in the active interpreter. The result is returned to MainJVM via
224: * the interpretResult method.
225: * @param s Source code to interpret.
226: */
227: public void interpret(String s) {
228: interpret(s, _activeInterpreter);
229: }
230:
231: /** Interprets the given string of source code with the given interpreter. The result is returned to MainJVM via
232: * the interpretResult method.
233: * @param s Source code to interpret.
234: * @param interpreterName Name of the interpreter to use
235: * @throws IllegalArgumentException if the named interpreter does not exist
236: */
237: public void interpret(String s, String interpreterName) {
238: interpret(s, getInterpreter(interpreterName));
239: }
240:
241: /** Interprets the given string of source code with the given interpreter. The result is returned to MainJVM via
242: * the interpretResult method. Not synchronized on this!
243: * @param input Source code to interpret.
244: * @param interpreter The interpreter (plus metadata) to use
245: */
246: public void interpret(final String input,
247: final InterpreterData interpreter) {
248: _log.log(this + ".interpret(" + input + ") called");
249: try {
250: synchronized (interpreter) {
251: if (interpreter.inProgress()) {
252: _mainJVM.interpretResult(new InterpreterBusy());
253: return;
254: }
255: // interpretationInProgress = true;
256: interpreter.setInProgress(true); // records that a given interpreter is in progress (used by debugger?)
257: }
258: // The following code is NOT synchronized on this. Mutual exclusion is guaranteed by preceding synchronized block.
259: // Utilities.showDebug("InterpreterJVM.interpret(" + input + ", ...) called");
260: Thread thread = new Thread("interpret thread: " + input) {
261: public void run() {
262: String s = input;
263: try { // Delimiting a catch for RemoteExceptions that might be thrown in catch clauses of enclosed try
264: try {
265: _log.log("Interpreter thread for " + input
266: + " has started");
267: // _dialog("to interp: " + s);
268:
269: // Utilities.showDebug("Preparing to invoke interpret method on " + s);
270: Object result = interpreter
271: .getInterpreter().interpret(s);
272: String resultString = String
273: .valueOf(result);
274: // Utilities.showDebug("Result string is: " + resultString);
275:
276: if (result == Interpreter.NO_RESULT) {
277: //return new VoidResult();
278: //_dialog("void interp ret: " + resultString);
279: _mainJVM
280: .interpretResult(new VoidResult());
281: } else {
282: // we use String.valueOf because it deals with result = null!
283: //_dialog("about to tell main result was " + resultString);
284: //return new ValueResult(resultString);
285: String style = InteractionsDocument.OBJECT_RETURN_STYLE;
286: if (result instanceof String) {
287: style = InteractionsDocument.STRING_RETURN_STYLE;
288: //Single quotes have already been added to chars by now, so they are read as strings
289: String possibleChar = (String) result;
290:
291: if (possibleChar.startsWith("\'")
292: && possibleChar
293: .endsWith("\'")
294: && possibleChar.length() == 3)
295: style = InteractionsDocument.CHARACTER_RETURN_STYLE;
296: }
297: if (result instanceof Number)
298: style = InteractionsDocument.NUMBER_RETURN_STYLE;
299: _mainJVM
300: .interpretResult(new ValueResult(
301: resultString, style));
302: }
303: } catch (ExceptionReturnedException e) {
304: Throwable t = e.getContainedException();
305: // Utilities.showStackTrace(t);
306: _dialog("interp exception: " + t);
307: // TODO: replace the following if ladder by dynamic dispatch. Create a visitor for DynamicJava errors?
308: if (t instanceof ParseException)
309: _mainJVM
310: .interpretResult(new SyntaxErrorResult(
311: (ParseException) t,
312: input));
313: else if (t instanceof TokenMgrError)
314: _mainJVM
315: .interpretResult(new SyntaxErrorResult(
316: (TokenMgrError) t,
317: input));
318: else if (t instanceof ParseError)
319: _mainJVM
320: .interpretResult(new SyntaxErrorResult(
321: (ParseError) t, input));
322: else {
323: //Other exceptions are non lexical/parse related exceptions. These include arithmetic exceptions,
324: //wrong version exceptions, etc.
325:
326: _mainJVM
327: .interpretResult(new ExceptionResult(
328: t.getClass().getName(),
329: t.getMessage(),
330: InterpreterJVM
331: .getStackTrace(t),
332: null));
333: }
334: } catch (Throwable t) {
335: // A user's toString method might throw anything, so we need to be careful
336: _dialog("irregular interp exception: " + t);
337: // Utilities.showStackTrace(t);
338: String shortMsg = null;
339: if ((t instanceof ParseError)
340: && ((ParseError) t)
341: .getParseException() != null)
342: shortMsg = ((ParseError) t)
343: .getMessage(); // in this case, getMessage is equivalent to getShortMessage
344: _mainJVM
345: .interpretResult(new ExceptionResult(
346: t.getClass().getName(), t
347: .getMessage(),
348: InterpreterJVM
349: .getStackTrace(t),
350: shortMsg));
351: }
352: } catch (RemoteException re) { /* MainJVM no longer accessible. Cannot recover. */
353: _log.log("MainJVM.interpret threw "
354: + re.toString());
355: }
356: }
357: }; // end of Thread definition
358:
359: thread.setDaemon(true);
360: thread.start();
361: } // end of interpretation block including synchronized prelude
362: catch (RemoteException re) { /* MainJVM not accessible. Cannot recover. */
363: _log.log("MainJVM.interpret threw" + re.toString());
364: } finally { // fields are volatile so no synchronization is necessary
365: // interpretationInProgress = false;
366: interpreter.setInProgress(false);
367: }
368: }
369:
370: private static String _processReturnValue(Object o) {
371: if (o instanceof String)
372: return "\"" + o + "\"";
373: if (o instanceof Character)
374: return "'" + o + "'";
375: return o.toString();
376: }
377:
378: /** Gets the string representation of the value of a variable in the current interpreter.
379: * @param var the name of the variable
380: * @return null if the variable is not defined, "null" if the value is null, or else its string representation
381: */
382: public synchronized String getVariableToString(String var)
383: throws RemoteException {
384: // Add to the default interpreter, if it is a JavaInterpreter
385: Interpreter i = _activeInterpreter.getInterpreter();
386: if (i instanceof JavaInterpreter) {
387: try {
388: Object value = ((JavaInterpreter) i).getVariable(var);
389: if (value == null)
390: return "null";
391: if (value instanceof koala.dynamicjava.interpreter.UninitializedObject)
392: return null;
393: return _processReturnValue(value);
394: } catch (IllegalStateException e) {
395: return null;
396: } // variable was not defined
397: }
398: return null;
399: }
400:
401: /** Gets the class name of a variable in the current interpreter.
402: * @param var the name of the variable
403: */
404: public synchronized String getVariableClassName(String var)
405: throws RemoteException {
406: // Add to the default interpreter, if it is a JavaInterpreter
407: Interpreter i = _activeInterpreter.getInterpreter();
408: if (i instanceof JavaInterpreter) {
409: try {
410: Class c = ((JavaInterpreter) i).getVariableClass(var);
411: if (c == null)
412: return "null";
413: else
414: return c.getName();
415: } catch (IllegalStateException e) {
416: // variable was not defined
417: return null;
418: }
419: } else
420: return null;
421: }
422:
423: /** Adds a named DynamicJavaAdapter to list of interpreters. Presets it to contain the current accumulated classpath.
424: * @param name the unique name for the interpreter
425: * @throws IllegalArgumentException if the name is not unique
426: */
427: public synchronized void addJavaInterpreter(String name) {
428: JavaInterpreter interpreter = new DynamicJavaAdapter(
429: _classPathManager);
430: // Add each entry on the accumulated classpath
431: _updateInterpreterClassPath(interpreter);
432: addInterpreter(name, interpreter);
433: }
434:
435: /** Adds a named JavaDebugInterpreter to the list of interpreters.
436: * @param name the unique name for the interpreter
437: * @param className the fully qualified class name of the class the debug interpreter is in
438: * @throws IllegalArgumentException if the name is not unique
439: */
440: public synchronized void addDebugInterpreter(String name,
441: String className) {
442: JavaDebugInterpreter interpreter = new JavaDebugInterpreter(
443: name, className);
444: interpreter.setPrivateAccessible(true);
445: // Add each entry on the accumulated classpath
446: _updateInterpreterClassPath(interpreter);
447: addInterpreter(name, interpreter);
448: }
449:
450: /** Adds a named interpreter to the list of interpreters.
451: * @param name the unique name for the interpreter
452: * @param interpreter the interpreter to add
453: * @throws IllegalArgumentException if the name is not unique
454: */
455: public synchronized void addInterpreter(String name,
456: Interpreter interpreter) {
457: if (_interpreters.containsKey(name)) {
458: throw new IllegalArgumentException("'" + name
459: + "' is not a unique interpreter name");
460: }
461: _interpreters.put(name, new InterpreterData(interpreter));
462: }
463:
464: /** Removes the interpreter with the given name, if it exists. Unsynchronized because _interpreters is immutable
465: * and its methods are thread-safe.
466: * @param name Name of the interpreter to remove
467: */
468: public void removeInterpreter(String name) {
469: _interpreters.remove(name);
470: }
471:
472: /** Returns the interpreter (with metadata) with the given name
473: * @param name the unique name of the desired interpreter
474: * @throws IllegalArgumentException if no such named interpreter exists
475: */
476: InterpreterData getInterpreter(String name) {
477: InterpreterData interpreter = _interpreters.get(name);
478: if (interpreter != null)
479: return interpreter;
480: else
481: throw new IllegalArgumentException("Interpreter '" + name
482: + "' does not exist.");
483: }
484:
485: /** Returns the Java interpreter with the given name
486: * @param name the unique name of the desired interpreter
487: * @throws IllegalArgumentException if no such named interpreter exists, or if the named interpreter is not a Java
488: * interpreter
489: */
490: public synchronized JavaInterpreter getJavaInterpreter(String name) {
491: if (printMessages)
492: System.out.println("Getting interpreter data");
493: InterpreterData interpreterData = getInterpreter(name);
494: if (printMessages)
495: System.out.println("Getting interpreter instance");
496: Interpreter interpreter = interpreterData.getInterpreter();
497: if (printMessages)
498: System.out.println("returning");
499:
500: if (interpreter instanceof JavaInterpreter)
501: return (JavaInterpreter) interpreter;
502: else {
503: throw new IllegalArgumentException("Interpreter '" + name
504: + "' is not a JavaInterpreter.");
505: }
506: }
507:
508: /** Sets the current interpreter to be the one specified by the given name
509: * @param name the unique name of the interpreter to set active
510: * @return Whether the new interpreter is currently in progress with an interaction
511: */
512: public synchronized boolean setActiveInterpreter(String name) {
513: _activeInterpreter = getInterpreter(name);
514: return _activeInterpreter.inProgress();
515: }
516:
517: /** Sets the default interpreter to be active.
518: * @return Whether the new interpreter is currently in progress with an interaction
519: */
520: public synchronized boolean setToDefaultInterpreter() {
521: _activeInterpreter = _defaultInterpreter;
522: return _activeInterpreter.inProgress();
523: }
524:
525: /** Gets the hashtable containing the named interpreters. Package private for testing purposes.
526: * @return said hashtable
527: */
528: Hashtable<String, InterpreterData> getInterpreters() {
529: return _interpreters;
530: }
531:
532: /** Returns the current active interpreter. Package private; for tests only. */
533: Interpreter getActiveInterpreter() {
534: return _activeInterpreter.getInterpreter();
535: }
536:
537: /** Gets the stack trace from the given exception, stripping off the bottom parts of the trace that are internal
538: * to the interpreter. This would be much easier to do in JDK 1.4, since you can get the stack trace frames
539: * directly, instead of having to parse this! TODO: revise this code to use the JDK 1.4+ API.
540: */
541: public static String getStackTrace(Throwable t) {
542: //_dialog("before creating reader");
543: BufferedReader reader = new BufferedReader(new StringReader(
544: StringOps.getStackTrace(t)));
545:
546: //_dialog("after creating reader");
547: LinkedList<String> traceItems = new LinkedList<String>();
548: try {
549: // we will generate list of trace items
550: // skip the first one since it's just the message
551: //_dialog("before first readLine");
552: reader.readLine();
553: //_dialog("after first readLine");
554:
555: String s;
556: while ((s = reader.readLine()) != null) {
557: //_dialog("read: " + s);
558: traceItems.add(s);
559: }
560: } catch (IOException ioe) {
561: return "Unable to get stack trace";
562: }
563:
564: // OK, now we crop off everything after the first "koala.dynamicjava." or "edu.rice.cs.drjava.", if there is one.
565:
566: // First, find the index of an occurrence.
567: int index = -1;
568: for (int i = 0; i < traceItems.size(); i++) {
569: String item = traceItems.get(i);
570: item = item.trim();
571: if (item.startsWith("at edu.rice.cs.drjava.")
572: || item.startsWith("at koala.dynamicjava.")) {
573: index = i;
574: break;
575: }
576: }
577:
578: // Now crop off the rest
579: if (index > -1) {
580: while (traceItems.size() > index)
581: traceItems.removeLast();
582: }
583:
584: // Last check: See if there are no items left. If there are none, put one in to say it happened at top-level.
585: if (traceItems.isEmpty())
586: traceItems.add(EMPTY_TRACE_TEXT);
587:
588: // OK, now rebuild string
589: final StringBuilder buf = new StringBuilder();
590: final ListIterator itor = traceItems.listIterator();
591: final String newLine = StringOps.EOL; // intended for output to system? (as opposed to Swing text)
592: boolean first = true;
593: while (itor.hasNext()) {
594: if (first)
595: first = false;
596: else
597: buf.append(newLine);
598:
599: buf.append(" " + ((String) itor.next()).trim());
600: }
601:
602: return buf.toString();
603: }
604:
605: // ---------- Java-specific methods ----------
606:
607: /** Sets the package scope for the current active interpreter, if it is a JavaInterpreter. */
608: public void setPackageScope(String s) {
609: Interpreter active = _activeInterpreter.getInterpreter();
610: if (active instanceof JavaInterpreter) {
611: ((JavaInterpreter) active).setPackageScope(s);
612: }
613: }
614:
615: /** @param show Whether to show a message if a reset operation fails. */
616: public void setShowMessageOnResetFailure(boolean show) {
617: _messageOnResetFailure = show;
618: }
619:
620: /** This method is called if the interpreterJVM cannot be exited (likely because of a modified security manager. */
621: protected void quitFailed(Throwable th) { // NOT synchronized
622: if (_messageOnResetFailure) {
623: String msg = "The interactions pane could not be reset:\n"
624: + th;
625: javax.swing.JOptionPane.showMessageDialog(null, msg);
626: }
627:
628: try {
629: _mainJVM.quitFailed(th);
630: } catch (RemoteException re) {
631: // nothing to do
632: _log.log("quitFailed: " + re.toString());
633: }
634: }
635:
636: /** Sets the interpreter to allow access to private members. */
637: public synchronized void setPrivateAccessible(boolean allow) {
638: Interpreter active = _activeInterpreter.getInterpreter();
639: if (active instanceof JavaInterpreter) {
640: ((JavaInterpreter) active).setPrivateAccessible(allow);
641: }
642: }
643:
644: // ---------- JUnit methods ----------
645: /** Sets up a JUnit test suite in the Interpreter JVM and finds which classes are really TestCases classes (by
646: * loading them). Unsynchronized because it contains a remote call and does not involve mutable local state.
647: * @param classNames the class names to run in a test
648: * @param files the associated file
649: * @return the class names that are actually test cases
650: */
651: public List<String> findTestClasses(List<String> classNames,
652: List<File> files) throws RemoteException {
653: return _junitTestManager.findTestClasses(classNames, files);
654: }
655:
656: /** Runs JUnit test suite already cached in the Interpreter JVM. Unsynchronized because it contains a remote call
657: * and does not involve mutable local state.
658: * @return false if no test suite is cached; true otherwise
659: */
660: public boolean runTestSuite() throws RemoteException {
661: return _junitTestManager.runTestSuite();
662: }
663:
664: /** Notifies Main JVM that JUnit has been invoked on a non TestCase class. Unsynchronized because it contains a
665: * remote call and does not involve mutable local state.
666: * @param isTestAll whether or not it was a use of the test all button
667: */
668: public void nonTestCase(boolean isTestAll) {
669: try {
670: _mainJVM.nonTestCase(isTestAll);
671: } catch (RemoteException re) {
672: // nothing to do
673: _log.log("nonTestCase: " + re.toString());
674: }
675: }
676:
677: /** Notifies the main JVM that JUnitTestManager has encountered an illegal class file. Unsynchronized because it
678: * contains a remote call and does not involve mutable local state.
679: * @param e the ClassFileError object describing the error on loading the file
680: */
681: public void classFileError(ClassFileError e) {
682: try {
683: _mainJVM.classFileError(e);
684: } catch (RemoteException re) {
685: // nothing to do
686: _log.log("classFileError: " + re.toString());
687: }
688: }
689:
690: /** Notifies that a suite of tests has started running. Unsynchronized because it contains a remote call and does
691: * not involve mutable local state.
692: * @param numTests The number of tests in the suite to be run.
693: */
694: public void testSuiteStarted(int numTests) {
695: try {
696: _mainJVM.testSuiteStarted(numTests);
697: } catch (RemoteException re) {
698: // nothing to do
699: _log.log("testSuiteStarted: " + re.toString());
700: }
701: }
702:
703: /** Notifies that a particular test has started. Unsynchronized because it contains a remote call and does not
704: * involve mutable local state.
705: * @param testName The name of the test being started.
706: */
707: public void testStarted(String testName) {
708: try {
709: _mainJVM.testStarted(testName);
710: } catch (RemoteException re) {
711: // nothing to do
712: _log.log("testStarted" + re.toString());
713: }
714: }
715:
716: /** Notifies that a particular test has ended. Unsynchronized because it contains a remote call.
717: * @param testName The name of the test that has ended.
718: * @param wasSuccessful Whether the test passed or not.
719: * @param causedError If not successful, whether the test caused an error or simply failed.
720: */
721: public void testEnded(String testName, boolean wasSuccessful,
722: boolean causedError) {
723: try {
724: _mainJVM.testEnded(testName, wasSuccessful, causedError);
725: } catch (RemoteException re) {
726: // nothing to do
727: _log.log("testEnded: " + re.toString());
728: }
729: }
730:
731: /** Notifies that a full suite of tests has finished running. Unsynchronized because it contains a remote call
732: * and does not involve mutable local state.
733: * @param errors The array of errors from all failed tests in the suite.
734: */
735: public void testSuiteEnded(JUnitError[] errors) {
736: try {
737: _mainJVM.testSuiteEnded(errors);
738: } catch (RemoteException re) {
739: // nothing to do
740: _log.log("testSuiteFinished: " + re.toString());
741: }
742: }
743:
744: /** Called when the JUnitTestManager wants to open a file that is not currently open. Unsynchronized because it
745: * contains a remote call and does not involve mutable local state.
746: * @param className the name of the class for which we want to find the file
747: * @return the file associated with the given class
748: */
749: public File getFileForClassName(String className) {
750: try {
751: return _mainJVM.getFileForClassName(className);
752: } catch (RemoteException re) {
753: // nothing to do
754: _log.log("getFileForClassName: " + re.toString());
755: return null;
756: }
757: }
758:
759: public void junitJVMReady() {
760: }
761:
762: //////////////////////////////////////////////////////////////
763: // ALL functions regarding classpath
764: //////////////////////////////////////////////////////////////
765:
766: /** Adds a classpath to the given interpreter. assumes that lock on this is held.
767: * @param interpreter the interpreter
768: */
769: protected/* synchronized */void _updateInterpreterClassPath(
770: JavaInterpreter interpreter) {
771:
772: for (File f : _classPathManager.getProjectCP())
773: interpreter.addProjectClassPath(f);
774:
775: for (File f : _classPathManager.getBuildDirectoryCP())
776: interpreter.addBuildDirectoryClassPath(f);
777:
778: for (File f : _classPathManager.getProjectFilesCP())
779: interpreter.addProjectFilesClassPath(f);
780:
781: for (File f : _classPathManager.getExternalFilesCP())
782: interpreter.addExternalFilesClassPath(f);
783:
784: for (File f : _classPathManager.getExtraCP())
785: interpreter.addExtraClassPath(f);
786: }
787:
788: /** Adds the given path to the classpath shared by ALL Java interpreters. Only unique paths are added.
789: * @param f Entry to add to the accumulated classpath
790: */
791: public synchronized void addExtraClassPath(File f) {
792: if (_classPath.contains(f))
793: return; // Don't add it again
794:
795: // Add to the default interpreter, if it is a JavaInterpreter
796: if (_defaultInterpreter.getInterpreter() instanceof JavaInterpreter) {
797: ((JavaInterpreter) _defaultInterpreter.getInterpreter())
798: .addExtraClassPath(f);
799: }
800:
801: // Add to any named JavaInterpreters to be consistent
802: Enumeration<InterpreterData> interpreters = _interpreters
803: .elements();
804: while (interpreters.hasMoreElements()) {
805: Interpreter interpreter = interpreters.nextElement()
806: .getInterpreter();
807: if (interpreter instanceof JavaInterpreter) {
808: ((JavaInterpreter) interpreter).addExtraClassPath(f);
809: }
810: }
811:
812: // Keep this entry on the accumulated classpath
813: _classPath.add(f);
814: }
815:
816: /** Adds the given file to the classpath shared by ALL Java interpreters. Only unique paths are added.
817: * @param f Entry to add to the accumulated classpath
818: */
819: public synchronized void addProjectClassPath(File f) {
820: if (_classPath.contains(f))
821: return; // Don't add it again
822:
823: // Add to the default interpreter, if it is a JavaInterpreter
824: if (_defaultInterpreter.getInterpreter() instanceof JavaInterpreter) {
825: ((JavaInterpreter) _defaultInterpreter.getInterpreter())
826: .addProjectClassPath(f);
827: }
828:
829: // Add to any named JavaInterpreters to be consistent
830: Enumeration<InterpreterData> interpreters = _interpreters
831: .elements();
832: while (interpreters.hasMoreElements()) {
833: Interpreter interpreter = interpreters.nextElement()
834: .getInterpreter();
835: if (interpreter instanceof JavaInterpreter) {
836: ((JavaInterpreter) interpreter).addProjectClassPath(f);
837: }
838: }
839:
840: // Keep this entry on the accumulated classpath
841: _classPath.add(f);
842: }
843:
844: /** Adds the given path to the classpath shared by ALL Java interpreters. Only unique paths are added.
845: * @param f Entry to add to the accumulated classpath
846: */
847: public synchronized void addBuildDirectoryClassPath(File f) {
848: if (_classPath.contains(f))
849: return; // Don't add it again
850:
851: // Add to the default interpreter, if it is a JavaInterpreter
852: if (_defaultInterpreter.getInterpreter() instanceof JavaInterpreter) {
853: ((JavaInterpreter) _defaultInterpreter.getInterpreter())
854: .addBuildDirectoryClassPath(f);
855: }
856:
857: // Add to any named JavaInterpreters to be consistent
858: Enumeration<InterpreterData> interpreters = _interpreters
859: .elements();
860: while (interpreters.hasMoreElements()) {
861: Interpreter interpreter = interpreters.nextElement()
862: .getInterpreter();
863: if (interpreter instanceof JavaInterpreter) {
864: ((JavaInterpreter) interpreter)
865: .addBuildDirectoryClassPath(f);
866: }
867: }
868:
869: // Keep this entry on the accumulated classpath
870: _classPath.add(f);
871: }
872:
873: /** Adds the given path to the classpath shared by ALL Java interpreters. Only unique paths are added.
874: * @param f Entry to add to the accumulated classpath
875: */
876: public synchronized void addProjectFilesClassPath(File f) {
877: if (_classPath.contains(f))
878: return; // Don't add it again
879:
880: // Add to the default interpreter, if it is a JavaInterpreter
881: if (_defaultInterpreter.getInterpreter() instanceof JavaInterpreter) {
882: ((JavaInterpreter) _defaultInterpreter.getInterpreter())
883: .addProjectFilesClassPath(f);
884: }
885:
886: // Add to any named JavaInterpreters to be consistent
887: Enumeration<InterpreterData> interpreters = _interpreters
888: .elements();
889: while (interpreters.hasMoreElements()) {
890: Interpreter interpreter = interpreters.nextElement()
891: .getInterpreter();
892: if (interpreter instanceof JavaInterpreter) {
893: ((JavaInterpreter) interpreter)
894: .addProjectFilesClassPath(f);
895: }
896: }
897:
898: // Keep this entry on the accumulated classpath
899: _classPath.add(f);
900: }
901:
902: /** Adds the given path to the classpath shared by ALL Java interpreters. Only unique paths are added.
903: * @param f Entry to add to the accumulated classpath
904: */
905: public synchronized void addExternalFilesClassPath(File f) {
906: if (_classPath.contains(f))
907: return; // Don't add it again
908:
909: // Add to the default interpreter, if it is a JavaInterpreter
910: if (_defaultInterpreter.getInterpreter() instanceof JavaInterpreter) {
911: ((JavaInterpreter) _defaultInterpreter.getInterpreter())
912: .addExternalFilesClassPath(f);
913: }
914:
915: // Add to any named JavaInterpreters to be consistent
916: Enumeration<InterpreterData> interpreters = _interpreters
917: .elements();
918: while (interpreters.hasMoreElements()) {
919: Interpreter interpreter = interpreters.nextElement()
920: .getInterpreter();
921: if (interpreter instanceof JavaInterpreter) {
922: ((JavaInterpreter) interpreter)
923: .addExternalFilesClassPath(f);
924: }
925: }
926:
927: // Keep this entry on the accumulated classpath
928: _classPath.add(f);
929: }
930:
931: public synchronized Iterable<File> getClassPath() {
932: Iterable<File> result = IterUtil.empty();
933: result = IterUtil.compose(result, _classPathManager
934: .getProjectCP());
935: result = IterUtil.compose(result, _classPathManager
936: .getBuildDirectoryCP());
937: result = IterUtil.compose(result, _classPathManager
938: .getProjectFilesCP());
939: result = IterUtil.compose(result, _classPathManager
940: .getExternalFilesCP());
941: result = IterUtil.compose(result, _classPathManager
942: .getExtraCP());
943: return result;
944: }
945:
946: public List<File> getAugmentedClassPath() {
947: return IterUtil.asList(getClassPath());
948: }
949:
950: }
951:
952: /** Bookkeeping class to maintain information about each interpreter, such as whether it is currently in progress. */
953: class InterpreterData {
954: protected final Interpreter _interpreter;
955: protected volatile boolean _inProgress;
956:
957: InterpreterData(Interpreter interpreter) {
958: _interpreter = interpreter;
959: _inProgress = false;
960: }
961:
962: // The following methods do not need to be synchronized because they access or set volatile fields.
963:
964: /** Gets the interpreter. */
965: public Interpreter getInterpreter() {
966: return _interpreter;
967: }
968:
969: /** Returns whether this interpreter is currently in progress with an interaction. */
970: public boolean inProgress() {
971: return _inProgress;
972: }
973:
974: /** Sets whether this interpreter is currently in progress. */
975: public void setInProgress(boolean inProgress) {
976: _inProgress = inProgress;
977: }
978: }
|