Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 882   Methods: 91
NCLOC: 466   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
DefaultGlobalModel.java 47.7% 74.9% 59.3% 65.9%
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;
 38   
 39   
 40    import java.awt.EventQueue;
 41   
 42    import java.io.*;
 43   
 44    import java.util.LinkedList;
 45    import java.util.List;
 46    import java.util.Vector;
 47    import java.util.Map;
 48    import java.util.TreeMap;
 49   
 50    import edu.rice.cs.drjava.DrJava;
 51   
 52    import edu.rice.cs.drjava.model.FileSaveSelector;
 53    import edu.rice.cs.drjava.model.compiler.DummyCompilerListener;
 54    import edu.rice.cs.drjava.config.BooleanOption;
 55    import edu.rice.cs.drjava.model.definitions.ClassNameNotFoundException;
 56    import edu.rice.cs.drjava.model.definitions.InvalidPackageException;
 57    import edu.rice.cs.drjava.model.debug.Breakpoint;
 58    import edu.rice.cs.drjava.model.debug.Debugger;
 59    import edu.rice.cs.drjava.model.debug.DebugException;
 60    import edu.rice.cs.drjava.model.debug.NoDebuggerAvailable;
 61    import edu.rice.cs.drjava.model.debug.DebugListener;
 62    import edu.rice.cs.drjava.model.debug.DebugWatchData;
 63    import edu.rice.cs.drjava.model.debug.DebugThreadData;
 64    import edu.rice.cs.drjava.model.javadoc.JavadocModel;
 65    import edu.rice.cs.drjava.model.javadoc.NoJavadocAvailable;
 66    import edu.rice.cs.drjava.model.repl.DefaultInteractionsModel;
 67    import edu.rice.cs.drjava.model.repl.DummyInteractionsListener;
 68    import edu.rice.cs.drjava.model.repl.InteractionsDocument;
 69    import edu.rice.cs.drjava.model.repl.InteractionsDJDocument;
 70    import edu.rice.cs.drjava.model.repl.InteractionsListener;
 71    import edu.rice.cs.drjava.model.repl.InteractionsScriptModel;
 72    import edu.rice.cs.drjava.model.repl.newjvm.MainJVM;
 73    import edu.rice.cs.drjava.model.compiler.CompilerListener;
 74    import edu.rice.cs.drjava.model.compiler.CompilerModel;
 75    import edu.rice.cs.drjava.model.compiler.DefaultCompilerModel;
 76    import edu.rice.cs.drjava.model.compiler.CompilerInterface;
 77    import edu.rice.cs.drjava.model.junit.DefaultJUnitModel;
 78    import edu.rice.cs.drjava.model.junit.JUnitModel;
 79    import edu.rice.cs.util.text.ConsoleDocument;
 80   
 81    import edu.rice.cs.plt.reflect.JavaVersion;
 82    import edu.rice.cs.plt.reflect.ReflectUtil;
 83    import edu.rice.cs.plt.iter.IterUtil;
 84    import edu.rice.cs.plt.io.IOUtil;
 85    import edu.rice.cs.plt.tuple.Pair;
 86   
 87    import edu.rice.cs.util.FileOpenSelector;
 88    import edu.rice.cs.util.FileOps;
 89    import edu.rice.cs.util.NullFile;
 90    import edu.rice.cs.util.AbsRelFile;
 91    import edu.rice.cs.util.OperationCanceledException;
 92    import edu.rice.cs.util.UnexpectedException;
 93    import edu.rice.cs.util.swing.Utilities;
 94   
 95    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 96   
 97    /** Handles the bulk of DrJava's program logic. The UI components interface with the GlobalModel through its public
 98    * methods, and the GlobalModel responds via the GlobalModelListener interface. This removes the dependency on the
 99    * UI for the logical flow of the program's features. With the current implementation, we can finally test the compile
 100    * functionality of DrJava, along with many other things. <p>
 101    * @version $Id: DefaultGlobalModel.java 5436 2011-08-02 06:58:19Z mgricken $
 102    */
 103    public class DefaultGlobalModel extends AbstractGlobalModel {
 104    /* FIELDS */
 105   
 106    /* static Log _log inherited from AbstractGlobalModel */
 107   
 108    /* Interpreter fields */
 109   
 110    /** The document used in the Interactions model. */
 111    protected final InteractionsDJDocument _interactionsDocument;
 112   
 113    /** RMI interface to the Interactions JVM. */
 114    final MainJVM _jvm;
 115   
 116    private final Thread _jvmStarter; // thread that invokes _jvm.startInterpreterJVM()
 117   
 118    /** Interface between the InteractionsDocument and the JavaInterpreter, which runs in a separate JVM. */
 119    protected final DefaultInteractionsModel _interactionsModel;
 120   
 121    /** Core listener attached to interactions model */
 122    protected InteractionsListener _interactionsListener = new InteractionsListener() {
 123  35 public void interactionStarted() { }
 124   
 125  33 public void interactionEnded() { }
 126   
 127  0 public void interactionErrorOccurred(int offset, int length) { }
 128   
 129  21 public void interpreterResetting() { }
 130   
 131  25 public void interpreterReady(File wd) {
 132  25 File buildDir = _state.getBuildDirectory();
 133  25 if (buildDir != null) {
 134    // System.out.println("adding for reset: " + _state.getBuildDirectory().getAbsolutePath());
 135  25 _jvm.addBuildDirectoryClassPath(IOUtil.attemptAbsoluteFile(buildDir));
 136    }
 137    }
 138   
 139  0 public void interpreterResetFailed(Throwable t) { }
 140   
 141  1 public void interpreterExited(int status) { }
 142   
 143  1 public void interpreterChanged(boolean inProgress) { }
 144   
 145  0 public void interactionIncomplete() { }
 146    };
 147   
 148    private CompilerListener _clearInteractionsListener = new DummyCompilerListener() {
 149  56 public void compileEnded(File workDir, List<? extends File> excludedFiles) {
 150    // Only clear interactions if there were no errors and unit testing is not in progress
 151  56 if ( (_compilerModel.getNumErrors() == 0 || _compilerModel.getCompilerErrorModel().hasOnlyWarnings())
 152    && ! _junitModel.isTestInProgress() && _resetAfterCompile) {
 153    // Utilities.show("compileEnded called in clearInteractionsListener");
 154  10 resetInteractions(workDir); // use same working directory as current interpreter
 155    }
 156    }
 157  0 public void activeCompilerChanged() {
 158  0 File workDir = _interactionsModel.getWorkingDirectory();
 159  0 resetInteractions(workDir, true); // use same working directory as current interpreter
 160    }
 161    };
 162   
 163    // ---- Compiler Fields ----
 164   
 165    /** CompilerModel manages all compiler functionality. */
 166    private final CompilerModel _compilerModel;
 167   
 168    /** Whether or not to reset the interactions JVM after compiling. Should only be false in test cases. */
 169    private volatile boolean _resetAfterCompile = true;
 170   
 171    /** Number of errors in last compilation. compilerModel._numErrors is trashed when the compile model is reset. */
 172    private volatile int _numCompErrors = 0;
 173   
 174    /* JUnit Fields */
 175   
 176    /** JUnitModel manages all JUnit functionality. */
 177    private final DefaultJUnitModel _junitModel;
 178   
 179    /* Javadoc Fields */
 180   
 181    /** Manages all Javadoc functionality. */
 182    protected volatile JavadocModel _javadocModel;
 183   
 184    /* Debugger Fields */
 185   
 186    /** Interface to the integrated debugger. If unavailable, set NoDebuggerAvailable.ONLY. */
 187    private volatile Debugger _debugger;
 188   
 189    /* CONSTRUCTORS */
 190    /** Constructs a new GlobalModel. Creates a new MainJVM and starts its Interpreter JVM. */
 191  157 public DefaultGlobalModel() {
 192  157 Iterable<? extends JDKToolsLibrary> tools = findLibraries();
 193  157 List<CompilerInterface> compilers = new LinkedList<CompilerInterface>();
 194  157 _debugger = null;
 195  157 _javadocModel = null;
 196  157 for (JDKToolsLibrary t : tools) {
 197    // only add compiler if it supports JAVA_5
 198  785 if (t.compiler().isAvailable() && t.version().supports(JavaVersion.JAVA_5)) {
 199  471 compilers.add(t.compiler());
 200    }
 201  157 if (_debugger == null && t.debugger().isAvailable()) { _debugger = t.debugger(); }
 202  157 if (_javadocModel == null && t.javadoc().isAvailable()) { _javadocModel = t.javadoc(); }
 203    }
 204  0 if (_debugger == null) { _debugger = NoDebuggerAvailable.ONLY; }
 205  0 if (_javadocModel == null) { _javadocModel = new NoJavadocAvailable(this); }
 206   
 207  157 File workDir = Utilities.TEST_MODE ? new File(System.getProperty("user.home")) : getWorkingDirectory();
 208  157 _jvm = new MainJVM(workDir);
 209    // AbstractMasterJVM._log.log(this + " has created a new MainJVM");
 210  157 _compilerModel = new DefaultCompilerModel(this, compilers);
 211  157 _junitModel = new DefaultJUnitModel(_jvm, _compilerModel, this);
 212  157 _interactionsDocument = new InteractionsDJDocument(_notifier);
 213   
 214  157 _interactionsModel = new DefaultInteractionsModel(this, _jvm, _interactionsDocument, workDir);
 215  157 _interactionsModel.addListener(_interactionsListener);
 216  157 _jvm.setInteractionsModel(_interactionsModel);
 217  157 _jvm.setJUnitModel(_junitModel);
 218   
 219  157 _setupDebugger();
 220   
 221    // Chain notifiers so that all events also go to GlobalModelListeners.
 222  157 _interactionsModel.addListener(_notifier);
 223  157 _compilerModel.addListener(_notifier);
 224  157 _junitModel.addListener(_notifier);
 225  157 _javadocModel.addListener(_notifier);
 226   
 227    // Listen to compiler to clear interactions appropriately.
 228    // XXX: The tests need this to be registered after _notifier, sadly.
 229    // This is obnoxiously order-dependent, but it works for now.
 230  157 _compilerModel.addListener(_clearInteractionsListener);
 231   
 232  157 _jvmStarter = new Thread("Start interpreter JVM") {
 233  157 public void run() { _jvm.startInterpreterJVM(); }
 234    };
 235  157 _jvmStarter.start();
 236   
 237    // Lightweight parsing has been disabled until we have something that is beneficial and works better in the background.
 238    // _parsingControl = new DefaultLightWeightParsingControl(this);
 239    }
 240   
 241    // makes the version coarser, if desired: if DISPLAY_ALL_COMPILER_VERSIONS is disabled, then only
 242    // the major version and the vendor will be considered
 243  5966 private static JavaVersion.FullVersion coarsenVersion(JavaVersion.FullVersion tVersion) {
 244  5966 BooleanOption displayAllOption = edu.rice.cs.drjava.config.OptionConstants.DISPLAY_ALL_COMPILER_VERSIONS;
 245  5966 if (!DrJava.getConfig().getSetting(displayAllOption).booleanValue()) {
 246  5966 tVersion = tVersion.onlyMajorVersionAndVendor();
 247    }
 248  5966 return tVersion;
 249    }
 250   
 251    // A pair of version and descriptor.
 252    // If the descriptor is something different than JDKDescriptor.NONE, then this pair will always
 253    // return false for equals(), except if it is compared to the identical pair.
 254    private static class LibraryKey implements Comparable<LibraryKey> {
 255    public static final int PRIORITY_BUILTIN = 0;
 256    public static final int PRIORITY_SEARCH = 1;
 257    public static final int PRIORITY_RUNTIME = 2;
 258    public static final int PRIORITY_CONFIG = 3;
 259    protected final int _priority; // 0 = search, 1 = runtime, 2 = config
 260    protected final JavaVersion.FullVersion _first;
 261    protected final JDKDescriptor _second;
 262   
 263  3454 public LibraryKey(int priority, JavaVersion.FullVersion first, JDKDescriptor second) {
 264  3454 _priority = priority;
 265  3454 _first = first;
 266  3454 _second = second;
 267    }
 268   
 269  0 public boolean equals(Object o) {
 270    // identity --> true
 271  0 if (this == o) { return true; }
 272    // different class --> false
 273  0 else if (o == null || !getClass().equals(o.getClass())) { return false; }
 274    else {
 275  0 LibraryKey cast = (LibraryKey) o;
 276    // only true if both versions are equal and both descriptors are NONE
 277  0 return
 278    (_priority == cast._priority) &&
 279  0 (_first == null ? cast._first == null : _first.equals(cast._first)) &&
 280  0 (_second == null ? cast._second == null :
 281    ((_second==JDKDescriptor.NONE) && (cast._second==JDKDescriptor.NONE)));
 282    }
 283    }
 284   
 285  0 public String toString() {
 286  0 return "priority "+_priority+", version "+_first.versionString()+" "+_first.maintenance()+" "+_first.update()+" "+_first.release()+" "+_first.vendor()+" "+_first.location()+", descriptor "+_second.getName();
 287    }
 288   
 289  0 public int hashCode() {
 290  0 return
 291    _priority ^
 292  0 (_first == null ? 0 : _first.hashCode()) ^
 293  0 (_second == null ? 0 : _second.hashCode() << 1) ^
 294    getClass().hashCode();
 295    }
 296   
 297  6437 public int compareTo(LibraryKey o) {
 298  6437 int result = _priority - o._priority;
 299  6437 if (result == 0) {
 300  4710 result = _first.compareTo(o._first);
 301    }
 302  6437 if (result == 0) {
 303  1884 if (_second==JDKDescriptor.NONE) { // identity
 304  1884 if (o._second==JDKDescriptor.NONE) { // identity
 305  1884 result = 0;
 306    }
 307    else {
 308    // this is NONE, other is something else; prefer NONE
 309  0 result = 1;
 310    }
 311    }
 312  0 else if (o._second==JDKDescriptor.NONE) { // identity
 313    // other is NONE, this is something else; prefer NONE
 314  0 result = -1;
 315    }
 316    else {
 317  0 result = _second.toString().compareTo(o._second.toString());
 318    }
 319    }
 320  6437 return result;
 321    }
 322    }
 323   
 324    // return a new version-descriptor pair for a library
 325  3454 private LibraryKey getLibraryKey(int priority, JDKToolsLibrary lib) {
 326  3454 return new LibraryKey(priority, coarsenVersion(lib.version()), lib.jdkDescriptor());
 327    }
 328   
 329  157 private Iterable<JDKToolsLibrary> findLibraries() {
 330    // Order to return: config setting, runtime (if different version), from search (if different versions)
 331   
 332    // We could give priority to libraries that have both available compilers and debuggers, but since this will
 333    // almost always be true, it seems like more trouble than it is worth
 334   
 335    // map is sorted by version, lowest-to-highest
 336  157 Map<LibraryKey, JDKToolsLibrary> results = new TreeMap<LibraryKey, JDKToolsLibrary>();
 337   
 338  157 File configTools = DrJava.getConfig().getSetting(JAVAC_LOCATION);
 339  157 if (configTools != FileOps.NULL_FILE) {
 340  0 JDKToolsLibrary fromConfig = JarJDKToolsLibrary.makeFromFile(configTools, this, JDKDescriptor.NONE);
 341  0 if (fromConfig.isValid()) {
 342  0 JarJDKToolsLibrary.msg("From config: "+fromConfig);
 343  0 results.put(getLibraryKey(LibraryKey.PRIORITY_CONFIG, fromConfig), fromConfig);
 344    }
 345  0 else { JarJDKToolsLibrary.msg("From config: invalid "+fromConfig); }
 346    }
 347  157 else { JarJDKToolsLibrary.msg("From config: not set"); }
 348   
 349  157 Iterable<JDKToolsLibrary> allFromRuntime = JDKToolsLibrary.makeFromRuntime(this);
 350   
 351  157 for(JDKToolsLibrary fromRuntime: allFromRuntime) {
 352  157 if (fromRuntime.isValid()) {
 353  157 if (!results.containsKey(getLibraryKey(LibraryKey.PRIORITY_RUNTIME, fromRuntime))) {
 354  157 JarJDKToolsLibrary.msg("From runtime: "+fromRuntime);
 355  157 results.put(getLibraryKey(LibraryKey.PRIORITY_RUNTIME, fromRuntime), fromRuntime);
 356    }
 357  0 else { JarJDKToolsLibrary.msg("From runtime: duplicate "+fromRuntime); }
 358    }
 359  0 else { JarJDKToolsLibrary.msg("From runtime: invalid "+fromRuntime); }
 360    }
 361   
 362  157 Iterable<JarJDKToolsLibrary> fromSearch = JarJDKToolsLibrary.search(this);
 363  157 for (JDKToolsLibrary t : fromSearch) {
 364  2512 JavaVersion.FullVersion tVersion = t.version();
 365  2512 JarJDKToolsLibrary.msg("From search: "+t);
 366  2512 JavaVersion.FullVersion coarsenedVersion = coarsenVersion(tVersion);
 367  2512 JarJDKToolsLibrary.msg("\ttVersion: "+tVersion+" "+tVersion.vendor());
 368  2512 JarJDKToolsLibrary.msg("\tcoarsenedVersion: "+coarsenedVersion+" "+coarsenedVersion.vendor());
 369    // give a lower priority to built-in compilers
 370  2512 int priority = (edu.rice.cs.util.FileOps.getDrJavaFile().equals(tVersion.location()))?LibraryKey.PRIORITY_BUILTIN:LibraryKey.PRIORITY_SEARCH;
 371  2512 if (!results.containsKey(getLibraryKey(priority, t))) {
 372  628 JarJDKToolsLibrary.msg("\tadded");
 373  628 results.put(getLibraryKey(priority, t), t);
 374    }
 375  1884 else { JarJDKToolsLibrary.msg("\tduplicate"); }
 376    }
 377   
 378  157 return IterUtil.reverse(results.values());
 379    }
 380   
 381    // public void junitAll() { _state.junitAll(); }
 382   
 383    /** Sets the build directory for a project. */
 384  2 public void setBuildDirectory(File f) {
 385  2 _state.setBuildDirectory(f);
 386  2 if (f != FileOps.NULL_FILE) {
 387    // System.out.println("adding: " + f.getAbsolutePath());
 388  0 _jvm.addBuildDirectoryClassPath(IOUtil.attemptAbsoluteFile(f));
 389    }
 390   
 391  2 _notifier.projectBuildDirChanged();
 392  2 setProjectChanged(true);
 393  2 setClassPathChanged(true);
 394    }
 395   
 396    // ----- METHODS -----
 397   
 398    /** @return the interactions model. */
 399  388 public DefaultInteractionsModel getInteractionsModel() { return _interactionsModel; }
 400   
 401    /** @return InteractionsDJDocument in use by the InteractionsDocument. */
 402  153 public InteractionsDJDocument getSwingInteractionsDocument() { return _interactionsDocument; }
 403   
 404  35 public InteractionsDocument getInteractionsDocument() { return _interactionsModel.getDocument(); }
 405   
 406    /** Gets the CompilerModel, which provides all methods relating to compilers. */
 407  1246 public CompilerModel getCompilerModel() { return _compilerModel; }
 408   
 409    /** Gets the JUnitModel, which provides all methods relating to JUnit testing. */
 410  64 public JUnitModel getJUnitModel() { return _junitModel; }
 411   
 412    /** Gets the JavadocModel, which provides all methods relating to Javadoc. */
 413  122 public JavadocModel getJavadocModel() { return _javadocModel; }
 414   
 415  4 public int getNumCompErrors() { return _numCompErrors; }
 416  56 public void setNumCompErrors(int num) { _numCompErrors = num; }
 417   
 418    /** Prepares this model to be thrown away. Never called in practice outside of quit(), except in tests. */
 419  155 public void dispose() {
 420  155 ensureJVMStarterFinished();
 421  155 _jvm.dispose();
 422  155 _notifier.removeAllListeners(); // removes the global model listeners!
 423    }
 424   
 425    /** Ensures that the _jvmStarter thread has executed. Never called in practice outside of GlobalModelTestCase.setUp(). */
 426  270 public void ensureJVMStarterFinished() {
 427  270 try { _jvmStarter.join(); } // some tests were reach this point before _jvmStarter has completed
 428  0 catch (InterruptedException e) { throw new UnexpectedException(e); }
 429    }
 430   
 431    /** Disposes of external resources. Kills the slave JVM. */
 432  0 public void disposeExternalResources() { _jvm.stopInterpreterJVM(); }
 433   
 434  23 public void resetInteractions(File wd) { resetInteractions(wd, false); }
 435   
 436    /** Clears and resets the slave JVM with working directory wd. Also clears the console if the option is
 437    * indicated (on by default). The reset operation is suppressed if the existing slave JVM has not been
 438    * used, {@code wd} matches its working directory, and forceReset is false. {@code wd} may be {@code null}
 439    * if a valid directory cannot be determined. In that case, the former working directory is used. This
 440    * method may run outside the event thread.
 441    */
 442  24 public void resetInteractions(File wd, boolean forceReset) {
 443  24 assert _interactionsModel._pane != null;
 444   
 445  24 debug.logStart();
 446  24 File workDir = _interactionsModel.getWorkingDirectory();
 447  0 if (wd == null) { wd = workDir; }
 448  24 forceReset |= isClassPathChanged();
 449  24 forceReset |= !wd.equals(workDir);
 450    // update the setting
 451  24 DrJava.getConfig().setSetting(LAST_INTERACTIONS_DIRECTORY, wd);
 452  24 getDebugger().setAutomaticTraceEnabled(false);
 453  24 _interactionsModel.resetInterpreter(wd, forceReset);
 454  24 debug.logEnd();
 455    }
 456   
 457    /** Interprets the current given text at the prompt in the interactions pane. */
 458  31 public void interpretCurrentInteraction() { _interactionsModel.interpretCurrentInteraction(); }
 459   
 460    /** Interprets file selected in the FileOpenSelector. Assumes strings have no trailing whitespace. Interpretation is
 461    * aborted after the first error.
 462    */
 463  3 public void loadHistory(final FileOpenSelector selector) {
 464  3 Utilities.invokeLater(new Runnable() {
 465  3 public void run() {
 466  3 try {_interactionsModel.loadHistory(selector); }
 467  0 catch(IOException e) { throw new UnexpectedException(e); }
 468    }
 469    });
 470    }
 471   
 472    /** Loads the history/histories from the given selector. */
 473  0 public InteractionsScriptModel loadHistoryAsScript(FileOpenSelector selector)
 474    throws IOException, OperationCanceledException {
 475  0 return _interactionsModel.loadHistoryAsScript(selector);
 476    }
 477   
 478    /** Clears the interactions history */
 479  1 public void clearHistory() { _interactionsModel.getDocument().clearHistory(); }
 480   
 481    /** Saves the unedited version of the current history to a file
 482    * @param selector File to save to
 483    */
 484  1 public void saveHistory(FileSaveSelector selector) throws IOException {
 485  1 _interactionsModel.getDocument().saveHistory(selector);
 486    }
 487   
 488    /** Saves the unedited version of the current history to a file
 489    * @param doc Document to save
 490    * @param selector File to save to
 491    */
 492  0 public void saveConsoleCopy(ConsoleDocument doc, FileSaveSelector selector) throws IOException {
 493  0 doc.saveCopy(selector);
 494    }
 495   
 496    /** Saves the edited version of the current history to a file
 497    * @param selector File to save to
 498    * @param editedVersion Edited verison of the history which will be saved to file instead of the lines saved in
 499    * the history. The saved file will still include any tags needed to recognize it as a history file.
 500    */
 501  0 public void saveHistory(FileSaveSelector selector, String editedVersion) throws IOException {
 502  0 _interactionsModel.getDocument().saveHistory(selector, editedVersion);
 503    }
 504   
 505    /** Returns the entire history as a String with semicolons as needed. */
 506  1 public String getHistoryAsStringWithSemicolons() {
 507  1 return _interactionsModel.getDocument().getHistoryAsStringWithSemicolons();
 508    }
 509   
 510    /** Returns the entire history as a String. */
 511  2 public String getHistoryAsString() {
 512  2 return _interactionsModel.getDocument().getHistoryAsString();
 513    }
 514   
 515    /** Called when the debugger wants to print a message. Inserts a newline. */
 516  0 public void printDebugMessage(String s) {
 517  0 _interactionsModel.getDocument().
 518    insertBeforeLastPrompt(s + "\n", InteractionsDocument.DEBUGGER_STYLE);
 519    }
 520   
 521    /** Returns the current class path in use by the Interpreter JVM. */
 522  0 public Iterable<File> getInteractionsClassPath() {
 523  0 return _jvm.getClassPath().unwrap(IterUtil.<File>empty());
 524    }
 525   
 526    /** Sets whether or not the Interactions JVM will be reset after a compilation succeeds. This should ONLY be used
 527    * in tests! This method is not supported by AbstractGlobalModel.
 528    * @param shouldReset Whether to reset after compiling
 529    */
 530  122 void setResetAfterCompile(boolean shouldReset) { _resetAfterCompile = shouldReset; }
 531   
 532    /** Gets the Debugger used by DrJava. */
 533  498 public Debugger getDebugger() { return _debugger; }
 534   
 535    /** Returns an available port number to use for debugging the interactions JVM.
 536    * @throws IOException if unable to get a valid port number.
 537    */
 538  0 public int getDebugPort() throws IOException { return _interactionsModel.getDebugPort(); }
 539   
 540    // ---------- ConcreteOpenDefDoc inner class ----------
 541   
 542    /** Inner class to handle operations on each of the open DefinitionsDocuments by the GlobalModel. <br><br>
 543    * This was at one time called the <code>DefinitionsDocumentHandler</code>
 544    * but was renamed (2004-Jun-8) to be more descriptive/intuitive.
 545    */
 546    class ConcreteOpenDefDoc extends AbstractGlobalModel.ConcreteOpenDefDoc {
 547    /** Standard constructor for a document read from a file. Initializes this ODD's DD.
 548    * @param f file describing DefinitionsDocument to manage
 549    */
 550  79 ConcreteOpenDefDoc(File f) {
 551  79 super(f);
 552   
 553    // update the syntax highlighting for this document
 554    // can't be done in AbstractGlobalModel.ConcreteOpenDefDoc because getCompilerModel is not supported
 555  79 updateSyntaxHighlighting();
 556    }
 557   
 558    /* Standard constructor for a new document (no associated file) */
 559  309 ConcreteOpenDefDoc(NullFile f) { super(f);
 560   
 561    // update the syntax highlighting for this document
 562    // can't be done in AbstractGlobalModel.ConcreteOpenDefDoc because getCompilerModel is not supported
 563  309 updateSyntaxHighlighting();
 564    }
 565   
 566    /** Starting compiling this document. Used only for unit testing. Only rus in the event thread. */
 567  48 public void startCompile() throws IOException {
 568  48 assert EventQueue.isDispatchThread();
 569  48 _compilerModel.compile(ConcreteOpenDefDoc.this);
 570    }
 571   
 572    private volatile InteractionsListener _runMain;
 573   
 574    /** Runs the main method in this document in the interactions pane after resetting interactions with the source
 575    * root for this document as the working directory. Warns the use if the class files for the doucment are not
 576    * up to date. Fires an event to signal when execution is about to begin.
 577    * NOTE: this code normally runs in the event thread; it cannot block waiting for an event that is triggered by
 578    * event thread execution!
 579    * NOTE: the command to run is constructed using {@link java.text.MessageFormat}. That means that certain characters,
 580    * single quotes and curly braces, for example, are special. To write single quotes, you need to double them.
 581    * To write curly braces, you need to enclose them in single quotes. Example:
 582    * MessageFormat.format("Abc {0} ''foo'' '{'something'}'", "def") returns "Abc def 'foo' {something}".
 583    *
 584    * @param command the command to run, with {0} indicating the place where the class name will be written
 585    * @param qualifiedClassName the qualified name of the class (in this document) to run. If NULL, it is the name
 586    * of the top level class.
 587    *
 588    * @exception ClassNameNotFoundException propagated from getFirstTopLevelClass()
 589    * @exception IOException propagated from GlobalModel.compileAll()
 590    */
 591  2 protected void _runInInteractions(final String command, String qualifiedClassName) throws ClassNameNotFoundException,
 592    IOException {
 593   
 594  2 assert EventQueue.isDispatchThread();
 595   
 596  2 _notifier.prepareForRun(ConcreteOpenDefDoc.this);
 597   
 598  2 String tempClassName = null;
 599   
 600  2 if(qualifiedClassName == null)
 601  2 tempClassName = getDocument().getQualifiedClassName();
 602    else
 603  0 tempClassName = qualifiedClassName;
 604   
 605    // Get the class name for this document, the first top level class in the document.
 606  2 final String className = tempClassName;
 607  2 final InteractionsDocument iDoc = _interactionsModel.getDocument();
 608  2 if (! checkIfClassFileInSync()) {
 609  1 iDoc.insertBeforeLastPrompt(DOCUMENT_OUT_OF_SYNC_MSG, InteractionsDocument.ERROR_STYLE);
 610  1 return;
 611    }
 612   
 613  1 final boolean wasDebuggerEnabled = getDebugger().isReady();
 614   
 615  1 _runMain = new DummyInteractionsListener() {
 616  1 public void interpreterReady(File wd) {
 617    // prevent listener from running twice
 618    // This used to be called using invokeLater, so that the listener would be removed
 619    // after the read lock of the notifier had been released, but that was not always
 620    // safe; the removal could still happen before the read lock was released
 621    // Now removeListener has been rewritten and can be called even when the lock is
 622    // held. In that case, the removal will be done as soon as possible.
 623  1 _interactionsModel.removeListener(_runMain); // listener cannot run
 624   
 625    // Run debugger restart in an invokeLater so that the InteractionsModel EventNotifier
 626    // reader-writer lock isn't held anymore.
 627  1 javax.swing.SwingUtilities.invokeLater(new Runnable() {
 628  1 public void run() {
 629    // Restart debugger if it was previously enabled and is now off
 630  1 if (wasDebuggerEnabled && (! getDebugger().isReady())) {
 631    // System.err.println("Trying to start debugger");
 632  0 try { getDebugger().startUp(); } catch(DebugException de) { /* ignore, continue without debugger */ }
 633    }
 634    // Load the proper text into the interactions document
 635  1 iDoc.clearCurrentInput();
 636  1 iDoc.append(java.text.MessageFormat.format(command, className), null);
 637   
 638    // Finally, execute the new interaction and record that event
 639  1 new Thread("Running document") {
 640  1 public void run() { _interactionsModel.interpretCurrentInteraction(); }
 641    }.start();
 642    }
 643    });
 644    }
 645    };
 646   
 647  1 File oldWorkDir = _interactionsModel.getWorkingDirectory();
 648  1 _interactionsModel.addListener(_runMain);
 649   
 650  1 File workDir;
 651  1 workDir = getWorkingDirectory();
 652   
 653    // Reset interactions to the working directory
 654  1 resetInteractions(workDir, !workDir.equals(oldWorkDir));
 655    }
 656   
 657    /** Runs the main method in this document in the interactions pane after resetting interactions with the source
 658    * root for this document as the working directory. Warns the use if the class files for the doucment are not
 659    * up to date. Fires an event to signal when execution is about to begin.
 660    * NOTE: this code normally runs in the event thread; it cannot block waiting for an event that is triggered by
 661    * event thread execution!
 662    *
 663    * @param qualifiedClassName the qualified name of the class (in this document) to run. If NULL, it is the name
 664    * of the top level class.
 665    *
 666    * @exception ClassNameNotFoundException propagated from getFirstTopLevelClass()
 667    * @exception IOException propagated from GlobalModel.compileAll()
 668    */
 669  2 public void runMain(String qualifiedClassName) throws ClassNameNotFoundException, IOException {
 670  2 _runInInteractions("java {0}", qualifiedClassName);
 671    }
 672   
 673    /** Runs this document as applet in the interactions pane after resetting interactions with the source
 674    * root for this document as the working directory. Warns the use if the class files for the doucment are not
 675    * up to date. Fires an event to signal when execution is about to begin.
 676    * NOTE: this code normally runs in the event thread; it cannot block waiting for an event that is triggered by
 677    * event thread execution!
 678    *
 679    * @param qualifiedClassName the qualified name of the class (in this document) to run. If NULL, it is the name
 680    * of the top level class.
 681    *
 682    * @exception ClassNameNotFoundException propagated from getFirstTopLevelClass()
 683    * @exception IOException propagated from GlobalModel.compileAll()
 684    */
 685  0 public void runApplet(String qualifiedClassName) throws ClassNameNotFoundException, IOException {
 686  0 _runInInteractions("applet {0}", qualifiedClassName);
 687    }
 688   
 689    /** Runs this document, and tries to be smart about it. It detects if the class is a regular Java class with a
 690    * main method, if it is an applet, or if it is an ACM Java Task Force program. It runs the program appropriately
 691    * in the interactions pane after resetting interactions with the source root for this document as the
 692    * working directory. Warns the use if the class files for the doucment are not up to date.
 693    * Fires an event to signal when execution is about to begin.
 694    * NOTE: this code normally runs in the event thread; it cannot block waiting for an event that is triggered by
 695    * event thread execution!
 696    *
 697    * @param qualifiedClassName the qualified name of the class (in this document) to run. If NULL, it is the name
 698    * of the top level class.
 699    *
 700    * @exception ClassNameNotFoundException propagated from getFirstTopLevelClass()
 701    * @exception IOException propagated from GlobalModel.compileAll()
 702    */
 703  0 public void runSmart(String qualifiedClassName) throws ClassNameNotFoundException, IOException {
 704  0 _runInInteractions("run {0}", qualifiedClassName);
 705    }
 706   
 707    /** Runs JUnit on the current document. Requires that all source documents are compiled before proceeding. */
 708  17 public void startJUnit() throws ClassNotFoundException, IOException { _junitModel.junit(this); }
 709   
 710    /** Generates Javadoc for this document, saving the output to a temporary directory. The location is provided to
 711    * the javadocEnded event on the given listener.
 712    * java@param saver FileSaveSelector for saving the file if it needs to be saved
 713    */
 714  0 public void generateJavadoc(FileSaveSelector saver) throws IOException {
 715    // Use the model's classpath, and use the EventNotifier as the listener
 716  0 _javadocModel.javadocDocument(this, saver);
 717    }
 718   
 719    /** Called to indicate the document is being closed, so to remove all related state from the debug manager. */
 720  151 public void removeFromDebugger() { getBreakpointManager().removeRegions(this); }
 721   
 722    // This creation context is useful for debugging memory leaks in DefinitionsPaneMemoryLeakTest.
 723    // It should be commented out for normal compilation.
 724    // String creationContext;
 725    // {
 726    // StringWriter sw = new StringWriter();
 727    // new RuntimeException("new ConcreteOpenDefDoc").printStackTrace(new PrintWriter(sw));
 728    // creationContext = sw.toString();
 729    // }
 730    } /* End of ConcreteOpenDefDoc */
 731   
 732    /** Creates a ConcreteOpenDefDoc for a new DefinitionsDocument.
 733    * @return OpenDefinitionsDocument object for a new document
 734    */
 735  309 protected ConcreteOpenDefDoc _createOpenDefinitionsDocument(NullFile f) { return new ConcreteOpenDefDoc(f); }
 736   
 737    /** Creates a ConcreteOpenDefDoc for a given file f
 738    * @return OpenDefinitionsDocument object for f
 739    */
 740  87 protected ConcreteOpenDefDoc _createOpenDefinitionsDocument(File f) throws IOException {
 741  8 if (! f.exists()) throw new FileNotFoundException("file " + f + " cannot be found");
 742  79 return new ConcreteOpenDefDoc(f);
 743    }
 744   
 745    /** Adds the source root for doc to the interactions classpath; this function is a helper to _openFiles.
 746    * @param doc the document to add to the classpath
 747    */
 748  207 protected void addDocToClassPath(OpenDefinitionsDocument doc) {
 749  207 try {
 750  207 File sourceRoot = doc.getSourceRoot();
 751  0 if (doc.isAuxiliaryFile()) { _interactionsModel.addProjectFilesClassPath(sourceRoot); }
 752  206 else { _interactionsModel.addExternalFilesClassPath(sourceRoot); }
 753  206 setClassPathChanged(true);
 754    }
 755    catch (InvalidPackageException e) {
 756    // Invalid package-- don't add it to classpath
 757    }
 758    }
 759   
 760  157 private void _setupDebugger() {
 761  157 _jvm.setDebugModel(_debugger.callback());
 762   
 763    // add listener to set the project file to "changed" when a breakpoint or watch is added, removed, or changed
 764  157 getBreakpointManager().addListener(new RegionManagerListener<Breakpoint>() {
 765  0 public void regionAdded(final Breakpoint bp) { setProjectChanged(true); }
 766  0 public void regionChanged(final Breakpoint bp) { setProjectChanged(true); }
 767  0 public void regionRemoved(final Breakpoint bp) {
 768  0 try { getDebugger().removeBreakpoint(bp); }
 769    catch(DebugException de) {
 770    /* just ignore it */
 771    // TODO: should try to pop up dialog to give the user the option of restarting the debugger (mgricken)
 772    // int result = JOptionPane.showConfirmDialog(null, "Could not remove breakpoint.", "Restart debugger?", JOptionPane.YES_NO_OPTION);
 773    // if (result==JOptionPane.YES_OPTION) {
 774    // getDebugger().shutdown();
 775    // getDebugger().startUp();
 776    // }
 777    }
 778  0 setProjectChanged(true);
 779    }
 780    });
 781  157 getBookmarkManager().addListener(new RegionManagerListener<MovingDocumentRegion>() {
 782  5 public void regionAdded(MovingDocumentRegion r) { setProjectChanged(true); }
 783  0 public void regionChanged(MovingDocumentRegion r) { setProjectChanged(true); }
 784  5 public void regionRemoved(MovingDocumentRegion r) { setProjectChanged(true); }
 785    });
 786   
 787  157 _debugger.addListener(new DebugListener() {
 788  0 public void watchSet(final DebugWatchData w) { setProjectChanged(true); }
 789  0 public void watchRemoved(final DebugWatchData w) { setProjectChanged(true); }
 790   
 791  0 public void regionAdded(final Breakpoint bp) { }
 792  0 public void regionChanged(final Breakpoint bp) { }
 793  0 public void regionRemoved(final Breakpoint bp) { }
 794  0 public void debuggerStarted() { }
 795  0 public void debuggerShutdown() { }
 796  0 public void threadLocationUpdated(OpenDefinitionsDocument doc, int lineNumber, boolean shouldHighlight) { }
 797  0 public void breakpointReached(final Breakpoint bp) { }
 798  0 public void stepRequested() { }
 799  0 public void currThreadSuspended() { }
 800  0 public void currThreadResumed() { }
 801  0 public void threadStarted() { }
 802  0 public void currThreadDied() { }
 803  0 public void nonCurrThreadDied() { }
 804  0 public void currThreadSet(DebugThreadData thread) { }
 805    });
 806    }
 807   
 808    /** Get the class path to be used in all class-related operations.
 809    * TODO: Ensure that this is used wherever appropriate.
 810    */
 811  55 public Iterable<File> getClassPath() {
 812  55 Iterable<File> result = IterUtil.empty();
 813   
 814  55 if (isProjectActive()) {
 815  0 File buildDir = getBuildDirectory();
 816  0 if (buildDir != null) { result = IterUtil.compose(result, buildDir); }
 817   
 818    /* We prefer to assume the project root is the project's source root, rather than
 819    * checking *every* file in the project for its source root. This is a bit problematic,
 820    * because "Compile Project" won't care if the user has multiple source roots (or even just a
 821    * single "src" subdirectory), and the user in this situation (assuming the build dir is
 822    * null) wouldn't notice a problem until trying to access the compiled classes in the
 823    * Interactions.
 824    */
 825  0 File projRoot = getProjectRoot();
 826  0 if (projRoot != null) { result = IterUtil.compose(result, projRoot); }
 827   
 828  0 Iterable<AbsRelFile> projectExtras = getExtraClassPath();
 829  0 if (projectExtras != null) { result = IterUtil.compose(result, projectExtras); }
 830    }
 831  55 else { result = IterUtil.compose(result, getSourceRootSet()); }
 832   
 833  55 Vector<File> globalExtras = DrJava.getConfig().getSetting(EXTRA_CLASSPATH);
 834  55 if (globalExtras != null) { result = IterUtil.compose(result, globalExtras); }
 835   
 836    /* We must add JUnit to the class path. We do so by including the current JVM's class path.
 837    * This is not ideal, because all other classes on the current class path (including all of DrJava's
 838    * internal classes) are also included. But we're probably stuck doing something like this if we
 839    * want to continue bundling JUnit with DrJava.
 840    */
 841  55 result = IterUtil.compose(result, ReflectUtil.SYSTEM_CLASS_PATH);
 842   
 843  55 return result;
 844    }
 845   
 846    /** Adds the project root (if a project is open), the source roots for other open documents, the paths in the
 847    * "extra classpath" config option, as well as any project-specific classpaths to the interpreter's classpath.
 848    * This method is called in DefaultInteractionsModel when the interpreter becomes ready. Runs outside the event
 849    * thread.
 850    */
 851  183 public void resetInteractionsClassPath() {
 852    // System.err.println("Resetting interactions class path");
 853  183 Iterable<AbsRelFile> projectExtras = getExtraClassPath();
 854    //System.out.println("Adding project classpath vector to interactions classpath: " + projectExtras);
 855  0 if (projectExtras != null) for (File cpE : projectExtras) { _interactionsModel.addProjectClassPath(cpE); }
 856   
 857  183 Vector<File> cp = DrJava.getConfig().getSetting(EXTRA_CLASSPATH);
 858  183 if (cp != null) {
 859  2 for (File f : cp) { _interactionsModel.addExtraClassPath(f); }
 860    }
 861   
 862  183 for (OpenDefinitionsDocument odd: getAuxiliaryDocuments()) {
 863    // this forwards directly to InterpreterJVM.addClassPath(String)
 864  0 try { _interactionsModel.addProjectFilesClassPath(odd.getSourceRoot()); }
 865    catch(InvalidPackageException e) { /* ignore it */ }
 866    }
 867   
 868  183 for (OpenDefinitionsDocument odd: getNonProjectDocuments()) {
 869    // this forwards directly to InterpreterJVM.addClassPath(String)
 870  213 try {
 871  213 File sourceRoot = odd.getSourceRoot();
 872  18 if (sourceRoot != null) _interactionsModel.addExternalFilesClassPath(sourceRoot);
 873    }
 874    catch(InvalidPackageException e) { /* ignore it */ }
 875    }
 876   
 877    // add project source root to projectFilesClassPath. All files in project tree have this root.
 878   
 879  183 _interactionsModel.addProjectFilesClassPath(getProjectRoot()); // is sync advisable here?
 880  183 setClassPathChanged(false); // reset classPathChanged state
 881    }
 882    }