Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 892   Methods: 64
NCLOC: 473   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
InteractionsModel.java 62.5% 76.8% 84.4% 75.7%
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;
 38   
 39    import java.io.*;
 40    import java.net.ServerSocket;
 41    import java.util.List;
 42    import java.util.ArrayList;
 43    import java.util.HashSet;
 44   
 45    import javax.swing.text.BadLocationException;
 46    import java.awt.EventQueue;
 47   
 48    import edu.rice.cs.drjava.ui.DrJavaErrorHandler;
 49    import edu.rice.cs.drjava.ui.InteractionsPane;
 50    import edu.rice.cs.util.FileOpenSelector;
 51    import edu.rice.cs.util.OperationCanceledException;
 52    import edu.rice.cs.util.StringOps;
 53    import edu.rice.cs.util.UnexpectedException;
 54    import edu.rice.cs.util.*;
 55    import edu.rice.cs.util.swing.Utilities;
 56    import edu.rice.cs.util.text.ConsoleDocumentInterface;
 57    import edu.rice.cs.util.text.ConsoleDocument;
 58    import edu.rice.cs.util.text.EditDocumentException;
 59    import edu.rice.cs.plt.tuple.Pair;
 60    import edu.rice.cs.drjava.DrJava;
 61    import edu.rice.cs.drjava.config.OptionConstants;
 62   
 63    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 64   
 65    /** A Swing specific model for the DrJava InteractionsPane. It glues together an InteractionsDocument, an
 66    * InteractionsPane and a JavaInterpreter. This abstract class provides common functionality for all such models.
 67    * The methods in this class generally can be executed only in the event thread once the model has been constructed.
 68    * @version $Id: InteractionsModel.java 5237 2010-04-27 07:52:59Z mgricken $
 69    */
 70    public abstract class InteractionsModel implements InteractionsModelCallback {
 71   
 72    /** Banner prefix. */
 73    public static final String BANNER_PREFIX = "Welcome to DrJava.";
 74   
 75    /** Number of milliseconds to wait after each println, to prevent the JVM from being flooded
 76    * with print calls. */
 77    public static final int WRITE_DELAY = 50;
 78   
 79    // public static final String _newLine = "\n"; // was StringOps.EOL; but Swing uses '\n' for newLine
 80   
 81    /** Keeps track of any listeners to the model. */
 82    protected final InteractionsEventNotifier _notifier = new InteractionsEventNotifier();
 83   
 84    /** InteractionsDocument containing the commands and history. This field is volatile rather than final because its
 85    * initialization is deferred until after the interactions pane is created. The InteractionsDocument constructor
 86    * indirectly accesses the pane generating a NullPointerException if _pane is uninitialized.
 87    */
 88    protected volatile InteractionsDocument _document;
 89   
 90    /** Whether we are waiting for the interpreter to register for the first time. */
 91    protected volatile boolean _waitingForFirstInterpreter;
 92   
 93    /** The working directory for the current interpreter. */
 94    protected volatile File _workingDirectory;
 95   
 96    /** A lock object to prevent print calls to System.out or System.err from flooding the JVM, ensuring the UI remains
 97    * responsive. Only public for testing purposes. */
 98    public final Object _writerLock;
 99   
 100    /** Number of milliseconds to wait after each println, to prevent the JVM from being flooded with print calls. */
 101    private final int _writeDelay;
 102   
 103    /** Port used by the debugger to connect to the Interactions JVM. Uniquely created in getDebugPort(). */
 104    private volatile int _debugPort;
 105   
 106    /** Whether the debug port has already been set. If not, calling getDebugPort will generate an available port. */
 107    private volatile boolean _debugPortSet;
 108   
 109    /** The String added to history when the interaction is complete or an error is thrown */
 110    private volatile String _toAddToHistory = "";
 111   
 112    /** The input listener to listen for requests to System.in. */
 113    protected volatile InputListener _inputListener;
 114   
 115    /** The embedded interactions document (a SwingDocument in native DrJava) */
 116    protected final ConsoleDocumentInterface _cDoc;
 117   
 118    /** The interactions pane bundled with this document. In contrast to a standard MVC decomposition, where the model
 119    * and the view are independent components, an interactions model inherently includes a prompt and a cursor marking
 120    * where the next input expression (in progress) begins and where the cursor is within that expression. In Swing, the
 121    * view contains the cursor. Our InteractionsDocument (a form of ConsoleDocument) contains the prompt. Public only for
 122    * testing purposes; otherwise protected.
 123    *
 124    * TODO: The Eclipse plug-in doesn't use Swing, and so has no InteractionsPane. In that case, _pane is always null.
 125    * This should be redesigned to eliminate the strong coupling with Swing.
 126    */
 127    public volatile InteractionsPane _pane; // initially null
 128   
 129    /** Banner displayed at top of the interactions document */
 130    private volatile String _banner;
 131   
 132    /** Last error, or null if successful. */
 133    protected volatile String _lastError = null;
 134    protected volatile String _secondToLastError = null;
 135   
 136    /** Set of classes or packages to import again when a breakpoint is hit. */
 137    protected final HashSet<String> _autoImportSet = new HashSet<String>();
 138   
 139    /** Constructs an InteractionsModel. The InteractionsPane is created later by the InteractionsController.
 140    * As a result, the posting of a banner at the top of InteractionsDocument must be deferred
 141    * until after the InteracationsPane has been set up.
 142    * @param cDoc document to use in the InteractionsDocument
 143    * @param wd Working directory for the interpreter
 144    * @param historySize Number of lines to store in the history
 145    * @param writeDelay Number of milliseconds to wait after each println
 146    */
 147  188 public InteractionsModel(ConsoleDocumentInterface cDoc, final File wd, int historySize, int writeDelay) {
 148  188 _document = new InteractionsDocument(cDoc, historySize);
 149  188 _cDoc = cDoc;
 150  188 _writeDelay = writeDelay;
 151  188 _waitingForFirstInterpreter = true;
 152  188 _workingDirectory = wd;
 153  188 _writerLock = new Object();
 154  188 _debugPort = -1;
 155  188 _debugPortSet = false;
 156  188 _inputListener = NoInputListener.ONLY;
 157  188 Utilities.invokeLater(new Runnable() {
 158  188 public void run() { _document.setBanner(generateBanner(wd));}
 159    });
 160    }
 161   
 162    /** Sets the _pane field and initializes the caret position in the pane. Called in the InteractionsController. */
 163  165 public void setUpPane(InteractionsPane pane) {
 164  165 _pane = pane;
 165  165 _pane.setCaretPosition(_document.getLength());
 166    // _caretInit(); // places the caret (in the UNIQUE interactions pane) at the end of the document
 167    }
 168   
 169    /** Adds an InteractionsListener to the model.
 170    * @param listener a listener that reacts to Interactions events.
 171    */
 172  481 public void addListener(InteractionsListener listener) { _notifier.addListener(listener); }
 173   
 174    /** Removea an InteractionsListener from the model. If the listener is not currently listening to this model, this
 175    * method has no effect.
 176    * @param listener a listener that reacts to Interactions events
 177    */
 178  2 public void removeListener(InteractionsListener listener) { _notifier.removeListener(listener); }
 179   
 180    /** Removes all InteractionsListeners from this model. */
 181  112 public void removeAllInteractionListeners() { _notifier.removeAllListeners(); }
 182   
 183    /** Returns the InteractionsDocument stored by this model. */
 184  233 public InteractionsDocument getDocument() { return _document; }
 185   
 186  1 public void interactionContinues() {
 187  1 _document.setInProgress(false);
 188  1 _notifyInteractionEnded();
 189  1 _notifyInteractionIncomplete();
 190    }
 191   
 192    /** Sets this model's notion of whether it is waiting for the first interpreter to connect. The interactionsReady
 193    * event is not fired for the first interpreter.
 194    */
 195  2 public void setWaitingForFirstInterpreter(boolean waiting) { _waitingForFirstInterpreter = waiting; }
 196   
 197    /** Interprets the current given text at the prompt in the interactions doc. May run outside the event thread. */
 198  47 public void interpretCurrentInteraction() {
 199   
 200  47 Utilities.invokeLater(new Runnable() {
 201  47 public void run() {
 202   
 203  0 if (_document.inProgress()) return; // Don't start a new interaction while one is in progress
 204   
 205  47 String text = _document.getCurrentInteraction();
 206  47 String toEval = text.trim();
 207  47 _prepareToInterpret(toEval); // Writes a newLine!
 208    // if (toEval.startsWith("java ")) toEval = _transformJavaCommand(toEval);
 209    // else if (toEval.startsWith("applet ")) toEval = _transformAppletCommand(toEval);
 210  47 toEval = transformCommands(toEval);
 211  47 if (DrJava.getConfig().getSetting(OptionConstants.DEBUG_AUTO_IMPORT).booleanValue() &&
 212    toEval.startsWith("import ")) {
 213    // add the class or package after the import to the set of auto-imports
 214    // NOTE: this only processes import statements until the first non-import statement or comment is reached
 215    // Example: import java.io.File; import java.io.IOException // imports both File and IOException
 216    // Example: import java.io.File; /* comment */ import java.io.IOException // imports only File
 217    // Example: import java.io.File; File f; import java.io.IOException // imports only File
 218  1 String line = toEval;
 219  1 do {
 220  1 line = line.substring("import ".length());
 221  1 String substr = line;
 222  1 int endPos = 0;
 223  1 while((endPos<substr.length()) &&
 224    ((Character.isJavaIdentifierPart(substr.charAt(endPos))) ||
 225    (substr.charAt(endPos) == '.') ||
 226  9 (substr.charAt(endPos) == '*'))) ++endPos;
 227  1 substr = substr.substring(0,endPos);
 228  1 _autoImportSet.add(substr);
 229   
 230    // remove substr from line
 231  1 line = line.substring(substr.length()).trim();
 232  0 if (!line.startsWith(";")) break;
 233  1 line = line.substring(1).trim();
 234  1 } while(line.startsWith("import "));
 235    }
 236    // System.err.println("Preparing to interpret '" + toEval + "'");
 237  47 final String evalText = toEval;
 238   
 239  47 new Thread(new Runnable() {
 240  47 public void run() {
 241  47 try { interpret(evalText); }
 242  0 catch(Throwable t) { DrJavaErrorHandler.record(t); }
 243    }
 244    }).start();
 245    }
 246    });
 247    }
 248   
 249    /** Executes import statements for the classes and packages in the auto-import set. */
 250  0 public void autoImport() {
 251  0 java.util.Vector<String> classes = DrJava.getConfig().getSetting(OptionConstants.INTERACTIONS_AUTO_IMPORT_CLASSES);
 252  0 final StringBuilder sb = new StringBuilder();
 253   
 254  0 for(String s: classes) {
 255  0 String name = s.trim();
 256  0 if (s.length() > 0) {
 257  0 sb.append("import ");
 258  0 sb.append(s.trim());
 259  0 sb.append("; ");
 260    }
 261    }
 262  0 if (DrJava.getConfig().getSetting(OptionConstants.DEBUG_AUTO_IMPORT).booleanValue()) {
 263  0 for(String s: _autoImportSet) {
 264  0 sb.append("import ");
 265  0 sb.append(s);
 266  0 sb.append("; ");
 267    }
 268    }
 269   
 270  0 if (sb.length() > 0) {
 271  0 interpret(sb.toString());
 272  0 _document.insertBeforeLastPrompt("Auto-import: " + sb.toString() + "\n", InteractionsDocument.DEBUGGER_STYLE);
 273    }
 274    }
 275   
 276    /** Performs pre-interpretation preparation of the interactions document and notifies the view. Must run in the
 277    * event thread for newline to be inserted at proper time. Assumes that Write Lock is already held. */
 278  47 private void _prepareToInterpret(String text) {
 279  47 _addNewline();
 280  47 _notifyInteractionStarted();
 281  47 _document.setInProgress(true);
 282  47 _toAddToHistory = text; // _document.addToHistory(text);
 283    //Do not add to history immediately in case the user is not finished typing when they press return
 284    }
 285   
 286    /** Appends a newLine to _document assuming that the Write Lock is already held. Must run in the event thread. */
 287  47 public void _addNewline() { append(StringOps.NEWLINE, InteractionsDocument.DEFAULT_STYLE); }
 288   
 289    /** Interprets the given command.
 290    * @param toEval command to be evaluated. */
 291  47 public final void interpret(String toEval) { _interpret(toEval); }
 292   
 293    /** Interprets the given command. This should only be called from interpret, never directly.
 294    * @param toEval command to be evaluated
 295    */
 296    protected abstract void _interpret(String toEval);
 297   
 298    /** Notifies the view that the current interaction is incomplete. */
 299    protected abstract void _notifyInteractionIncomplete();
 300   
 301    /** Notifies listeners that an interaction has started. (Subclasses must maintain listeners.) */
 302    public abstract void _notifyInteractionStarted();
 303   
 304    /** Gets the string representation of the value of a variable in the current interpreter.
 305    * @param var the name of the variable
 306    * @return A string representation of the value, or {@code null} if the variable is not defined.
 307    */
 308    public abstract Pair<String,String> getVariableToString(String var);
 309   
 310    /** Resets the Java interpreter with working directory wd. */
 311  25 public final void resetInterpreter(File wd, boolean force) {
 312  25 _workingDirectory = wd;
 313  25 _autoImportSet.clear(); // clear list when interpreter is reset
 314  25 _resetInterpreter(wd, force);
 315    }
 316   
 317    /** Resets the Java interpreter. This should only be called from resetInterpreter, never directly. */
 318    protected abstract void _resetInterpreter(File wd, boolean force);
 319   
 320    /** Returns the working directory for the current interpreter. */
 321  137 public File getWorkingDirectory() { return _workingDirectory; }
 322   
 323    /** These add the given path to the classpaths used in the interpreter.
 324    * @param f the path to add
 325    */
 326    public abstract void addProjectClassPath(File f);
 327   
 328    /** These add the given path to the build directory classpaths used in the interpreter.
 329    * @param f the path to add
 330    */
 331    public abstract void addBuildDirectoryClassPath(File f);
 332   
 333    /** These add the given path to the project files classpaths used in the interpreter.
 334    * @param f the path to add
 335    */
 336    public abstract void addProjectFilesClassPath(File f);
 337   
 338    /** These add the given path to the external files classpaths used in the interpreter.
 339    * @param f the path to add
 340    */
 341    public abstract void addExternalFilesClassPath(File f);
 342   
 343    /** These add the given path to the extra classpaths used in the interpreter.
 344    * @param f the path to add
 345    */
 346    public abstract void addExtraClassPath(File f);
 347   
 348    /** Handles a syntax error being returned from an interaction
 349    * @param offset the first character of the error in the InteractionsDocument
 350    * @param length the length of the error.
 351    */
 352    protected abstract void _notifySyntaxErrorOccurred(int offset, int length);
 353   
 354   
 355    /** Interprets the files selected in the FileOpenSelector. Assumes all strings have no trailing whitespace.
 356    * Interprets the array all at once so if there are any errors, none of the statements after the first
 357    * erroneous one are processed. Only runs in the event thread.
 358    */
 359  3 public void loadHistory(final FileOpenSelector selector) throws IOException {
 360  3 ArrayList<String> histories;
 361  3 try { histories = _getHistoryText(selector); }
 362  0 catch (OperationCanceledException oce) { return; }
 363  3 final ArrayList<String> _histories = histories;
 364   
 365  3 _document.clearCurrentInteraction();
 366   
 367    // Insert into the document and interpret
 368  3 final StringBuilder buf = new StringBuilder();
 369  3 for (String hist: _histories) {
 370  3 ArrayList<String> interactions = _removeSeparators(hist);
 371  3 for (String curr: interactions) {
 372  5 int len = curr.length();
 373  5 buf.append(curr);
 374  1 if (len > 0 && curr.charAt(len - 1) != ';') buf.append(';');
 375  5 buf.append(StringOps.EOL);
 376    }
 377    }
 378  3 String text = buf.toString().trim();
 379    // System.err.println("Histtory is: '" + text + "'");
 380  3 append(text, InteractionsDocument.DEFAULT_STYLE);
 381  3 interpretCurrentInteraction();
 382    // System.err.println("Interpreting loaded history");
 383   
 384    }
 385   
 386    /** Opens the files chosen in the given file selector, and returns an ArrayList with one history string
 387    * for each selected file.
 388    * @param selector A file selector supporting multiple file selection
 389    * @return a list of histories (one for each selected file)
 390    */
 391  4 protected static ArrayList<String> _getHistoryText(FileOpenSelector selector)
 392    throws IOException, OperationCanceledException {
 393  4 File[] files = selector.getFiles();
 394  0 if (files == null) throw new IOException("No Files returned from FileSelector");
 395   
 396  4 ArrayList<String> histories = new ArrayList<String>();
 397  4 ArrayList<String> strings = new ArrayList<String>();
 398   
 399  4 for (File f: files) {
 400  0 if (f == null) throw new IOException("File name returned from FileSelector is null");
 401  4 try {
 402  4 FileInputStream fis = new FileInputStream(f);
 403  4 InputStreamReader isr = new InputStreamReader(fis);
 404  4 BufferedReader br = new BufferedReader(isr);
 405  4 while (true) {
 406  16 String line = br.readLine();
 407  4 if (line == null) break;
 408  12 strings.add(line);
 409    }
 410  4 br.close(); // win32 needs readers closed explicitly!
 411    }
 412  0 catch (IOException ioe) { throw new IOException("File name returned from FileSelector is null"); }
 413   
 414    // Create a single string with all formatted lines from this history
 415  4 final StringBuilder text = new StringBuilder();
 416  4 boolean firstLine = true;
 417  4 int formatVersion = 1;
 418  4 for (String s: strings) {
 419  12 int sl = s.length();
 420  12 if (sl > 0) {
 421   
 422    // check for format version string. NOTE: the original file format did not have a version string
 423  2 if (firstLine && (s.trim().equals(History.HISTORY_FORMAT_VERSION_2.trim()))) formatVersion = 2;
 424   
 425  12 switch (formatVersion) {
 426  4 case (1):
 427    // When reading this format, we need to make sure each line ends in a semicolon.
 428    // This behavior can be buggy; that's why the format was changed.
 429  4 text.append(s);
 430  2 if (s.charAt(sl - 1) != ';') text.append(';');
 431  4 text.append(StringOps.EOL);
 432  4 break;
 433  8 case (2):
 434  6 if (!firstLine) text.append(s).append(StringOps.EOL); // omit version string from output
 435  8 break;
 436    }
 437  12 firstLine = false;
 438    }
 439    }
 440   
 441    // Add the entire formatted text to the list of histories
 442  4 histories.add(text.toString());
 443    }
 444  4 return histories;
 445    }
 446   
 447    /* Loads the contents of the specified file(s) into the histories buffer. This method is dynamic because it refers
 448    * to this. */
 449  1 public InteractionsScriptModel loadHistoryAsScript(FileOpenSelector selector)
 450    throws IOException, OperationCanceledException {
 451  1 ArrayList<String> histories = _getHistoryText(selector);
 452  1 ArrayList<String> interactions = new ArrayList<String>();
 453  1 for (String hist: histories) interactions.addAll(_removeSeparators(hist));
 454  1 return new InteractionsScriptModel(this, interactions);
 455    }
 456   
 457    /** Removes the interaction-separator comments from a history, so that they will not appear when executing
 458    * the history.
 459    * @param text The full, formatted text of an interactions history (obtained from _getHistoryText)
 460    * @return A list of strings representing each interaction in the history. If no separators are present,
 461    * the entire history is treated as one interaction.
 462    */
 463  4 protected static ArrayList<String> _removeSeparators(String text) {
 464  4 String sep = History.INTERACTION_SEPARATOR;
 465  4 int len = sep.length();
 466  4 ArrayList<String> interactions = new ArrayList<String>();
 467   
 468    // Loop while there are still separators, adding the text between separators
 469    // as separate elements to the interactions list
 470  4 int index = text.indexOf(sep);
 471  4 int lastIndex = 0;
 472  4 while (index != -1) {
 473  5 interactions.add(text.substring(lastIndex, index).trim());
 474  5 lastIndex = index + len;
 475  5 index = text.indexOf(sep, lastIndex);
 476    }
 477   
 478    // get last interaction
 479  4 String last = text.substring(lastIndex, text.length()).trim();
 480  2 if (!"".equals(last)) interactions.add(last);
 481  4 return interactions;
 482    }
 483   
 484    /** Returns the port number to use for debugging the interactions JVM. Generates an available port if one has
 485    * not been set manually.
 486    * @throws IOException if unable to get a valid port number.
 487    */
 488  182 public int getDebugPort() throws IOException {
 489  159 if (!_debugPortSet) _createNewDebugPort();
 490  182 return _debugPort;
 491    }
 492   
 493    /** Generates an available port for use with the debugger.
 494    * @throws IOException if unable to get a valid port number.
 495    */
 496  181 protected void _createNewDebugPort() throws IOException {
 497    // Utilities.showDebug("InteractionsModel: _createNewDebugPort() called");
 498  181 try {
 499  181 ServerSocket socket = new ServerSocket(0);
 500  181 _debugPort = socket.getLocalPort();
 501  181 socket.close();
 502    }
 503    catch (java.net.SocketException se) {
 504    // something wrong with sockets, can't use for debugger
 505  0 _debugPort = -1;
 506    }
 507  181 _debugPortSet = true;
 508  181 System.setProperty("drjava.debug.port", String.valueOf(_debugPort));
 509    }
 510   
 511    /** Sets the port number to use for debugging the interactions JVM.
 512    * @param port Port to use to debug the interactions JVM
 513    */
 514  2 public void setDebugPort(int port) {
 515  2 _debugPort = port;
 516  2 _debugPortSet = true;
 517    }
 518   
 519    private static final int DELAY_INTERVAL = 10;
 520    private volatile int delayCount = DELAY_INTERVAL;
 521   
 522    /** Called when the repl prints to System.out. Includes a delay to prevent flooding the interactions document. This
 523    * method can safely be called from outside the event thread.
 524    * @param s String to print
 525    */
 526  12 public void replSystemOutPrint(final String s) {
 527  12 Utilities.invokeLater(new Runnable() {
 528  12 public void run() { _document.insertBeforeLastPrompt(s, InteractionsDocument.SYSTEM_OUT_STYLE); }
 529    });
 530  12 if (delayCount == 0) {
 531  0 scrollToCaret();
 532    // System.err.println(s + " printed; caretPostion = " + _pane.getCaretPosition());
 533  0 _writerDelay();
 534  0 delayCount = DELAY_INTERVAL;
 535    }
 536  12 else delayCount--;
 537    }
 538   
 539    /** Called when the repl prints to System.err. Includes a delay to prevent flooding the interactions document. This
 540    * method can safely be called from outside the event thread.
 541    * @param s String to print
 542    */
 543  2 public void replSystemErrPrint(final String s) {
 544  2 Utilities.invokeLater(new Runnable() {
 545  2 public void run() { _document.insertBeforeLastPrompt(s, InteractionsDocument.SYSTEM_ERR_STYLE); }
 546    });
 547  2 if (delayCount == 0) {
 548  0 scrollToCaret();
 549    // System.err.println(s + " printed; caretPostion = " + _pane.getCaretPosition());
 550  0 _writerDelay();
 551  0 delayCount = DELAY_INTERVAL;
 552    }
 553  2 else delayCount--;
 554    }
 555   
 556    /** Returns a line of text entered by the user at the equivalent of System.in. Only executes in the event thread. */
 557  4 public String getConsoleInput() { return _inputListener.getConsoleInput(); }
 558   
 559    /** Sets the listener for any type of single-source input event. The listener can only be changed with the
 560    * changeInputListener method.except in testing code which needs to install a different listener after the
 561    * interactiona controller has been created (method testConsoleInput in GlobalModelIOTest).
 562    * @param listener a listener that reacts to input requests
 563    * @throws IllegalStateException if the input listener is locked
 564    */
 565  167 public void setInputListener(InputListener listener) {
 566  167 if (_inputListener == NoInputListener.ONLY || Utilities.TEST_MODE) { _inputListener = listener; }
 567  0 else throw new IllegalStateException("Cannot change the input listener until it is released.");
 568    }
 569   
 570    /** Changes the input listener. Takes in the old listener to ensure that the owner
 571    * of the original listener is aware that it is being changed. It is therefore
 572    * important NOT to include a public accessor to the input listener on the model.
 573    * Only used in a single thread in unit tests, so synchronization is unnecessary.
 574    * @param oldListener the listener that was installed
 575    * @param newListener the listener to be installed
 576    */
 577  1 public void changeInputListener(InputListener oldListener, InputListener newListener) {
 578  1 if (_inputListener == oldListener) _inputListener = newListener;
 579    else
 580  0 throw new IllegalArgumentException("The given old listener is not installed!");
 581    }
 582   
 583    /** Performs the common behavior when an interaction ends. Subclasses might want to additionally notify listeners
 584    * here. (Do this after calling super()). Access is public for testing purposes.
 585    */
 586  44 public void _interactionIsOver() {
 587  44 Utilities.invokeLater(new Runnable() {
 588  44 public void run() {
 589  44 _document.addToHistory(_toAddToHistory);
 590  44 _document.setInProgress(false);
 591  44 _document.insertPrompt();
 592  44 _notifyInteractionEnded();
 593    }
 594    });
 595  44 scrollToCaret();
 596    }
 597   
 598    /** Notifies listeners that an interaction has ended. (Subclasses must maintain listeners.) */
 599    protected abstract void _notifyInteractionEnded();
 600   
 601    /** Appends a string to the given document using a named style.
 602    * @param s String to append to the end of the document
 603    * @param styleName Name of the style to use for s
 604    */
 605  73 public void append(final String s, final String styleName) {
 606  73 Utilities.invokeLater(new Runnable() { public void run() { _document.append(s, styleName); } });
 607  73 scrollToCaret();
 608    }
 609   
 610    /** Waits for a small amount of time on a shared writer lock. */
 611  0 public void _writerDelay() {
 612  0 synchronized(_writerLock) {
 613  0 try {
 614    // Wait to prevent being flooded with println's
 615  0 _writerLock.wait(_writeDelay);
 616    }
 617  0 catch (EditDocumentException e) { throw new UnexpectedException(e); }
 618    catch (InterruptedException e) { /* Not a problem. continue */}
 619    }
 620    }
 621   
 622    /** Signifies that the most recent interpretation completed successfully, returning no value. */
 623  14 public void replReturnedVoid() {
 624  14 _secondToLastError = _lastError;
 625  14 _lastError = null;
 626  14 _interactionIsOver();
 627    }
 628   
 629    /** Appends the returned result to the interactions document, inserts a prompt in the interactions document, and
 630    * advances the caret in the interactions pane.
 631    * @param result The .toString-ed version of the value that was returned by the interpretation. We must return the
 632    * String form because returning the Object directly would require the data type to be serializable.
 633    */
 634  23 public void replReturnedResult(String result, String style) {
 635    // Utilities.show("InteractionsModel.replReturned(...) passed '" + result + "'");
 636  23 _secondToLastError = _lastError;
 637  23 _lastError = null;
 638  23 append(result + "\n", style);
 639  23 _interactionIsOver();
 640    }
 641   
 642    /**
 643    * Default behavior set to return what it's given.
 644    * Used to replace line number and file name in a throwable when the error
 645    * occurs in a Language Level file.
 646    * Overriden in DefaultInteractionModel when a GlobalModel is available
 647    * to make a LanguageLevelStackTraceMapper.
 648    * @param sT the stackTrace to replace line number and file name
 649    * @return the same stackTrace.
 650    */
 651  0 public StackTraceElement[] replaceLLException(StackTraceElement[] sT) {
 652  0 return sT;
 653   
 654    }
 655   
 656    /** Signifies that the most recent interpretation was ended due to an exception being thrown. */
 657  0 public void replThrewException(String message, StackTraceElement[] stackTrace) {
 658  0 stackTrace = replaceLLException(stackTrace);
 659  0 StringBuilder sb = new StringBuilder(message);
 660  0 for(StackTraceElement ste: stackTrace) {
 661  0 sb.append("\n\tat ");
 662  0 sb.append(ste);
 663    }
 664  0 replThrewException(sb.toString().trim());
 665    }
 666   
 667   
 668    /** Signifies that the most recent interpretation was ended due to an exception being thrown. */
 669  1 public void replThrewException(final String message) {
 670  1 if (message.endsWith("<EOF>\"")) {
 671  0 interactionContinues();
 672    }
 673    else {
 674  1 Utilities.invokeLater(new Runnable() {
 675  1 public void run() { _document.appendExceptionResult(message, InteractionsDocument.ERROR_STYLE); }
 676    });
 677  1 _secondToLastError = _lastError;
 678  1 _lastError = message;
 679  1 _interactionIsOver();
 680    }
 681    }
 682   
 683    /** Signifies that the most recent interpretation was preempted by a syntax error. The integer parameters
 684    * support future error highlighting.
 685    * @param errorMessage The syntax error message
 686    * @param startRow The starting row of the error
 687    * @param startCol The starting column of the error
 688    * @param endRow The end row of the error
 689    * param endCol The end column of the error
 690    */
 691  1 public void replReturnedSyntaxError(String errorMessage, String interaction, int startRow, int startCol,
 692    int endRow, int endCol ) {
 693    // Note: this method is currently never called. The highlighting functionality needs
 694    // to be restored.
 695  1 _secondToLastError = _lastError;
 696  1 _lastError = errorMessage;
 697  1 if (errorMessage != null) {
 698  1 if (errorMessage.endsWith("<EOF>\"")) {
 699  1 interactionContinues();
 700  1 return;
 701    }
 702    }
 703   
 704  0 Pair<Integer,Integer> oAndL =
 705    StringOps.getOffsetAndLength(interaction, startRow, startCol, endRow, endCol);
 706   
 707  0 _notifySyntaxErrorOccurred(_document.getPromptPos() + oAndL.first().intValue(),oAndL.second().intValue());
 708   
 709  0 _document.appendSyntaxErrorResult(errorMessage, interaction, startRow, startCol, endRow, endCol,
 710    InteractionsDocument.ERROR_STYLE);
 711   
 712  0 _interactionIsOver();
 713    }
 714   
 715    /** Signifies that the most recent interpretation contained a call to System.exit.
 716    * @param status The exit status that will be returned.
 717    */
 718  1 public void replCalledSystemExit(int status) {
 719    // Utilities.showDebug("InteractionsModel: replCalledSystemExit(" + status + ") called");
 720  1 _notifyInterpreterExited(status);
 721    }
 722   
 723    /** Notifies listeners that the interpreter has exited unexpectedly. (Subclasses must maintain listeners.)
 724    * @param status Status code of the dead process
 725    */
 726    protected abstract void _notifyInterpreterExited(int status);
 727   
 728    /** Called when the interpreter starts to reset. */
 729  22 public void interpreterResetting() {
 730  22 if (! _waitingForFirstInterpreter) {
 731  22 Utilities.invokeLater(new Runnable() {
 732  22 public void run() {
 733  22 _document.insertBeforeLastPrompt(" Resetting Interactions ...\n", InteractionsDocument.ERROR_STYLE);
 734  22 _document.setInProgress(true);
 735    }
 736    });
 737   
 738    // Change to a new debug port to avoid conflicts
 739  22 try { _createNewDebugPort(); }
 740    catch (IOException ioe) {
 741    // Oh well, leave it at the previous port
 742    }
 743  22 _notifyInterpreterResetting();
 744    // Utilities.showDebug("InteractionsModel: interpreterResetting notification complete");
 745    }
 746    }
 747   
 748    /** Notifies listeners that the interpreter is resetting. (Subclasses must maintain listeners.) */
 749    protected abstract void _notifyInterpreterResetting();
 750   
 751    /** This method is called by the Main JVM if the Interpreter JVM cannot be exited
 752    * @param t The Throwable thrown by System.exit
 753    */
 754  0 public void interpreterResetFailed(Throwable t) {
 755  0 _interpreterResetFailed(t);
 756  0 _document.setInProgress(false);
 757  0 _notifyInterpreterResetFailed(t);
 758    }
 759   
 760  0 public void interpreterWontStart(Exception e) {
 761  0 _interpreterWontStart(e);
 762  0 _document.setInProgress(true); // prevent editing -- is there a better way to do this?
 763    }
 764   
 765    /** Any extra action to perform (beyond notifying listeners) when the interpreter fails to reset.
 766    * @param t The Throwable thrown by System.exit
 767    */
 768    protected abstract void _interpreterResetFailed(Throwable t);
 769   
 770    /** Notifies listeners that the interpreter reset failed. (Subclasses must maintain listeners.)
 771    * @param t Throwable explaining why the reset failed.
 772    */
 773    protected abstract void _notifyInterpreterResetFailed(Throwable t);
 774   
 775    /** Action to perform when the interpreter won't start. */
 776    protected abstract void _interpreterWontStart(Exception e);
 777   
 778  0 public String getBanner() { return _banner; }
 779   
 780  1 public String getStartUpBanner() { return getBanner(_workingDirectory); }
 781   
 782  214 public static String getBanner(File wd) { return BANNER_PREFIX + " Working directory is " + wd + '\n'; }
 783   
 784  213 private String generateBanner(File wd) {
 785  213 _banner = getBanner(wd);
 786  213 return _banner;
 787    }
 788   
 789    // /** Initializes the caret in a new or reset InteractionsModel. */
 790    // private void _caretInit() { advanceCaret(_document.getLength()); }
 791   
 792    // /** Advances the caret in the interactions pane by n characters. After component realization, only runs in event thread
 793    // * and assumes read lock or write lock is already held.
 794    // */
 795    // protected void advanceCaret(final int n) {
 796    // /* In legacy unit tests, _pane can apparently be null in some cases. It can also be mutated in the middle of run()
 797    // in InteractionsDJDocumentTest.testStylesListContentAndReset. */
 798    // final InteractionsPane pane = _pane;
 799    //// if (Utilities.TEST_MODE && pane == null) return; // Some legacy unit tests do not set up an interactions pane
 800    //
 801    // int caretPos = pane.getCaretPosition();
 802    // int newCaretPos = Math.min(caretPos + n, _document.getLength());
 803    // pane.setCaretPos(newCaretPos);
 804    // }
 805   
 806  118 protected void scrollToCaret() {
 807  118 Utilities.invokeLater(new Runnable() {
 808  118 public void run() {
 809  118 final InteractionsPane pane = _pane;
 810  23 if (pane == null) return; // Can be called in tests when component has not been realized
 811  95 int pos = pane.getCaretPosition();
 812  95 try { pane.scrollRectToVisible(pane.modelToView(pos)); }
 813  0 catch(BadLocationException e) { throw new UnexpectedException(e); }
 814    }
 815    });
 816    }
 817   
 818    /** Called when a new Java interpreter has registered and is ready for use. */
 819  183 public void interpreterReady(final File wd) {
 820  183 debug.logStart();
 821    // System.err.println("interpreterReady(" + wd + ") called in InteractionsModel"); // DEBUG
 822    // System.out.println("_waitingForFirstInterpreter = " + _waitingForFirstInterpreter); // DEBUG
 823  183 if (! _waitingForFirstInterpreter) {
 824  25 Utilities.invokeLater(new Runnable() {
 825  25 public void run() {
 826  25 _document.reset(generateBanner(wd));
 827  25 _document.setInProgress(false);
 828  25 if (_pane != null) _pane.setCaretPosition(_document.getLength());
 829   
 830  25 performDefaultImports();
 831   
 832  25 _notifyInterpreterReady(wd);
 833    }
 834    });
 835    }
 836  183 _waitingForFirstInterpreter = false;
 837  183 debug.logEnd();
 838    }
 839   
 840    /** Perform the default imports of the classes and packages listed in the INTERACTIONS_AUTO_IMPORT_CLASSES. */
 841  25 public void performDefaultImports() {
 842  25 java.util.Vector<String> classes = DrJava.getConfig().getSetting(OptionConstants.INTERACTIONS_AUTO_IMPORT_CLASSES);
 843  25 final StringBuilder sb = new StringBuilder();
 844   
 845  25 for(String s: classes) {
 846  0 String name = s.trim();
 847  0 if (s.length() > 0) {
 848  0 sb.append("import ");
 849  0 sb.append(s.trim());
 850  0 sb.append("; ");
 851    }
 852    }
 853  25 if (sb.length() > 0) {
 854  0 interpret(sb.toString());
 855  0 _document.insertBeforeLastPrompt("Default imports: " + sb.toString() + "\n", InteractionsDocument.DEBUGGER_STYLE);
 856    }
 857    }
 858   
 859    /** Notifies listeners that the interpreter is ready. (Subclasses must maintain listeners.) */
 860    public abstract void _notifyInterpreterReady(File wd);
 861   
 862    /** Singleton InputListener which should never be asked for input. */
 863    private static class NoInputListener implements InputListener {
 864    public static final NoInputListener ONLY = new NoInputListener();
 865  23 private NoInputListener() { }
 866   
 867  1 public String getConsoleInput() { throw new IllegalStateException("No input listener installed!"); }
 868    }
 869   
 870    /** Gets the console tab document for this interactions model */
 871    public abstract ConsoleDocument getConsoleDocument();
 872   
 873    /** Return the last error, or null if successful. */
 874  10 public String getLastError() {
 875  10 return _lastError;
 876    }
 877   
 878    /** Return the second to last error, or null if successful. */
 879  0 public String getSecondToLastError() {
 880  0 return _secondToLastError;
 881    }
 882   
 883    /** Reset the information about the last and second to last error. */
 884  0 public void resetLastErrors() {
 885  0 _lastError = _secondToLastError = null;
 886    }
 887   
 888    /** Returns the last history item and then removes it, or returns null if the history is empty. */
 889  0 public String removeLastFromHistory() {
 890  0 return _document.removeLastFromHistory();
 891    }
 892    }