Clover coverage report - DrJava Test Coverage (drjava-20110828-r5448)
Coverage timestamp: Sun Aug 28 2011 03:13:33 CDT
file stats: LOC: 1,101   Methods: 132
NCLOC: 620   Classes: 13
 
 Source file Conditionals Statements Methods TOTAL
MainJVM.java 35.6% 49.5% 44.7% 46.3%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.model.repl.newjvm;
 38   
 39    import java.rmi.*;
 40    import java.io.*;
 41    import java.net.SocketException;
 42   
 43    import java.util.List;
 44    import java.util.ArrayList;
 45    import java.util.Map;
 46    import java.util.concurrent.TimeoutException;
 47   
 48    import edu.rice.cs.drjava.DrJava;
 49    import edu.rice.cs.drjava.config.OptionConstants;
 50    import edu.rice.cs.drjava.model.repl.*;
 51    import edu.rice.cs.drjava.model.junit.JUnitError;
 52    import edu.rice.cs.drjava.model.junit.JUnitModelCallback;
 53    import edu.rice.cs.drjava.model.debug.DebugModelCallback;
 54    import edu.rice.cs.drjava.platform.PlatformFactory;
 55    import edu.rice.cs.drjava.ui.DrJavaErrorHandler;
 56   
 57    import edu.rice.cs.util.ArgumentTokenizer;
 58    import edu.rice.cs.util.FileOps;
 59    import edu.rice.cs.util.UnexpectedException;
 60    import edu.rice.cs.plt.io.IOUtil;
 61    import edu.rice.cs.plt.iter.IterUtil;
 62    import edu.rice.cs.plt.reflect.ReflectUtil;
 63    import edu.rice.cs.plt.reflect.JavaVersion;
 64    import edu.rice.cs.plt.tuple.Option;
 65    import edu.rice.cs.plt.tuple.Pair;
 66    import edu.rice.cs.plt.concurrent.JVMBuilder;
 67    import edu.rice.cs.plt.concurrent.StateMonitor;
 68    import edu.rice.cs.plt.concurrent.CompletionMonitor;
 69   
 70    import edu.rice.cs.util.newjvm.*;
 71    import edu.rice.cs.util.classloader.ClassFileError;
 72   
 73    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 74   
 75    /**
 76    * <p>Manages a remote JVM. Includes methods for communication in both directions: MainJVMRemoteI
 77    * provides callbacks allowing the remote JVM to access the model, and a variety of delegating
 78    * methods wrap calls to the InterpreterJVMRemoteI methods, taking care of any RMI-related errors.
 79    * In the case of errors, these interpreter-delegating methods communicate the failure via the
 80    * return value. (Note that it is impossible to guarantee success of these methods -- the remote
 81    * process may exit arbitrarily at any time -- and clients should behave gracefully when failures
 82    * occur.)</p>
 83    *
 84    * <p>The current design is flawed: strictly speaking, two sequential interpreter-delegating calls to
 85    * this object may communicate with <em>different</em> JVMs if the remote JVM happens to reset in
 86    * the interim. A better design would return a separate object for interfacing with each unique remote
 87    * JVM. In this way, clients would know that all calls to a certain object would be forwarded to
 88    * the same remote JVM.</p>
 89    *
 90    * @version $Id: MainJVM.java 5436 2011-08-02 06:58:19Z mgricken $
 91    */
 92    public class MainJVM extends AbstractMasterJVM implements MainJVMRemoteI {
 93   
 94    /** Number of slave startup failures allowed before aborting the startup process. */
 95    private static final int MAX_STARTUP_FAILURES = 3;
 96   
 97    /** Number of milliseconds to block while waiting for an InterpreterJVM stub. */
 98    private static final int STARTUP_TIMEOUT = 10000;
 99   
 100    /** Contains the current InterpreterJVM stub, or {@code null} if it is not running. */
 101    private final StateMonitor<State> _state;
 102   
 103    /** Instance of inner class to handle interpret result. */
 104    private final ResultHandler _handler = new ResultHandler();
 105   
 106    /** Listens to interactions-related events. */
 107    private volatile InteractionsModelCallback _interactionsModel;
 108   
 109    /** Listens to JUnit-related events. */
 110    private volatile JUnitModelCallback _junitModel;
 111   
 112    /** Listens to debug-related events */
 113    private volatile DebugModelCallback _debugModel;
 114   
 115   
 116    /* JVM execution options */
 117   
 118    /** Whether to allow "assert" statements to run in the remote JVM. */
 119    private volatile boolean _allowAssertions = false;
 120   
 121    /** Class path to use for starting the interpreter JVM */
 122    private volatile Iterable<File> _startupClassPath;
 123   
 124    /** Working directory for slave JVM */
 125    private volatile File _workingDir;
 126   
 127    /** Creates a new MainJVM to interface to another JVM; the MainJVM has a link to the partially initialized
 128    * global model. The MainJVM but does not automatically start the Interpreter JVM. Callers must set the
 129    * InteractionsModel and JUnitModel and then call startInterpreterJVM().
 130    */
 131  159 public MainJVM(File wd) {
 132  159 super(InterpreterJVM.class.getName());
 133  159 _workingDir = wd;
 134  159 _interactionsModel = new DummyInteractionsModel();
 135  159 _junitModel = new DummyJUnitModel();
 136  159 _debugModel = new DummyDebugModel();
 137  159 _state = new StateMonitor<State>(new FreshState());
 138  159 _startupClassPath = ReflectUtil.SYSTEM_CLASS_PATH;
 139    }
 140   
 141   
 142    /*
 143    * === Startup and shutdown methods ===
 144    */
 145   
 146    /** Starts the interpreter if it's not running already. */
 147  159 public void startInterpreterJVM() { _state.value().start(); }
 148   
 149    /**
 150    * Stop the interpreter if it's current running. (Note that, until {@link #startInterpreterJVM} is called
 151    * again, all methods that delegate to the interpreter JVM will fail, returning "false" or "none".)
 152    */
 153  0 public void stopInterpreterJVM() { _state.value().stop(); }
 154   
 155    /**
 156    * Get a "fresh" interpreter JVM. Has the same effect as {@link #startInterpreterJVM} if no interpreter
 157    * is running. If a currently-running JVM is already "fresh", it is still stopped and restarted when
 158    * {@code force} is true.
 159    */
 160  26 public void restartInterpreterJVM(boolean force) { _state.value().restart(force); }
 161   
 162    /**
 163    * Stop the interpreter JVM, do not restart it, and terminate the RMI server associated with this object.
 164    * May be useful when a number of different MainJVM objects are created (such as when running tests).
 165    */
 166  157 public void dispose() { _state.value().dispose(); }
 167   
 168   
 169    /*
 170    * === AbstractMasterJVM methods ===
 171    */
 172   
 173    /**
 174    * Callback for when the slave JVM has connected, and the bidirectional communications link has been
 175    * established. Provides access to the newly-created slave JVM.
 176    */
 177  180 protected void handleSlaveConnected(SlaveRemote newSlave) {
 178  180 InterpreterJVMRemoteI slaveCast = (InterpreterJVMRemoteI) newSlave;
 179  180 _state.value().started(slaveCast);
 180    }
 181   
 182    /**
 183    * Callback for when the slave JVM has quit.
 184    * @param status The exit code returned by the slave JVM.
 185    */
 186  156 protected void handleSlaveQuit(int status) {
 187  157 debug.logValue("Slave quit", "status", status);
 188  157 _state.value().stopped(status);
 189    }
 190   
 191    /**
 192    * Callback for when the slave JVM fails to either run or respond to {@link SlaveRemote#start}.
 193    * @param e Exception that occurred during startup.
 194    */
 195  0 protected void handleSlaveWontStart(Exception e) {
 196  0 debug.log("Slave won't start", e);
 197  0 _state.value().startFailed(e);
 198    }
 199   
 200   
 201   
 202    /*
 203    * === MainJVMRemoteI methods ===
 204    */
 205   
 206    // TODO: export other objects, such as the interactionsModel, thus avoiding the need to delegate here?
 207   
 208    /** Forwards a call to System.err from InterpreterJVM to the local InteractionsModel.
 209    * @param s String that was printed in the other JVM
 210    */
 211  0 public void systemErrPrint(String s) {
 212  0 debug.logStart();
 213  0 _interactionsModel.replSystemErrPrint(s);
 214    // Utilities.clearEventQueue(); // wait for event queue task to complete
 215  0 debug.logEnd();
 216    }
 217   
 218    /** Forwards a call to System.out from InterpreterJVM to the local InteractionsModel.
 219    * @param s String that was printed in the other JVM
 220    */
 221  8 public void systemOutPrint(String s) {
 222  8 debug.logStart();
 223  8 _interactionsModel.replSystemOutPrint(s);
 224    // Utilities.clearEventQueue(); // wait for event queue task to complete
 225  8 debug.logEnd();
 226    }
 227   
 228    /** Asks the main jvm for input from the console.
 229    * @return the console input
 230    */
 231  1 public String getConsoleInput() {
 232  1 String s = _interactionsModel.getConsoleInput();
 233    // System.err.println("MainJVM.getConsoleInput() returns '" + s + "'");
 234  1 return s;
 235    }
 236   
 237    /** Called if JUnit is invoked on a non TestCase class. Forwards from the other JVM to the local JUnit model.
 238    * @param isTestAll whether or not it was a use of the test all button
 239    * @param didCompileFail whether or not a compile before this JUnit attempt failed
 240    */
 241  0 public void nonTestCase(boolean isTestAll, boolean didCompileFail) {
 242  0 _junitModel.nonTestCase(isTestAll, didCompileFail);
 243    }
 244   
 245    /** Called if the slave JVM encounters an illegal class file in testing. Forwards from
 246    * the other JVM to the local JUnit model.
 247    * @param e the ClassFileError describing the error when loading the class file
 248    */
 249  0 public void classFileError(ClassFileError e) {
 250  0 _junitModel.classFileError(e);
 251    }
 252   
 253    /** Called to indicate that a suite of tests has started running.
 254    * Forwards from the other JVM to the local JUnit model.
 255    * @param numTests The number of tests in the suite to be run.
 256    */
 257  16 public void testSuiteStarted(int numTests) {
 258  16 _junitModel.testSuiteStarted(numTests);
 259    }
 260   
 261    /** Called when a particular test is started. Forwards from the slave JVM to the local JUnit model.
 262    * @param testName The name of the test being started.
 263    */
 264  25 public void testStarted(String testName) {
 265  25 _junitModel.testStarted(testName);
 266    }
 267   
 268    /** Called when a particular test has ended. Forwards from the other JVM to the local JUnit model.
 269    * @param testName The name of the test that has ended.
 270    * @param wasSuccessful Whether the test passed or not.
 271    * @param causedError If not successful, whether the test caused an error or simply failed.
 272    */
 273  24 public void testEnded(String testName, boolean wasSuccessful, boolean causedError) {
 274  24 _junitModel.testEnded(testName, wasSuccessful, causedError);
 275    }
 276   
 277    /** Called when a full suite of tests has finished running. Forwards from the other JVM to the local JUnit model.
 278    * @param errors The array of errors from all failed tests in the suite.
 279    */
 280  15 public void testSuiteEnded(JUnitError[] errors) {
 281  15 _junitModel.testSuiteEnded(errors);
 282    }
 283   
 284    /** Called when the JUnitTestManager wants to open a file that is not currently open.
 285    * @param className the name of the class for which we want to find the file
 286    * @return the file associated with the given class
 287    */
 288  4 public File getFileForClassName(String className) {
 289  4 return _junitModel.getFileForClassName(className);
 290    }
 291   
 292    // /** Notifies the main jvm that an assignment has been made in the given debug interpreter.
 293    // * Does not notify on declarations.
 294    // *
 295    // * This method is not currently necessary, since we don't copy back values in a debug interpreter until the thread
 296    // * has resumed.
 297    // *
 298    // * @param name the name of the debug interpreter
 299    // */
 300    // public void notifyDebugInterpreterAssignment(String name) {
 301    // }
 302   
 303   
 304    /*
 305    * === Local getters and setters ===
 306    */
 307   
 308    /** Provides an object to listen to interactions-related events. */
 309  158 public void setInteractionsModel(InteractionsModelCallback model) { _interactionsModel = model; }
 310   
 311    /** Provides an object to listen to test-related events.*/
 312  157 public void setJUnitModel(JUnitModelCallback model) { _junitModel = model; }
 313   
 314    /** Provides an object to listen to debug-related events.
 315    * @param model the debug model
 316    */
 317  157 public void setDebugModel(DebugModelCallback model) { _debugModel = model; }
 318   
 319    /** Sets whether the remote JVM will run "assert" statements after the next restart. */
 320  157 public void setAllowAssertions(boolean allow) { _allowAssertions = allow; }
 321   
 322    /**
 323    * Sets the class path to use for starting the interpreter JVM. Must include the classes for the interpreter.
 324    * @param classPath Class path for the interpreter JVM
 325    */
 326  0 public void setStartupClassPath(String classPath) {
 327  0 _startupClassPath = IOUtil.parsePath(classPath);
 328    }
 329   
 330    /** Sets the working directory for the interpreter (takes effect on next startup). */
 331  25 public void setWorkingDirectory(File dir) {
 332  25 _workingDir = dir;
 333    }
 334   
 335    /** Declared as a getter in order to allow subclasses to override the standard behavior. */
 336  39 protected InterpretResult.Visitor<Void> resultHandler() { return _handler; }
 337   
 338   
 339    /* === Wrappers for InterpreterJVMRemoteI methods === */
 340   
 341    /** Interprets string s in the remote JVM. Blocks until the interpreter is connected and evaluation completes.
 342    * @return {@code true} if successful; {@code false} if the subprocess is unavailable, the subprocess dies
 343    * during the call, or an unexpected exception occurs.
 344    */
 345  54 public boolean interpret(final String s) {
 346  54 InterpreterJVMRemoteI remote = _state.value().interpreter(true);
 347  0 if (remote == null) { return false; }
 348  54 try {
 349  54 debug.logStart("Interpreting " + s);
 350  54 InterpretResult result = remote.interpret(s);
 351  52 result.apply(resultHandler());
 352  52 debug.logEnd("result", result);
 353  52 return true;
 354    }
 355  2 catch (RemoteException e) { debug.logEnd(); _handleRemoteException(e); return false; }
 356    }
 357   
 358    /**
 359    * Gets the string representation of the value of a variable in the current interpreter, or "none"
 360    * if the remote JVM is unavailable or an error occurs. Blocks until the interpreter is connected.
 361    * @param var the name of the variable
 362    */
 363  0 public Option<Pair<String,String>> getVariableToString(String var) {
 364  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 365  0 if (remote == null) { return Option.none(); }
 366  0 try { return Option.some(remote.getVariableToString(var)); }
 367  0 catch (RemoteException e) { _handleRemoteException(e); return Option.none(); }
 368    }
 369   
 370    /**
 371    * Blocks until the interpreter is connected. Returns {@code true} if the change was successfully passed to
 372    * the remote JVM.
 373    */
 374  0 public boolean addProjectClassPath(File f) {
 375  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 376  0 if (remote == null) { return false; }
 377  0 try { remote.addProjectClassPath(f); return true; }
 378  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 379    }
 380   
 381    /**
 382    * Blocks until the interpreter is connected. Returns {@code true} if the change was successfully passed to
 383    * the remote JVM.
 384    */
 385  25 public boolean addBuildDirectoryClassPath(File f) {
 386  25 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 387  0 if (remote == null) { return false; }
 388  25 try { remote.addBuildDirectoryClassPath(f); return true; }
 389  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 390    }
 391   
 392    /**
 393    * Blocks until the interpreter is connected. Returns {@code true} if the change was successfully passed to
 394    * the remote JVM.
 395    */
 396  183 public boolean addProjectFilesClassPath(File f) {
 397  183 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 398  1 if (remote == null) { return false; }
 399  182 try { remote.addProjectFilesClassPath(f); return true; }
 400  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 401    }
 402   
 403    /**
 404    * Blocks until the interpreter is connected. Returns {@code true} if the change was successfully passed to
 405    * the remote JVM.
 406    */
 407  225 public boolean addExternalFilesClassPath(File f) {
 408  225 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 409  0 if (remote == null) { return false; }
 410  225 try { remote.addExternalFilesClassPath(f); return true; }
 411  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 412    }
 413   
 414    /**
 415    * Blocks until the interpreter is connected. Returns {@code true} if the change was successfully passed to
 416    * the remote JVM.
 417    */
 418  2 public boolean addExtraClassPath(File f) {
 419  2 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 420  0 if (remote == null) { return false; }
 421  2 try { remote.addExtraClassPath(f); return true; }
 422  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 423    }
 424   
 425    /** Returns the current class path of the interpreter as a list of unique entries. The result is "none"
 426    * if the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 427    */
 428  0 public Option<Iterable<File>> getClassPath() {
 429  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 430  0 if (remote == null) { return Option.none(); }
 431  0 try { return Option.some(remote.getClassPath()); }
 432  0 catch (RemoteException e) { _handleRemoteException(e); return Option.none(); }
 433    }
 434   
 435    /** Sets the Interpreter to be in the given package. Blocks until the interpreter is connected.
 436    * @param packageName Name of the package to enter.
 437    */
 438  0 public boolean setPackageScope(String packageName) {
 439  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 440  0 if (remote == null) { return false; }
 441  0 try { remote.interpret("package " + packageName + ";"); return true; }
 442  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 443    }
 444   
 445    /** Sets up a JUnit test suite in the Interpreter JVM and finds which classes are really TestCase
 446    * classes (by loading them). Blocks until the interpreter is connected and the operation completes.
 447    * @param classNames the class names to run in a test
 448    * @param files the associated file
 449    * @return the class names that are actually test cases
 450    */
 451  18 public Option<List<String>> findTestClasses(List<String> classNames, List<File> files) {
 452  18 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 453  0 if (remote == null) { return Option.none(); }
 454  18 try { return Option.some(remote.findTestClasses(classNames, files)); }
 455  0 catch (RemoteException e) { _handleRemoteException(e); return Option.none(); }
 456    }
 457   
 458    /**
 459    * Runs the JUnit test suite already cached in the Interpreter JVM. Blocks until the remote JVM is available.
 460    * Returns {@code false} if no test suite is cached, the remote JVM is unavailable, or an error occurs.
 461    */
 462  16 public boolean runTestSuite() {
 463  16 InterpreterJVMRemoteI remote = _state.value().interpreter(true);
 464  0 if (remote == null) { return false; }
 465  16 try { return remote.runTestSuite(); }
 466  1 catch (RemoteException e) { _handleRemoteException(e); return false; }
 467    }
 468   
 469    // /** Updates the security manager in slave JVM */
 470    // public void enableSecurityManager() throws RemoteException {
 471    // _interpreterJVM().enableSecurityManager();
 472    // }
 473    //
 474    // /** Updates the security manager in slave JVM */
 475    // public void disableSecurityManager() throws RemoteException{
 476    // _interpreterJVM().disableSecurityManager();
 477    // }
 478   
 479   
 480    /**
 481    * Adds a named interpreter to the list. The result is {@code false} if the remote JVM is unavailable or
 482    * if an exception occurs. Blocks until the interpreter is connected.
 483    * @param name the unique name for the interpreter
 484    * @throws IllegalArgumentException if the name is not unique
 485    */
 486  2 public boolean addInterpreter(String name) {
 487  2 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 488  0 if (remote == null) { return false; }
 489  2 try { remote.addInterpreter(name); return true; }
 490  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 491    }
 492   
 493    /** Removes the interpreter with the given name, if it exists. The result is {@code false} if
 494    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 495    * @param name Name of the interpreter to remove
 496    */
 497  0 public boolean removeInterpreter(String name) {
 498  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 499  0 if (remote == null) { return false; }
 500  0 try { remote.removeInterpreter(name); return true; }
 501  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 502    }
 503   
 504    /** Sets the current interpreter to the one specified by name. The result is "none" if
 505    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 506    * @param name the unique name of the interpreter to set active
 507    * @return Status flags: whether the current interpreter changed, and whether it is busy; or "none" on an error
 508    */
 509  3 public Option<Pair<Boolean, Boolean>> setActiveInterpreter(String name) {
 510  3 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 511  0 if (remote == null) { return Option.none(); }
 512  3 try { return Option.some(remote.setActiveInterpreter(name)); }
 513  0 catch (RemoteException e) { _handleRemoteException(e); return Option.none(); }
 514    }
 515   
 516    /** Sets the default interpreter to be the current one. The result is "none" if
 517    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 518    * @return Status flags: whether the current interpreter changed, and whether it is busy; or "none" on an error
 519    */
 520  26 public Option<Pair<Boolean, Boolean>> setToDefaultInterpreter() {
 521  26 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 522  0 if (remote == null) { return Option.none(); }
 523  26 try { return Option.some(remote.setToDefaultInterpreter()); }
 524  0 catch (RemoteException e) { _handleRemoteException(e); return Option.none(); }
 525    }
 526   
 527    /** Sets the interpreter to enforce access to all members. The result is {@code false} if
 528    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 529    */
 530  0 public boolean setEnforceAllAccess(boolean enforce) {
 531  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 532  0 if (remote == null) { return false; }
 533  0 try { remote.setEnforceAllAccess(enforce); return true; }
 534  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 535    }
 536   
 537    /** Sets the interpreter to enforce access to private members. The result is {@code false} if
 538    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 539    */
 540  0 public boolean setEnforcePrivateAccess(boolean enforce) {
 541  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 542  0 if (remote == null) { return false; }
 543  0 try { remote.setEnforcePrivateAccess(enforce); return true; }
 544  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 545    }
 546   
 547    /** Require a semicolon at the end of statements. The result is {@code false} if
 548    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 549    */
 550  0 public boolean setRequireSemicolon(boolean require) {
 551  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 552  0 if (remote == null) { return false; }
 553  0 try { remote.setRequireSemicolon(require); return true; }
 554  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 555    }
 556   
 557    /** Require variable declarations to include an explicit type. The result is {@code false} if
 558    * the remote JVM is unavailable or if an exception occurs. Blocks until the interpreter is connected.
 559    */
 560  0 public boolean setRequireVariableType(boolean require) {
 561  0 InterpreterJVMRemoteI remote = _state.value().interpreter(false);
 562  0 if (remote == null) { return false; }
 563  0 try { remote.setRequireVariableType(require); return true; }
 564  0 catch (RemoteException e) { _handleRemoteException(e); return false; }
 565    }
 566   
 567    /*
 568    * === Helper methods ===
 569    */
 570   
 571    /** Call invokeSlave with the appropriate JVMBuilder. */
 572  180 private void _doStartup() {
 573  180 File dir = _workingDir;
 574    // TODO: Eliminate NULL_FILE. It is a bad idea! The correct behavior when it is used always depends on
 575    // context, so it can never be treated transparently. In this case, the process won't start.
 576  0 if (dir == FileOps.NULL_FILE) { dir = IOUtil.WORKING_DIRECTORY; }
 577   
 578  180 List<String> jvmArgs = new ArrayList<String>();
 579   
 580    // ConcJUnit argument: -Xbootclasspath/p:rt.concjunit.jar
 581    // ------------------------------------------------------
 582    // this section here loops if the rt.concjunit.jar file is
 583    // being re-generated or the settings are changed
 584  180 final CompletionMonitor cm = new CompletionMonitor();
 585  180 boolean repeat;
 586  180 do {
 587  180 repeat = false;
 588  180 File junitLocation = DrJava.getConfig().getSetting(OptionConstants.JUNIT_LOCATION);
 589  180 boolean javaVersion7 = JavaVersion.CURRENT.supports(JavaVersion.JAVA_7);
 590    // ConcJUnit is available if (a) the built-in framework is used, or (b) the external
 591    // framework is a valid ConcJUnit jar file, AND the compiler is not Java 7 or newer.
 592  180 boolean concJUnitAvailable =
 593    !javaVersion7 &&
 594    (!DrJava.getConfig().getSetting(OptionConstants.JUNIT_LOCATION_ENABLED) ||
 595    edu.rice.cs.drjava.model.junit.ConcJUnitUtils.isValidConcJUnitFile(junitLocation));
 596   
 597  180 File rtLocation = DrJava.getConfig().getSetting(OptionConstants.RT_CONCJUNIT_LOCATION);
 598  180 boolean rtLocationConfigured =
 599    edu.rice.cs.drjava.model.junit.ConcJUnitUtils.isValidRTConcJUnitFile(rtLocation);
 600   
 601  180 if (DrJava.getConfig().getSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED).
 602    equals(OptionConstants.ConcJUnitCheckChoices.ALL) && // "lucky" enabled
 603    !rtLocationConfigured && // not valid
 604    (rtLocation != null) && // not null
 605    (!FileOps.NULL_FILE.equals(rtLocation)) && // not NULL_FILE
 606    (rtLocation.exists())) { // but exists
 607    // invalid file, clear setting
 608  0 DrJava.getConfig().setSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED,
 609    OptionConstants.ConcJUnitCheckChoices.NO_LUCKY);
 610  0 rtLocationConfigured = false;
 611  0 javax.swing.JOptionPane.showMessageDialog(null,
 612    "The selected file is invalid and was disabled:\n"+rtLocation,
 613    "Invalid ConcJUnit Runtime File",
 614    javax.swing.JOptionPane.ERROR_MESSAGE);
 615    }
 616  180 if (concJUnitAvailable && // ConcJUnit available
 617    rtLocationConfigured && // runtime configured
 618    DrJava.getConfig().getSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED).
 619    equals(OptionConstants.ConcJUnitCheckChoices.ALL)) { // and "lucky" enabled
 620  0 try {
 621    // NOTE: this is a work-around
 622    // it seems like it's impossible to pass long file names here on Windows
 623    // so we are using a clumsy method that determines the short file name
 624  0 File shortF = FileOps.getShortFile(rtLocation);
 625   
 626    // check the JavaVersion of the rt.concjunit.jar file to make sure it is compatible
 627  0 if (edu.rice.cs.drjava.model.junit.ConcJUnitUtils.isCompatibleRTConcJUnitFile(shortF)) {
 628    // enabled, valid and compatible
 629    // add the JVM argument
 630  0 jvmArgs.add("-Xbootclasspath/p:"+shortF.getAbsolutePath().replace(File.separatorChar, '/'));
 631    }
 632    else {
 633    // enabled, valid but incompatible
 634    // ask to regenerate
 635  0 repeat = true; // re-check settings
 636  0 cm.reset();
 637  0 boolean attempted = edu.rice.cs.drjava.model.junit.ConcJUnitUtils.
 638    showIncompatibleWantToRegenerateDialog(null,
 639  0 new Runnable() { public void run() { cm.signal(); } }, // yes
 640  0 new Runnable() { public void run() { cm.signal(); } }); // no
 641  0 while(!cm.attemptEnsureSignaled()); // wait for dialog to finish
 642  0 if (!attempted) { repeat = false; }
 643    }
 644    }
 645    catch(IOException ioe) {
 646    // we couldn't get the short file name (on Windows), disable "lucky" warnings
 647  0 DrJava.getConfig().setSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED,
 648    OptionConstants.ConcJUnitCheckChoices.NO_LUCKY);
 649  0 rtLocationConfigured = false;
 650  0 javax.swing.JOptionPane.showMessageDialog(null,
 651    "There was a problem with the selected file, and it was disabled:\n"+rtLocation,
 652    "Invalid ConcJUnit Runtime File",
 653    javax.swing.JOptionPane.ERROR_MESSAGE);
 654    }
 655    }
 656  180 } while(repeat);
 657    // end of the section that may loop
 658    // ------------------------------------------------------
 659   
 660  177 if (_allowAssertions) { jvmArgs.add("-ea"); }
 661  180 int debugPort = _getDebugPort();
 662  180 if (debugPort > -1) {
 663  178 jvmArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=" + debugPort);
 664  178 jvmArgs.add("-Xdebug");
 665  178 jvmArgs.add("-Xnoagent");
 666  178 jvmArgs.add("-Djava.compiler=NONE");
 667    }
 668  180 String slaveMemory = DrJava.getConfig().getSetting(OptionConstants.SLAVE_JVM_XMX);
 669  180 if (!"".equals(slaveMemory) && !OptionConstants.heapSizeChoices.get(0).equals(slaveMemory)) {
 670  0 jvmArgs.add("-Xmx" + slaveMemory + "M");
 671    }
 672  180 String slaveArgs = DrJava.getConfig().getSetting(OptionConstants.SLAVE_JVM_ARGS);
 673  180 if (PlatformFactory.ONLY.isMacPlatform()) {
 674  0 jvmArgs.add("-Xdock:name=Interactions");
 675    }
 676   
 677    // add additional boot class path items specified by the selected compiler
 678  180 for (File f: _interactionsModel.getCompilerBootClassPath()) {
 679  0 try {
 680    // NOTE: this is a work-around
 681    // it seems like it's impossible to pass long file names here on Windows
 682    // so we are using a clumsy method that determines the short file name
 683  0 File shortF = FileOps.getShortFile(f);
 684  0 jvmArgs.add("-Xbootclasspath/a:"+shortF.getAbsolutePath().replace(File.separatorChar, '/'));
 685    }
 686    catch(IOException ioe) {
 687    // TODO: figure out what to do here. error? warn?
 688    }
 689    }
 690   
 691  180 jvmArgs.addAll(ArgumentTokenizer.tokenize(slaveArgs));
 692   
 693  180 JVMBuilder jvmb = new JVMBuilder(_startupClassPath).directory(dir).jvmArguments(jvmArgs);
 694   
 695    // extend classpath if JUnit/ConcJUnit location specified
 696  180 File junitLocation = DrJava.getConfig().getSetting(OptionConstants.JUNIT_LOCATION);
 697  180 boolean junitLocationConfigured =
 698    (edu.rice.cs.drjava.model.junit.ConcJUnitUtils.isValidJUnitFile(junitLocation) ||
 699    edu.rice.cs.drjava.model.junit.ConcJUnitUtils.isValidConcJUnitFile(junitLocation));
 700  180 if (DrJava.getConfig().getSetting(OptionConstants.JUNIT_LOCATION_ENABLED) && // enabled
 701    !junitLocationConfigured && // not valid
 702    (junitLocation != null) && // not null
 703    (!FileOps.NULL_FILE.equals(junitLocation)) && // not NULL_FILE
 704    (junitLocation.exists())) { // but exists
 705    // invalid file, clear setting
 706  0 DrJava.getConfig().setSetting(OptionConstants.JUNIT_LOCATION_ENABLED, false);
 707  0 junitLocationConfigured = false;
 708    }
 709  180 ArrayList<File> extendedClassPath = new ArrayList<File>();
 710  180 if (DrJava.getConfig().getSetting(OptionConstants.JUNIT_LOCATION_ENABLED) &&
 711    junitLocationConfigured) {
 712  0 extendedClassPath.add(junitLocation);
 713    }
 714  1980 for(File f: jvmb.classPath()) { extendedClassPath.add(f); }
 715  180 jvmb = jvmb.classPath(edu.rice.cs.plt.iter.IterUtil.asSizedIterable(extendedClassPath));
 716   
 717    // add Java properties controlling ConcJUnit
 718  180 Map<String, String> props = jvmb.propertiesCopy();
 719   
 720    // settings are mutually exclusive
 721  180 boolean all = DrJava.getConfig().getSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED).
 722    equals(OptionConstants.ConcJUnitCheckChoices.ALL);
 723  180 boolean noLucky = DrJava.getConfig().getSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED).
 724    equals(OptionConstants.ConcJUnitCheckChoices.NO_LUCKY);
 725  180 boolean onlyThreads = DrJava.getConfig().getSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED).
 726    equals(OptionConstants.ConcJUnitCheckChoices.ONLY_THREADS);
 727  180 boolean none = DrJava.getConfig().getSetting(OptionConstants.CONCJUNIT_CHECKS_ENABLED).
 728    equals(OptionConstants.ConcJUnitCheckChoices.NONE);
 729    // "threads" is enabled as long as the setting isn't NONE
 730  180 props.put("edu.rice.cs.cunit.concJUnit.check.threads.enabled",
 731    new Boolean(!none).toString());
 732    // "join" is enabled for ALL and NO_LUCKY
 733  180 props.put("edu.rice.cs.cunit.concJUnit.check.join.enabled",
 734    new Boolean(all || noLucky).toString());
 735    // "lucky" is enabled only for ALL
 736  180 props.put("edu.rice.cs.cunit.concJUnit.check.lucky.enabled",
 737    new Boolean(all).toString());
 738   
 739  180 jvmb = jvmb.properties(props);
 740   
 741  180 invokeSlave(jvmb);
 742    }
 743   
 744    /** Returns the debug port to use, as specified by the model. Returns -1 if no usable port could be found. */
 745  180 private int _getDebugPort() {
 746  180 int port = -1;
 747  180 try { port = _interactionsModel.getDebugPort(); }
 748    catch (IOException ioe) {
 749    /* Can't find port; don't use debugger */
 750    }
 751  180 return port;
 752    }
 753   
 754    /** Lets the model know if any exceptions occur while communicating with the Interpreter JVM. */
 755  3 private void _handleRemoteException(RemoteException e) {
 756  3 if (e instanceof UnmarshalException) {
 757    /* Interpreter JVM has disappeared (perhaps reset); just ignore the error. */
 758  3 if (e.getCause() instanceof EOFException) return;
 759    /* Deals with bug 2688586: Reset during debugging throws UnmarshalException
 760    * We may want to extend this to all kinds of SocketExceptions. */
 761  0 if ((e.getCause() instanceof SocketException) &&
 762  0 (e.getCause().getMessage().equals("Connection reset"))) return;
 763    }
 764  0 DrJavaErrorHandler.record(e);
 765    }
 766   
 767    /*
 768    * Helper classes
 769    */
 770   
 771    /** State-based implementation of the starting/stopping functionality. */
 772    private abstract class State {
 773    /**
 774    * Get the current interpreter -- null if unavailable. Block if necessary.
 775    * @param used Whether this access will lead to a used JVM -- one that should be reset even when not forced
 776    */
 777    public abstract InterpreterJVMRemoteI interpreter(boolean used);
 778    /** Ensure that the interpreter is starting or running. Block if necessary. */
 779    public abstract void start();
 780    /** Ensure that the interpreter is stopping or not running. Block if necessary. */
 781    public abstract void stop();
 782    /**
 783    * Ensure that the interpreter is stopping or not running, to be started again. Block if necessary.
 784    * @param force Whether an unused, running JVM should be restarted
 785    */
 786    public abstract void restart(boolean force);
 787    public abstract void dispose();
 788    /** React to a completed startup. */
 789  0 public void started(InterpreterJVMRemoteI i) { throw new IllegalStateException("Unexpected started() call"); }
 790    /** React to a failed startup. */
 791  0 public void startFailed(Exception e) { throw new IllegalStateException("Unexpected startFailed() call"); }
 792    /** React to a completed shutdown (requested or spontaneous). */
 793  135 public void stopped(int status) { throw new IllegalStateException("Unexpected stopped() call"); }
 794    }
 795   
 796    /** Fresh, hasn't yet been started. */
 797    private class FreshState extends State {
 798  0 public InterpreterJVMRemoteI interpreter(boolean used) { return null; }
 799  159 public void start() {
 800  159 if (_state.compareAndSet(this, new StartingState())) { _doStartup(); }
 801  0 else { _state.value().start(); }
 802    }
 803  0 public void stop() { }
 804  0 public void restart(boolean force) { start(); }
 805  0 public void dispose() {
 806  0 if (_state.compareAndSet(this, new DisposedState())) { MainJVM.super.dispose(); }
 807  0 else { _state.value().dispose(); }
 808    }
 809    }
 810   
 811    /** Has been started, waiting for startup to complete. */
 812    private class StartingState extends State {
 813    private final int _failures;
 814  180 public StartingState() { _failures = 0; }
 815  0 private StartingState(int failures) { _failures = failures; }
 816   
 817  10 public InterpreterJVMRemoteI interpreter(boolean used) {
 818  10 try { return _state.ensureNotState(this, STARTUP_TIMEOUT).interpreter(used); }
 819  0 catch (TimeoutException e) { return null; }
 820  0 catch (InterruptedException e) { throw new UnexpectedException(e); }
 821    }
 822   
 823  0 public void start() { }
 824   
 825  0 public void restart(boolean force) {
 826  0 try { _state.ensureNotState(this, STARTUP_TIMEOUT).restart(force); }
 827  0 catch (Exception e) { throw new UnexpectedException(e); }
 828    }
 829   
 830  0 public void stop() {
 831  0 try { _state.ensureNotState(this, STARTUP_TIMEOUT).stop(); }
 832  0 catch (Exception e) { throw new UnexpectedException(e); }
 833    }
 834   
 835  0 public void dispose() { stop(); _state.value().dispose(); }
 836   
 837  180 @Override public void started(InterpreterJVMRemoteI i) {
 838  180 if (_state.compareAndSet(this, new FreshRunningState(i))) {
 839  180 boolean enforceAllAccess = DrJava.getConfig().getSetting(OptionConstants.DYNAMICJAVA_ACCESS_CONTROL)
 840    .equals(OptionConstants.DynamicJavaAccessControlChoices.PRIVATE_AND_PACKAGE); // "all"
 841  180 try { i.setEnforceAllAccess(enforceAllAccess); }
 842  0 catch (RemoteException re) { _handleRemoteException(re); }
 843   
 844  180 boolean enforcePrivateAccess = !DrJava.getConfig().getSetting(OptionConstants.DYNAMICJAVA_ACCESS_CONTROL)
 845    .equals(OptionConstants.DynamicJavaAccessControlChoices.DISABLED); // not "none"
 846  180 try { i.setEnforcePrivateAccess(enforcePrivateAccess); }
 847  0 catch (RemoteException re) { _handleRemoteException(re); }
 848   
 849  180 Boolean requireSemicolon = DrJava.getConfig().getSetting(OptionConstants.DYNAMICJAVA_REQUIRE_SEMICOLON);
 850  180 try { i.setRequireSemicolon(requireSemicolon); }
 851  0 catch (RemoteException re) { _handleRemoteException(re); }
 852   
 853  180 Boolean requireVariableType = DrJava.getConfig().getSetting(OptionConstants.DYNAMICJAVA_REQUIRE_VARIABLE_TYPE);
 854  180 try { i.setRequireVariableType(requireVariableType); }
 855  0 catch (RemoteException re) { _handleRemoteException(re); }
 856   
 857    // Note that _workingDir isn't guaranteed to be the dir at the time startup began. Is that a problem?
 858    // (Is the user ever going to see a working dir message that doesn't match the actual setting?)
 859  180 _interactionsModel.interpreterReady(_workingDir);
 860  180 _junitModel.junitJVMReady();
 861    }
 862  0 else { _state.value().started(i); }
 863    }
 864   
 865  0 @Override public void startFailed(Exception e) {
 866  0 int count = _failures + 1;
 867  0 if (count < MAX_STARTUP_FAILURES) {
 868  0 if (_state.compareAndSet(this, new StartingState(count))) { _doStartup(); }
 869  0 else { _state.value().startFailed(e); }
 870    }
 871    else {
 872  0 if (_state.compareAndSet(this, new FreshState())) { _interactionsModel.interpreterWontStart(e); }
 873  0 else { _state.value().startFailed(e); }
 874    }
 875    }
 876    }
 877   
 878    /** Has an active interpreter available. */
 879    private class RunningState extends State {
 880    protected final InterpreterJVMRemoteI _interpreter;
 881  219 public RunningState(InterpreterJVMRemoteI interpreter) { _interpreter = interpreter; }
 882  553 public InterpreterJVMRemoteI interpreter(boolean used) { return _interpreter; }
 883  0 public void start() { }
 884   
 885  156 public void stop() {
 886  156 if (_state.compareAndSet(this, new StoppingState())) { quitSlave(); }
 887  0 else { _state.value().stop(); }
 888    }
 889   
 890  21 public void restart(boolean force) {
 891  21 if (_state.compareAndSet(this, new RestartingState())) {
 892  21 _interactionsModel.interpreterResetting();
 893  21 quitSlave();
 894    }
 895  0 else { _state.value().restart(force); }
 896    }
 897   
 898  156 public void dispose() { stop(); _state.value().dispose(); }
 899   
 900  1 @Override public void stopped(int status) {
 901  1 if (_state.compareAndSet(this, new RestartingState())) {
 902  1 _interactionsModel.replCalledSystemExit(status);
 903  1 _interactionsModel.interpreterResetting();
 904    }
 905  1 _state.value().stopped(status); // delegate whether state changed here or in another thread
 906    }
 907    }
 908   
 909    /** Variant of RunningState where the interpreter JVM has not yet been used. */
 910    private class FreshRunningState extends RunningState {
 911  180 public FreshRunningState(InterpreterJVMRemoteI interpreter) { super(interpreter); }
 912  499 @Override public InterpreterJVMRemoteI interpreter(boolean used) {
 913  499 if (used) {
 914  39 _state.compareAndSet(this, new RunningState(_interpreter));
 915  39 return _state.value().interpreter(used); // delegate whether state changed here or in another thread
 916    }
 917  460 else { return super.interpreter(used); }
 918    }
 919  13 @Override public void restart(boolean force) {
 920  8 if (force) { super.restart(force); }
 921    else {
 922    // otherwise, ignore and say that we are ready
 923  5 _interactionsModel.interpreterReady(_workingDir);
 924    }
 925    }
 926    }
 927   
 928    /** Waiting for stop, should automatically start when that happens. */
 929    private class RestartingState extends State {
 930   
 931  8 public InterpreterJVMRemoteI interpreter(boolean used) {
 932  8 try { return _state.ensureNotState(this, STARTUP_TIMEOUT).interpreter(used); }
 933  1 catch (TimeoutException e) { return null; }
 934  0 catch (InterruptedException e) { throw new UnexpectedException(e); }
 935    }
 936   
 937  0 public void start() { }
 938   
 939  0 public void stop() {
 940  0 if (!_state.compareAndSet(this, new StoppingState())) { _state.value().stop(); }
 941    }
 942   
 943  0 public void restart(boolean force) { }
 944   
 945  1 public void dispose() {
 946  1 if (_state.compareAndSet(this, new DisposedState())) { MainJVM.super.dispose(); }
 947  0 else { _state.value().dispose(); }
 948    }
 949   
 950  21 @Override public void stopped(int status) {
 951  21 if (_state.compareAndSet(this, new StartingState())) { _doStartup(); }
 952  0 else { _state.value().stopped(status); }
 953    }
 954    }
 955   
 956    /** Waiting for stop, no restart. */
 957    private class StoppingState extends State {
 958  0 public InterpreterJVMRemoteI interpreter(boolean used) { return null; }
 959   
 960  0 public void start() {
 961  0 try { _state.ensureNotState(this, STARTUP_TIMEOUT).start(); }
 962  0 catch (Exception e) { throw new UnexpectedException(e); }
 963    }
 964   
 965  0 public void stop() { }
 966   
 967  0 public void restart(boolean force) {
 968  0 if (!_state.compareAndSet(this, new RestartingState())) { _state.value().restart(force); }
 969    }
 970   
 971  156 public void dispose() {
 972  156 if (_state.compareAndSet(this, new DisposedState())) { MainJVM.super.dispose(); }
 973  0 else { _state.value().dispose(); }
 974    }
 975   
 976  0 @Override public void stopped(int status) {
 977  0 if (!_state.compareAndSet(this, new FreshState())) { _state.value().stopped(status); }
 978    }
 979    }
 980   
 981    private class DisposedState extends State {
 982  0 public InterpreterJVMRemoteI interpreter(boolean used) { throw new IllegalStateException("MainJVM is disposed"); }
 983  0 public void start() { throw new IllegalStateException("MainJVM is disposed"); }
 984  0 public void stop() { throw new IllegalStateException("MainJVM is disposed"); }
 985  0 public void restart(boolean force) { throw new IllegalStateException("MainJVM is disposed"); }
 986  0 public void dispose() { }
 987  0 public void stopped() { /* may occur if transitioned here from Restarting or Stopping */ }
 988    }
 989   
 990   
 991    /** Performs the appropriate action to return any type of result from a call to interpret back to the GlobalModel. */
 992    private class ResultHandler implements InterpretResult.Visitor<Void> {
 993    /** Lets the model know that void was returned. */
 994  9 public Void forNoValue() {
 995  9 _interactionsModel.replReturnedVoid();
 996  9 return null;
 997    }
 998   
 999    /** Calls replReturnedResult() */
 1000  0 public Void forObjectValue(String objString, String objTypeString) {
 1001  0 _interactionsModel.replReturnedResult(objString, InteractionsDocument.OBJECT_RETURN_STYLE);
 1002  0 return null;
 1003    }
 1004   
 1005    /** Calls replReturnedResult() */
 1006  5 public Void forStringValue(String s) {
 1007  5 _interactionsModel.replReturnedResult('"' + s + '"', InteractionsDocument.STRING_RETURN_STYLE);
 1008  5 return null;
 1009    }
 1010   
 1011    /** Calls replReturnedResult() */
 1012  0 public Void forCharValue(Character c) {
 1013  0 _interactionsModel.replReturnedResult("'" + c + "'", InteractionsDocument.CHARACTER_RETURN_STYLE);
 1014  0 return null;
 1015    }
 1016   
 1017    /** Calls replReturnedResult() */
 1018  18 public Void forNumberValue(Number n) {
 1019  18 _interactionsModel.replReturnedResult(n.toString(), InteractionsDocument.NUMBER_RETURN_STYLE);
 1020  18 return null;
 1021    }
 1022   
 1023    /** Calls replReturnedResult() */
 1024  0 public Void forBooleanValue(Boolean b) {
 1025  0 _interactionsModel.replReturnedResult(b.toString(), InteractionsDocument.OBJECT_RETURN_STYLE);
 1026  0 return null;
 1027    }
 1028   
 1029    /** Calls replThrewException() */
 1030  0 public Void forEvalException(String message, StackTraceElement[] stackTrace) {
 1031    // TODO: restore location/syntax highlighting functionality
 1032  0 _interactionsModel.replThrewException(message, stackTrace);
 1033  0 return null;
 1034    }
 1035   
 1036    /** Calls replThrewException() */
 1037  7 public Void forException(String message) {
 1038    // TODO: restore location/syntax highlighting functionality
 1039  7 _interactionsModel.replThrewException(message);
 1040  7 return null;
 1041    }
 1042   
 1043  0 public Void forUnexpectedException(Throwable t) {
 1044  0 _interactionsModel.replReturnedVoid();
 1045  0 throw new UnexpectedException(t);
 1046    }
 1047   
 1048  0 public Void forBusy() {
 1049  0 _interactionsModel.replReturnedVoid();
 1050  0 throw new UnexpectedException("MainJVM.interpret() called when InterpreterJVM was busy!");
 1051    }
 1052    }
 1053   
 1054    /** InteractionsModel which does not react to events. */
 1055    public static class DummyInteractionsModel implements InteractionsModelCallback {
 1056  2 public int getDebugPort() throws IOException { return -1; }
 1057  0 public void replSystemOutPrint(String s) { }
 1058  0 public void replSystemErrPrint(String s) { }
 1059  0 public String getConsoleInput() {
 1060  0 throw new IllegalStateException("Cannot request input from dummy interactions model!");
 1061    }
 1062  0 public void setInputListener(InputListener il) {
 1063  0 throw new IllegalStateException("Cannot set the input listener of dummy interactions model!");
 1064    }
 1065  0 public void changeInputListener(InputListener from, InputListener to) {
 1066  0 throw new IllegalStateException("Cannot change the input listener of dummy interactions model!");
 1067    }
 1068  0 public void replReturnedVoid() { }
 1069  0 public void replReturnedResult(String result, String style) { }
 1070  0 public void replThrewException(String message, StackTraceElement[] stackTrace) { }
 1071  0 public void replThrewException(String message) { }
 1072  0 public void replReturnedSyntaxError(String errorMessage, String interaction, int startRow, int startCol, int endRow,
 1073    int endCol) { }
 1074  0 public void replCalledSystemExit(int status) { }
 1075  1 public void interpreterResetting() { }
 1076  0 public void interpreterResetFailed(Throwable th) { }
 1077  0 public void interpreterWontStart(Exception e) { }
 1078  2 public void interpreterReady(File wd) { }
 1079  2 public List<File> getCompilerBootClassPath() { return new ArrayList<File>(); }
 1080  0 public String transformCommands(String interactionsString) { return interactionsString; }
 1081    }
 1082   
 1083    /** JUnitModel which does not react to events. */
 1084    public static class DummyJUnitModel implements JUnitModelCallback {
 1085  0 public void nonTestCase(boolean isTestAll, boolean didCompileFail) { }
 1086  0 public void classFileError(ClassFileError e) { }
 1087  0 public void testSuiteStarted(int numTests) { }
 1088  0 public void testStarted(String testName) { }
 1089  0 public void testEnded(String testName, boolean wasSuccessful, boolean causedError) { }
 1090  0 public void testSuiteEnded(JUnitError[] errors) { }
 1091  0 public File getFileForClassName(String className) { return null; }
 1092  0 public Iterable<File> getClassPath() { return IterUtil.empty(); }
 1093  3 public void junitJVMReady() { }
 1094    }
 1095   
 1096    /** DebugModelCallback which does not react to events. */
 1097    public static class DummyDebugModel implements DebugModelCallback {
 1098  0 public void notifyDebugInterpreterAssignment(String name) {
 1099    }
 1100    }
 1101    }