0001: /*BEGIN_COPYRIGHT_BLOCK
0002: *
0003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
0004: * All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions are met:
0008: * * Redistributions of source code must retain the above copyright
0009: * notice, this list of conditions and the following disclaimer.
0010: * * Redistributions in binary form must reproduce the above copyright
0011: * notice, this list of conditions and the following disclaimer in the
0012: * documentation and/or other materials provided with the distribution.
0013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
0014: * names of its contributors may be used to endorse or promote products
0015: * derived from this software without specific prior written permission.
0016: *
0017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0028: *
0029: * This software is Open Source Initiative approved Open Source Software.
0030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
0031: *
0032: * This file is part of DrJava. Download the current version of this project
0033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
0034: *
0035: * END_COPYRIGHT_BLOCK*/
0036:
0037: package edu.rice.cs.drjava.model.repl.newjvm;
0038:
0039: import java.rmi.*;
0040: import java.io.*;
0041: import java.net.MalformedURLException;
0042:
0043: import java.util.List;
0044: import java.util.ArrayList;
0045:
0046: // NOTE: Do NOT import/use the config framework in this class!
0047: // (It seems to crash Eclipse...)
0048: import edu.rice.cs.drjava.DrJava;
0049: import edu.rice.cs.drjava.config.OptionConstants;
0050: import edu.rice.cs.drjava.model.GlobalModel;
0051: import edu.rice.cs.drjava.model.repl.*;
0052: import edu.rice.cs.drjava.model.junit.JUnitError;
0053: import edu.rice.cs.drjava.model.junit.JUnitModelCallback;
0054: import edu.rice.cs.drjava.model.debug.DebugModelCallback;
0055:
0056: import edu.rice.cs.util.ArgumentTokenizer;
0057: import edu.rice.cs.util.FileOps;
0058: import edu.rice.cs.util.Log;
0059: import edu.rice.cs.util.StringOps;
0060: import edu.rice.cs.util.UnexpectedException;
0061: import edu.rice.cs.plt.io.IOUtil;
0062: import edu.rice.cs.plt.iter.IterUtil;
0063:
0064: import edu.rice.cs.util.newjvm.*;
0065: import edu.rice.cs.util.classloader.ClassFileError;
0066: import edu.rice.cs.util.swing.Utilities;
0067: import edu.rice.cs.util.swing.ScrollableDialog;
0068: import koala.dynamicjava.parser.wrapper.*;
0069:
0070: import static edu.rice.cs.plt.debug.DebugUtil.debug;
0071:
0072: /** Manages a remote JVM.
0073: * @version $Id: MainJVM.java 4255 2007-08-28 19:17:37Z mgricken $
0074: */
0075: public class MainJVM extends AbstractMasterJVM implements
0076: MainJVMRemoteI {
0077: /** Name of the class to use in the remote JVM. */
0078: private static final String SLAVE_CLASS_NAME = "edu.rice.cs.drjava.model.repl.newjvm.InterpreterJVM";
0079:
0080: public static final String DEFAULT_INTERPRETER_NAME = "DEFAULT";
0081:
0082: // _log is inherited from AbstractMasterJVM
0083:
0084: /** Working directory for slave JVM */
0085: private volatile File _workDir;
0086:
0087: /** Listens to interactions-related events. */
0088: private volatile InteractionsModelCallback _interactionsModel;
0089:
0090: /** Listens to JUnit-related events. */
0091: private volatile JUnitModelCallback _junitModel;
0092:
0093: /** Listens to debug-related events */
0094: private volatile DebugModelCallback _debugModel;
0095:
0096: /** Used to protect interpreterJVM setting */
0097: private final Object _interpreterLock = new Object();
0098:
0099: /** Records state of slaveJVM (interpreterJVM); used to suppress restartInteractions on a fresh JVM */
0100: private volatile boolean _slaveJVMUsed = false;
0101:
0102: /** This flag is set to false to inhibit the automatic restart of the JVM. */
0103: private volatile boolean _restart = true;
0104:
0105: /** This flag is set to remember that the JVM is cleanly restarting, so that the replCalledSystemExit method
0106: * does not need to be called.
0107: */
0108: private volatile boolean _cleanlyRestarting = false;
0109:
0110: /** Number of previous attempts to start slave JVM in this startup. */
0111: private volatile int _numAttempts = 0;
0112:
0113: /** Number of slave startup failures allowed before aborting the startup process. */
0114: private static final int MAX_COUNT = 3;
0115:
0116: /** Instance of inner class to handle interpret result. */
0117: private final ResultHandler _handler = new ResultHandler();
0118:
0119: /** Whether to allow "assert" statements to run in the remote JVM. */
0120: private volatile boolean _allowAssertions = false;
0121:
0122: /** Classpath to use for starting the interpreter JVM */
0123: private volatile Iterable<File> _startupClassPath;
0124:
0125: /** A list of user-defined arguments to pass to the interpreter. */
0126: private volatile List<String> _optionArgs;
0127:
0128: /** The name of the current interpreter. */
0129: private volatile String _currentInterpreterName = DEFAULT_INTERPRETER_NAME;
0130:
0131: /** Creates a new MainJVM to interface to another JVM; the MainJVM has a link to the partially initialized
0132: * global model. The MainJVM but does not automatically start the Interpreter JVM. Callers must set the
0133: * InteractionsModel and JUnitModel and then call startInterpreterJVM().
0134: */
0135: public MainJVM(File wd) throws RemoteException {
0136: super (SLAVE_CLASS_NAME);
0137: // Utilities.show("Starting the slave JVM");
0138: _workDir = wd;
0139: _waitForQuitThreadName = "Wait for Interactions to Exit Thread";
0140: // _exportMasterThreadName = "Export DrJava to RMI Thread";
0141:
0142: _interactionsModel = new DummyInteractionsModel();
0143: _junitModel = new DummyJUnitModel();
0144: _debugModel = new DummyDebugModel();
0145: _startupClassPath = GlobalModel.RUNTIME_CLASS_PATH;
0146: _optionArgs = new ArrayList<String>();
0147: }
0148:
0149: public boolean isInterpreterRunning() {
0150: return _interpreterJVM() != null;
0151: }
0152:
0153: public boolean slaveJVMUsed() {
0154: return _slaveJVMUsed;
0155: }
0156:
0157: /** Provides an object to listen to interactions-related events. */
0158: public void setInteractionsModel(InteractionsModelCallback model) {
0159: _interactionsModel = model;
0160: }
0161:
0162: /** Provides an object to listen to test-related events.*/
0163: public void setJUnitModel(JUnitModelCallback model) {
0164: _junitModel = model;
0165: }
0166:
0167: /** Provides an object to listen to debug-related events.
0168: * @param model the debug model
0169: */
0170: public void setDebugModel(DebugModelCallback model) {
0171: _debugModel = model;
0172: }
0173:
0174: /** Sets whether the remote JVM will run "assert" statements after the next restart. */
0175: public void setAllowAssertions(boolean allow) {
0176: _allowAssertions = allow;
0177: }
0178:
0179: /** Sets the extra (optional) arguments to be passed to the interpreter.
0180: * @param argString the arguments as they would be typed at the command-line
0181: */
0182: public void setOptionArgs(String argString) {
0183: _optionArgs = ArgumentTokenizer.tokenize(argString);
0184: }
0185:
0186: /** Interprets string s in slave JVM. No masterJVMLock synchronization because reading _restart is the only
0187: * access.to master JVM state. */
0188: public void interpret(final String s) {
0189: // silently fail if disabled. see killInterpreter docs for details.
0190: if (!_restart)
0191: return;
0192:
0193: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0194:
0195: // Spawn thread on InterpreterJVM side
0196: // (will receive result in the interpretResult(...) method)
0197: try {
0198: _log.log(this + ".interpret(" + s + ")");
0199: _slaveJVMUsed = true;
0200: _interactionsModel.slaveJVMUsed();
0201: slave.interpret(s);
0202: } catch (java.rmi.UnmarshalException ume) {
0203: // Could not receive result from interpret; system probably exited.
0204: // We will silently fail and let the interpreter restart.
0205: _log
0206: .log(this
0207: + ".interpret threw UnmarshalException, so interpreter is dead:\n"
0208: + ume);
0209: } catch (RemoteException re) {
0210: _threwException(re);
0211: }
0212: }
0213:
0214: /** Gets the string representation of the value of a variable in the current interpreter.
0215: * @param var the name of the variable
0216: */
0217: public String getVariableToString(String var) {
0218: // silently fail if disabled. see killInterpreter docs for details.
0219: if (!_restart)
0220: return null;
0221:
0222: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0223:
0224: try {
0225: return slave.getVariableToString(var);
0226: } catch (RemoteException re) {
0227: _threwException(re);
0228: return null;
0229: }
0230: }
0231:
0232: /** Gets the class name of a variable in the current interpreter.
0233: * @param var the name of the variable
0234: */
0235: public String getVariableClassName(String var) {
0236: // silently fail if disabled. see killInterpreter docs for details.
0237: if (!_restart)
0238: return null;
0239:
0240: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0241:
0242: try {
0243: return slave.getVariableClassName(var);
0244: } catch (RemoteException re) {
0245: _threwException(re);
0246: return null;
0247: }
0248: }
0249:
0250: /** Called when a call to interpret has completed.
0251: * @param result The result of the interpretation
0252: */
0253: public void interpretResult(InterpretResult result)
0254: throws RemoteException {
0255: try {
0256: _log.log(this + ".interpretResult(" + result + ")");
0257: result.apply(getResultHandler());
0258: } catch (Throwable t) {
0259: _log.log(this + "interpretResult threw " + t.toString());
0260: }
0261: }
0262:
0263: // /**
0264: // * Adds a single path to the Interpreter's class path.
0265: // * This method <b>cannot</b> take multiple paths separated by
0266: // * a path separator; it must be called separately for each path.
0267: // * @param path Path to be added to classpath
0268: // */
0269: // public void addClassPath(String path) {
0270: // // silently fail if disabled. see killInterpreter docs for details.
0271: // if (! _restart) return;
0272: //
0273: // ensureInterpreterConnected();
0274: //
0275: // try {
0276: // // System.err.println("addclasspath to " + _interpreterJVM() + ": " + path);
0277: // // System.err.println("full classpath: " + getClasspath());
0278: // _interpreterJVM().addClassPath(path);
0279: // }
0280: // catch (RemoteException re) {
0281: // _threwException(re);
0282: // }
0283: // }
0284:
0285: public void addProjectClassPath(File f) {
0286: if (!_restart)
0287: return;
0288: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0289:
0290: try {
0291: slave.addProjectClassPath(f);
0292: } catch (RemoteException re) {
0293: _threwException(re);
0294: }
0295: }
0296:
0297: public void addBuildDirectoryClassPath(File f) {
0298: if (!_restart)
0299: return;
0300: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0301:
0302: try {
0303: slave.addBuildDirectoryClassPath(f);
0304: } catch (RemoteException re) {
0305: _threwException(re);
0306: }
0307: }
0308:
0309: public void addProjectFilesClassPath(File f) {
0310: if (!_restart)
0311: return;
0312: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0313:
0314: try {
0315: slave.addProjectFilesClassPath(f);
0316: } catch (RemoteException re) {
0317: _threwException(re);
0318: }
0319: }
0320:
0321: public void addExternalFilesClassPath(File f) {
0322: if (!_restart)
0323: return;
0324: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0325:
0326: try {
0327: slave.addExternalFilesClassPath(f);
0328: } catch (RemoteException re) {
0329: _threwException(re);
0330: }
0331: }
0332:
0333: public void addExtraClassPath(File f) {
0334: if (!_restart)
0335: return;
0336: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0337:
0338: try {
0339: slave.addExtraClassPath(f);
0340: } catch (RemoteException re) {
0341: _threwException(re);
0342: }
0343: }
0344:
0345: /** Returns the current classpath of the interpreter as a list of
0346: * unique entries. The list is empty if a remote exception occurs.
0347: */
0348: public Iterable<File> getClassPath() {
0349: // silently fail if disabled. see killInterpreter docs for details.
0350: if (_restart) {
0351:
0352: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0353:
0354: try {
0355: return IterUtil.compose(slave.getAugmentedClassPath(),
0356: _startupClassPath);
0357: } catch (RemoteException re) {
0358: _threwException(re);
0359: return IterUtil.empty();
0360: }
0361: } else {
0362: return IterUtil.empty();
0363: }
0364: }
0365:
0366: /** Sets the Interpreter to be in the given package.
0367: * @param packageName Name of the package to enter.
0368: */
0369: public void setPackageScope(String packageName) {
0370: // silently fail if disabled. see killInterpreter docs for details.
0371: if (!_restart)
0372: return;
0373:
0374: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0375:
0376: try {
0377: slave.setPackageScope(packageName);
0378: } catch (RemoteException re) {
0379: _threwException(re);
0380: }
0381: }
0382:
0383: /** @param show Whether to show a message if a reset operation fails. */
0384: public void setShowMessageOnResetFailure(boolean show) {
0385: // silently fail if disabled. see killInterpreter docs for details.
0386: if (!_restart)
0387: return;
0388:
0389: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0390:
0391: try {
0392: slave.setShowMessageOnResetFailure(show);
0393: } catch (RemoteException re) {
0394: _threwException(re);
0395: }
0396: }
0397:
0398: /** Forwards a call to System.err from InterpreterJVM to the local InteractionsModel.
0399: * @param s String that was printed in the other JVM
0400: */
0401: public void systemErrPrint(String s) throws RemoteException {
0402: _interactionsModel.replSystemErrPrint(s);
0403: }
0404:
0405: /** Forwards a call to System.out from InterpreterJVM to the local InteractionsModel.
0406: * @param s String that was printed in the other JVM
0407: */
0408: public void systemOutPrint(String s) throws RemoteException {
0409: _interactionsModel.replSystemOutPrint(s);
0410: }
0411:
0412: /** Sets up a JUnit test suite in the Interpreter JVM and finds which classes are really TestCases
0413: * classes (by loading them)
0414: * @param classNames the class names to run in a test
0415: * @param files the associated file
0416: * @return the class names that are actually test cases
0417: */
0418: public List<String> findTestClasses(List<String> classNames,
0419: List<File> files) throws RemoteException {
0420: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0421: return slave.findTestClasses(classNames, files);
0422: }
0423:
0424: /** Runs the JUnit test suite already cached in the Interpreter JVM.
0425: * @return false if no test suite is cached; true otherwise
0426: */
0427: public boolean runTestSuite() throws RemoteException {
0428: return _interpreterJVM().runTestSuite();
0429: }
0430:
0431: /** Called if JUnit is invoked on a non TestCase class. Forwards from the other JVM to the local JUnit model.
0432: * @param isTestAll whether or not it was a use of the test all button
0433: */
0434: public void nonTestCase(boolean isTestAll) throws RemoteException {
0435: _junitModel.nonTestCase(isTestAll);
0436: }
0437:
0438: /** Called if the slave JVM encounters an illegal class file in testing. Forwards from
0439: * the other JVM to the local JUnit model.
0440: * @param e the ClassFileError describing the error when loading the class file
0441: */
0442: public void classFileError(ClassFileError e) throws RemoteException {
0443: // Utilities.showDebug("classFileError(" + e + ") called in MainJVM");
0444: _junitModel.classFileError(e);
0445: }
0446:
0447: /** Called to indicate that a suite of tests has started running.
0448: * Forwards from the other JVM to the local JUnit model.
0449: * @param numTests The number of tests in the suite to be run.
0450: */
0451: public void testSuiteStarted(int numTests) throws RemoteException {
0452: _slaveJVMUsed = true;
0453: // Utilities.show("MainJVM.testSuiteStarted(" + numTests + ") called");
0454: _interactionsModel.slaveJVMUsed();
0455: _junitModel.testSuiteStarted(numTests);
0456: }
0457:
0458: /** Called when a particular test is started. Forwards from the slave JVM to the local JUnit model.
0459: * @param testName The name of the test being started.
0460: */
0461: public void testStarted(String testName) throws RemoteException {
0462: // Utilities.show("MainJVM.testStarted(" + testName + ") called");
0463: _slaveJVMUsed = true;
0464: // Utilities.show("MainJVM.testStarted(" + testName + ") called");
0465: _junitModel.testStarted(testName);
0466: }
0467:
0468: /** Called when a particular test has ended. Forwards from the other JVM to the local JUnit model.
0469: * @param testName The name of the test that has ended.
0470: * @param wasSuccessful Whether the test passed or not.
0471: * @param causedError If not successful, whether the test caused an error or simply failed.
0472: */
0473: public void testEnded(String testName, boolean wasSuccessful,
0474: boolean causedError) throws RemoteException {
0475: _junitModel.testEnded(testName, wasSuccessful, causedError);
0476: }
0477:
0478: /** Called when a full suite of tests has finished running. Forwards from the other JVM to the local JUnit model.
0479: * @param errors The array of errors from all failed tests in the suite.
0480: */
0481: public void testSuiteEnded(JUnitError[] errors)
0482: throws RemoteException {
0483: // Utilities.showDebug("MainJVM.testSuiteEnded() called");
0484: _junitModel.testSuiteEnded(errors);
0485: }
0486:
0487: /** Called when the JUnitTestManager wants to open a file that is not currently open.
0488: * @param className the name of the class for which we want to find the file
0489: * @return the file associated with the given class
0490: */
0491: public File getFileForClassName(String className)
0492: throws RemoteException {
0493: return _junitModel.getFileForClassName(className);
0494: }
0495:
0496: /** Notifies the main jvm that an assignment has been made in the given debug interpreter.
0497: * Does not notify on declarations.
0498: *
0499: * This method is not currently necessary, since we don't copy back values in a debug interpreter until the thread
0500: * has resumed.
0501: *
0502: * @param name the name of the debug interpreter
0503: *
0504: public void notifyDebugInterpreterAssignment(String name) {
0505: }*/
0506:
0507: /**Accessor for the remote interface to the Interpreter JVM; _slave is protected field of the supserclass. */
0508: private InterpreterJVMRemoteI _interpreterJVM() {
0509: return (InterpreterJVMRemoteI) _slave;
0510: }
0511:
0512: // /** Updates the security manager in slave JVM */
0513: // public void enableSecurityManager() throws RemoteException {
0514: // _interpreterJVM().enableSecurityManager();
0515: // }
0516: //
0517: // /** Updates the security manager in slave JVM */
0518: // public void disableSecurityManager() throws RemoteException{
0519: // _interpreterJVM().disableSecurityManager();
0520: // }
0521:
0522: /** Adds a named DynamicJavaAdapter to the list of interpreters.
0523: * @param name the unique name for the interpreter
0524: * @throws IllegalArgumentException if the name is not unique
0525: */
0526: public void addJavaInterpreter(String name) {
0527: // silently fail if disabled. see killInterpreter docs for details.
0528: if (!_restart)
0529: return;
0530:
0531: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0532:
0533: try {
0534: slave.addJavaInterpreter(name);
0535: } catch (RemoteException re) {
0536: _threwException(re);
0537: }
0538: }
0539:
0540: /** Adds a named JavaDebugInterpreter to the list of interpreters.
0541: * @param name the unique name for the interpreter
0542: * @param className the fully qualified class name of the class the debug interpreter is in
0543: * @throws IllegalArgumentException if the name is not unique
0544: */
0545: public void addDebugInterpreter(String name, String className) {
0546: // silently fail if disabled. see killInterpreter docs for details.
0547: if (!_restart)
0548: return;
0549:
0550: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0551:
0552: try {
0553: slave.addDebugInterpreter(name, className);
0554: } catch (RemoteException re) {
0555: _threwException(re);
0556: }
0557: }
0558:
0559: /** Removes the interpreter with the given name, if it exists.
0560: * @param name Name of the interpreter to remove
0561: */
0562: public void removeInterpreter(String name) {
0563: // silently fail if disabled. see killInterpreter docs for details.
0564: if (!_restart)
0565: return;
0566:
0567: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0568:
0569: try {
0570: slave.removeInterpreter(name);
0571: if (name.equals(_currentInterpreterName))
0572: _currentInterpreterName = null;
0573: } catch (RemoteException re) {
0574: _threwException(re);
0575: }
0576: }
0577:
0578: /** Sets the current interpreter to the one specified by name
0579: * @param name the unique name of the interpreter to set active
0580: * @return Whether the new interpreter is currently processing an interaction (i.e., whether an interactionEnded
0581: * event will be fired)
0582: */
0583: public boolean setActiveInterpreter(String name) {
0584: // silently fail if disabled. see killInterpreter docs for details.
0585: if (!_restart)
0586: return false;
0587: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0588:
0589: try {
0590: boolean result = slave.setActiveInterpreter(name);
0591: _currentInterpreterName = name;
0592: return result;
0593: } catch (RemoteException re) {
0594: _threwException(re);
0595: return false;
0596: }
0597: }
0598:
0599: /** Sets the default interpreter to be the current one.
0600: * @return Whether the new interpreter is currently in progress with an interaction (ie. whether an
0601: * interactionEnded event will be fired)
0602: */
0603: public boolean setToDefaultInterpreter() {
0604: // silently fail if disabled. see killInterpreter docs for details.
0605: if (!_restart)
0606: return false;
0607:
0608: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0609:
0610: try {
0611: boolean result = slave.setToDefaultInterpreter();
0612: _currentInterpreterName = DEFAULT_INTERPRETER_NAME;
0613: return result;
0614: } catch (ConnectIOException ce) {
0615: _log
0616: .log(this
0617: + "could not connect to the interpreterJVM after killing it. Threw "
0618: + ce);
0619: return false;
0620: } catch (RemoteException re) {
0621: _threwException(re);
0622: return false;
0623: }
0624: }
0625:
0626: /** Accesses the cached current interpreter name. */
0627: public String getCurrentInterpreterName() {
0628: return _currentInterpreterName;
0629: }
0630:
0631: /** Kills the running interpreter JVM, and restarts with working directory wd if wd != null. If wd == null, the
0632: * interpreter is not restarted. Note: If the interpreter is not restarted, all of the methods that delegate to the
0633: * interpreter will silently fail! Therefore, killing without restarting should be used with extreme care and only in
0634: * carefully controlled test cases or when DrJava is quitting anyway.
0635: */
0636:
0637: public void killInterpreter(File wd) {
0638: boolean restart;
0639: synchronized (_masterJVMLock) {
0640: _workDir = wd;
0641: _restart = (wd != null);
0642: _cleanlyRestarting = true;
0643: restart = _restart;
0644: }
0645:
0646: /* Dropping lock before performing operations on the interactions document/pane and making remote call. */
0647: try {
0648: if (restart)
0649: _interactionsModel.interpreterResetting();
0650: quitSlave();
0651: } // new slave JVM is restarted by call on startInterpreterJVM on death of current slave
0652: catch (RemoteException e) {
0653: _log
0654: .log(this
0655: + "could not connect to the interpreterJVM while trying to kill it. Threw "
0656: + e);
0657: }
0658: }
0659:
0660: /** Sets the classpath to use for starting the interpreter JVM. Must include the classes for the interpreter.
0661: * @param classPath Classpath for the interpreter JVM
0662: */
0663: public void setStartupClassPath(String classPath) {
0664: _startupClassPath = IOUtil.attemptCanonicalFiles(IOUtil
0665: .parsePath(classPath));
0666: }
0667:
0668: /** Starts the interpreter if it's not running already. */
0669: public void startInterpreterJVM() {
0670: _log.log(this + ".startInterpreterJVM() called");
0671: // synchronized(_masterJVMLock) { // synch is unnecessary
0672: if (isStartupInProgress() || isInterpreterRunning())
0673: return; // These predicates simply check volatile boolean flags
0674: // }
0675: // Pass assertion and debug port information as JVM arguments
0676: ArrayList<String> jvmArgs = new ArrayList<String>();
0677: if (allowAssertions())
0678: jvmArgs.add("-ea");
0679: int debugPort = getDebugPort();
0680: _log.log("Main JVM starting with debug port: " + debugPort);
0681: if (debugPort > -1) {
0682: jvmArgs
0683: .add("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address="
0684: + debugPort);
0685: jvmArgs.add("-Xdebug");
0686: jvmArgs.add("-Xnoagent");
0687: jvmArgs.add("-Djava.compiler=NONE");
0688: }
0689: // Cannot do the following line because it causes an error on Macs in the Eclipse plug-in.
0690: // By instantiating the config, somehow the Apple JVM tries to start up AWT, which seems
0691: // to be prohibited by Eclipse. Badness ensues.
0692: // String optionArgString = DrJava.getConfig().getSetting(OptionConstants.JVM_ARGS);
0693: // List<String> optionArgs = ArgumentTokenizer.tokenize(optionArgString);
0694: jvmArgs.addAll(_optionArgs);
0695: String[] jvmArgsArray = new String[jvmArgs.size()];
0696: for (int i = 0; i < jvmArgs.size(); i++) {
0697: jvmArgsArray[i] = jvmArgs.get(i);
0698: }
0699:
0700: // Create and invoke the Interpreter JVM
0701: _numAttempts = 0;
0702: try {
0703: // _startupClasspath is sent in as the interactions classpath
0704: // Utilities.show("Calling invokeSlave(" + jvmArgs + ", " + _startupClassPath + ", " + _workDir +")");
0705: invokeSlave(jvmArgsArray, IOUtil
0706: .pathToString(_startupClassPath), _workDir);
0707: _slaveJVMUsed = false;
0708: } catch (RemoteException re) {
0709: _threwException(re);
0710: } catch (IOException ioe) {
0711: _threwException(ioe);
0712: }
0713: }
0714:
0715: /** React if the slave JVM quits. Restarts the JVM unless _restart is false, and notifies the InteractionsModel
0716: * if the quit was unexpected. Called from a thread within AbstractMasterJVM waiting for the death of the process
0717: * that starts and runs the slave JVM.
0718: * @param status Status returned by the dead process.
0719: */
0720: protected void handleSlaveQuit(int status) {
0721: // Only restart the slave if _restart is true
0722: // Utilities.showDebug("MainJVM: slaveJVM has quit with status " + status + " _workDir = " + _workDir +
0723: // " _cleanlyRestarting = " + _cleanlyRestarting);
0724: if (_restart) {
0725: // We have already fired this event if we are cleanly restarting
0726: if (!_cleanlyRestarting)
0727: _interactionsModel.interpreterResetting();
0728: // Utilities.showDebug("MainJVM: calling startInterpreterJVM()");
0729: startInterpreterJVM();
0730: }
0731:
0732: if (!_cleanlyRestarting)
0733: _interactionsModel.replCalledSystemExit(status);
0734: _cleanlyRestarting = false;
0735: }
0736:
0737: /** Action to take if the slave JVM quits before registering. Assumes _masterJVMLock is held.
0738: * @param status Status code of the JVM
0739: * TODO: revise the unit tests that kill the slave prematurely (by making them wait until the
0740: * slave registers) and remove the TEST_MODE escape.
0741: */
0742: protected void slaveQuitDuringStartup(int status) {
0743: super .slaveQuitDuringStartup(status);
0744: _numAttempts++; // no synchronization since this is the only place that _numAttempts is modified
0745: if (Utilities.TEST_MODE || _numAttempts < MAX_COUNT)
0746: return; // Some tests kill the slave immediately after it starts.
0747:
0748: // The slave JVM is not enabled after this to prevent an infinite loop of attempted startups
0749: _restart = false;
0750:
0751: // Signal that an internal error occurred
0752: String msg = "Interpreter JVM exited before registering, status: "
0753: + status;
0754: IllegalStateException e = new IllegalStateException(msg);
0755: new edu.rice.cs.drjava.ui.DrJavaErrorHandler().handle(e);
0756: }
0757:
0758: /** Called if the slave JVM dies before it is able to register.
0759: * @param cause The Throwable which caused the slave to die.
0760: */
0761: public void errorStartingSlave(Throwable cause)
0762: throws RemoteException {
0763: new edu.rice.cs.drjava.ui.DrJavaErrorHandler().handle(cause);
0764: }
0765:
0766: /** This method is called by the interpreter JVM if it cannot be exited.
0767: * @param th The Throwable thrown by System.exit
0768: */
0769: public void quitFailed(Throwable th) throws RemoteException {
0770: _interactionsModel.interpreterResetFailed(th);
0771: _cleanlyRestarting = false;
0772: }
0773:
0774: /** Returns whether a JVM is currently starting. This override widens the visibility of the method. */
0775: public boolean isStartupInProgress() {
0776: return super .isStartupInProgress();
0777: }
0778:
0779: /** Called when Interpreter JVM connects to us after being started. Assumes that _masterJVMLock is already held. */
0780: protected void handleSlaveConnected() {
0781: // we reset the enabled flag since, unless told otherwise via
0782: // killInterpreter(false), we want to automatically respawn
0783: // System.out.println("handleSlaveConnected() called in MainJVM"); // DEBUG
0784: _restart = true;
0785: _cleanlyRestarting = false;
0786:
0787: Boolean allowAccess = DrJava.getConfig().getSetting(
0788: OptionConstants.ALLOW_PRIVATE_ACCESS);
0789: setPrivateAccessible(allowAccess.booleanValue());
0790:
0791: // System.out.println("Calling interpreterReady(" + _workDir + ") called in MainJVM"); // DEBUG
0792: _interactionsModel.interpreterReady(_workDir);
0793: _junitModel.junitJVMReady();
0794:
0795: _log.log("Main JVM Thread for slave connection is: "
0796: + Thread.currentThread());
0797:
0798: // notify a thread that is waiting in ensureInterpreterConnected
0799: synchronized (_interpreterLock) {
0800: _interpreterLock.notifyAll();
0801: }
0802: }
0803:
0804: /** ReEnables restarting the slave if it has been turned off by repeated startup failures. */
0805: public void enableRestart() {
0806: _restart = true;
0807: }
0808:
0809: /** Returns the visitor to handle an InterpretResult. */
0810: protected InterpretResultVisitor<Object> getResultHandler() {
0811: return _handler;
0812: }
0813:
0814: /** Returns the debug port to use, as specified by the model. Returns -1 if no usable port could be found. */
0815: protected int getDebugPort() {
0816: int port = -1;
0817: try {
0818: port = _interactionsModel.getDebugPort();
0819: } catch (IOException ioe) {
0820: /* Can't find port; don't use debugger */
0821: }
0822: return port;
0823: }
0824:
0825: /** Return whether to allow assertions in the InterpreterJVM. */
0826: protected boolean allowAssertions() {
0827: String version = System.getProperty("java.version");
0828: return (_allowAssertions && (version != null) && ("1.4.0"
0829: .compareTo(version) <= 0));
0830: }
0831:
0832: /** Lets the model know if any exceptions occur while communicating with the Interpreter JVM. */
0833: private void _threwException(Throwable t) {
0834: String shortMsg = null;
0835: if ((t instanceof ParseError)
0836: && ((ParseError) t).getParseException() != null)
0837: shortMsg = ((ParseError) t).getMessage(); // in this case, getMessage is equivalent to getShortMessage
0838: _interactionsModel.replThrewException(t.getClass().getName(), t
0839: .getMessage(), StringOps.getStackTrace(t), shortMsg);
0840: ;
0841: }
0842:
0843: /** Sets the interpreter to allow access to private members. TODO: synchronize? */
0844: public void setPrivateAccessible(boolean allow) {
0845: // silently fail if disabled. see killInterpreter docs for details.
0846: if (!_restart)
0847: return;
0848:
0849: InterpreterJVMRemoteI slave = ensureInterpreterConnected();
0850: try {
0851: slave.setPrivateAccessible(allow);
0852: } catch (RemoteException re) {
0853: _threwException(re);
0854: }
0855: }
0856:
0857: /** If an interpreter has not registered itself, this method will block until one does.*/
0858: public InterpreterJVMRemoteI ensureInterpreterConnected() {
0859: // _log.log("ensureInterpreterConnected called by Main JVM");
0860: try {
0861: synchronized (_interpreterLock) {
0862: /* Now we silently fail if interpreter is disabled instead of throwing an exception. This situation
0863: * occurs only in test cases and when DrJava is about to quit.
0864: */
0865: if (!_restart) {
0866: throw new IllegalStateException(
0867: "Interpreter is disabled");
0868: }
0869: InterpreterJVMRemoteI slave = _interpreterJVM();
0870: while (slave == null) {
0871: // _log.log("interpreter is null in Main JVM, waiting for it to register");
0872: _interpreterLock.wait();
0873: slave = _interpreterJVM();
0874: }
0875:
0876: // _log.log("interpreter " + interp + " registered in Main JVM");
0877: return slave;
0878: }
0879: } catch (InterruptedException ie) {
0880: throw new UnexpectedException(ie);
0881: }
0882: }
0883:
0884: /** Asks the main jvm for input from the console.
0885: * @return the console input
0886: */
0887: public String getConsoleInput() {
0888: String s = _interactionsModel.getConsoleInput();
0889: // System.err.println("MainJVM.getConsoleInput() returns '" + s + "'");
0890: return s;
0891: }
0892:
0893: /**
0894: * Peforms the appropriate action to return any type of result
0895: * from a call to interpret back to the GlobalModel.
0896: */
0897: private class ResultHandler implements
0898: InterpretResultVisitor<Object> {
0899: /** Lets the model know that void was returned.
0900: * @return null
0901: */
0902: public Object forVoidResult(VoidResult that) {
0903: _interactionsModel.replReturnedVoid();
0904: return null;
0905: }
0906:
0907: /** Returns a value result (as a String) back to the model.
0908: * @return null
0909: */
0910: public Object forValueResult(ValueResult that) {
0911: String result = that.getValueStr();
0912: String style = that.getStyle();
0913: _interactionsModel.replReturnedResult(result, style);
0914: return null;
0915: }
0916:
0917: /** Returns an exception back to the model.
0918: * @return null
0919: */
0920: public Object forExceptionResult(ExceptionResult that) { /**/
0921: _interactionsModel.replThrewException(that
0922: .getExceptionClass(), that.getExceptionMessage(),
0923: that.getStackTrace(), that.getSpecialMessage());
0924: return null;
0925: }
0926:
0927: /** Indicates there was a syntax error to the model.
0928: * @return null
0929: */
0930: public Object forSyntaxErrorResult(SyntaxErrorResult that) {
0931: _interactionsModel.replReturnedSyntaxError(that
0932: .getErrorMessage(), that.getInteraction(), that
0933: .getStartRow(), that.getStartCol(), that
0934: .getEndRow(), that.getEndCol());
0935: return null;
0936: }
0937:
0938: public Object forInterpreterBusy(InterpreterBusy that) {
0939: throw new UnexpectedException(
0940: "MainJVM.interpret() called when InterpreterJVM was busy!");
0941: }
0942: }
0943:
0944: /** InteractionsModel which does not react to events. */
0945: public static class DummyInteractionsModel implements
0946: InteractionsModelCallback {
0947: public int getDebugPort() throws IOException {
0948: return -1;
0949: }
0950:
0951: public void replSystemOutPrint(String s) {
0952: }
0953:
0954: public void replSystemErrPrint(String s) {
0955: }
0956:
0957: public String getConsoleInput() {
0958: throw new IllegalStateException(
0959: "Cannot request input from dummy interactions model!");
0960: }
0961:
0962: public void setInputListener(InputListener il) {
0963: throw new IllegalStateException(
0964: "Cannot set the input listener of dummy interactions model!");
0965: }
0966:
0967: public void changeInputListener(InputListener from,
0968: InputListener to) {
0969: throw new IllegalStateException(
0970: "Cannot change the input listener of dummy interactions model!");
0971: }
0972:
0973: public void replReturnedVoid() {
0974: }
0975:
0976: public void replReturnedResult(String result, String style) {
0977: }
0978:
0979: public void replThrewException(String exceptionClass,
0980: String message, String stackTrace, String specialMessage) {
0981: }
0982:
0983: public void replReturnedSyntaxError(String errorMessage,
0984: String interaction, int startRow, int startCol,
0985: int endRow, int endCol) {
0986: }
0987:
0988: public void replCalledSystemExit(int status) {
0989: }
0990:
0991: public void interpreterResetting() {
0992: }
0993:
0994: public void interpreterResetFailed(Throwable th) {
0995: }
0996:
0997: public void interpreterReady(File wd) {
0998: }
0999:
1000: public void slaveJVMUsed() {
1001: }
1002: }
1003:
1004: /** JUnitModel which does not react to events. */
1005: public static class DummyJUnitModel implements JUnitModelCallback {
1006: public void nonTestCase(boolean isTestAll) {
1007: }
1008:
1009: public void classFileError(ClassFileError e) {
1010: }
1011:
1012: public void testSuiteStarted(int numTests) {
1013: }
1014:
1015: public void testStarted(String testName) {
1016: }
1017:
1018: public void testEnded(String testName, boolean wasSuccessful,
1019: boolean causedError) {
1020: }
1021:
1022: public void testSuiteEnded(JUnitError[] errors) {
1023: }
1024:
1025: public File getFileForClassName(String className) {
1026: return null;
1027: }
1028:
1029: public Iterable<File> getClassPath() {
1030: return IterUtil.empty();
1031: }
1032:
1033: public void junitJVMReady() {
1034: }
1035: }
1036:
1037: /** DebugModelCallback which does not react to events. */
1038: public static class DummyDebugModel implements DebugModelCallback {
1039: public void notifyDebugInterpreterAssignment(String name) {
1040: }
1041: }
1042: }
|