001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.repl;
038:
039: import java.io.*;
040: import java.net.ServerSocket;
041: import java.util.List;
042: import java.util.ArrayList;
043:
044: import edu.rice.cs.drjava.CodeStatus;
045: import edu.rice.cs.drjava.ui.InteractionsController;
046: import edu.rice.cs.util.FileOpenSelector;
047: import edu.rice.cs.util.OperationCanceledException;
048: import edu.rice.cs.util.StringOps;
049: import edu.rice.cs.util.UnexpectedException;
050: import edu.rice.cs.util.*;
051: import edu.rice.cs.util.swing.Utilities;
052: import edu.rice.cs.util.text.ConsoleDocumentInterface;
053: import edu.rice.cs.util.text.ConsoleDocument;
054: import edu.rice.cs.util.text.EditDocumentException;
055: import edu.rice.cs.plt.tuple.Pair;
056:
057: /** A model which can serve as the glue between an InteractionsDocument and a JavaInterpreter. This
058: * abstract class provides common functionality for all such models.
059: * @version $Id: InteractionsModel.java 4264 2007-11-15 01:33:48Z mgricken $
060: */
061: public abstract class InteractionsModel implements
062: InteractionsModelCallback {
063:
064: /** Banner prefix. */
065: public static final String BANNER_PREFIX = "Welcome to DrJava.";
066:
067: public static final String _newLine = "\n"; // was StringOps.EOL; but Swing uses '\n' for newLine
068:
069: /** Keeps track of any listeners to the model. */
070: protected final InteractionsEventNotifier _notifier = new InteractionsEventNotifier();
071:
072: /** InteractionsDocument containing the commands and history. */
073: protected final InteractionsDocument _document;
074:
075: /** Whether we are waiting for the interpreter to register for the first time. */
076: protected volatile boolean _waitingForFirstInterpreter;
077:
078: /** The working directory for the current interpreter. */
079: protected volatile File _workingDirectory;
080:
081: /** A lock object to prevent multiple threads from interpreting at once. */
082: private final Object _interpreterLock;
083:
084: /** A lock object to prevent print calls to System.out or System.err from flooding the JVM, ensuring the UI remains
085: * responsive. Only public for testing purposes. */
086: public final Object _writerLock;
087:
088: /** Number of milliseconds to wait after each println, to prevent the JVM from being flooded with print calls. */
089: private final int _writeDelay;
090:
091: /** Port used by the debugger to connect to the Interactions JVM. Uniquely created in getDebugPort(). */
092: private volatile int _debugPort;
093:
094: /** Whether the debug port has already been set. If not, calling getDebugPort will generate an available port. */
095: private volatile boolean _debugPortSet;
096:
097: /** The String added to history when the interaction is complete or an error is thrown */
098: private volatile String _toAddToHistory = "";
099:
100: /** The input listener to listen for requests to System.in. */
101: protected volatile InputListener _inputListener;
102:
103: /** The embedded interactions document (a SwingDocument in native DrJava) */
104: protected final ConsoleDocumentInterface _adapter;
105:
106: /** Banner displayed at top of the interactions document */
107: private volatile String _banner;
108:
109: /** Last error, or null if successful. */
110: protected volatile Pair<String, String> _lastError = null;
111: protected volatile Pair<String, String> _secondToLastError = null;
112:
113: /** Constructs an InteractionsModel.
114: * @param adapter DocumentAdapter to use in the InteractionsDocument
115: * @param wd Working directory for the interpreter
116: * @param historySize Number of lines to store in the history
117: * @param writeDelay Number of milliseconds to wait after each println
118: */
119: public InteractionsModel(ConsoleDocumentInterface adapter, File wd,
120: int historySize, int writeDelay) {
121: _writeDelay = writeDelay;
122: _document = new InteractionsDocument(adapter, historySize,
123: getBanner(wd));
124: _adapter = adapter;
125: _waitingForFirstInterpreter = true;
126: _workingDirectory = wd;
127: _interpreterLock = new Object();
128: _writerLock = new Object();
129: _debugPort = -1;
130: _debugPortSet = false;
131: _inputListener = NoInputListener.ONLY;
132: }
133:
134: /** Add an InteractionsListener to the model.
135: * @param listener a listener that reacts to Interactions events */
136: public void addListener(InteractionsListener listener) {
137: _notifier.addListener(listener);
138: }
139:
140: /** Remove an InteractionsListener from the model. If the listener is not currently listening to this model, this
141: * method has no effect.
142: * @param listener a listener that reacts to Interactions events
143: */
144: public void removeListener(InteractionsListener listener) {
145: _notifier.removeListener(listener);
146: }
147:
148: /** Removes all InteractionsListeners from this model. */
149: public void removeAllInteractionListeners() {
150: _notifier.removeAllListeners();
151: }
152:
153: /** Returns the InteractionsDocument stored by this model. */
154: public InteractionsDocument getDocument() {
155: return _document;
156: }
157:
158: public void interactionContinues() {
159: _document.setInProgress(false);
160: _notifyInteractionEnded();
161: _notifyInteractionIncomplete();
162: }
163:
164: /** Sets this model's notion of whether it is waiting for the first interpreter to connect. The interactionsReady
165: * event is not fired for the first interpreter.
166: */
167: public void setWaitingForFirstInterpreter(boolean waiting) {
168: _waitingForFirstInterpreter = waiting;
169: }
170:
171: /** Interprets the current given text at the prompt in the interactions doc. */
172: public void interpretCurrentInteraction() {
173: synchronized (_interpreterLock) {
174: // Don't start a new interaction while one is in progress
175: if (_document.inProgress())
176: return;
177:
178: String text = _document.getCurrentInteraction();
179: String toEval = text.trim();
180: if (toEval.startsWith("java "))
181: toEval = _testClassCall(toEval);
182:
183: _prepareToInterpret(text);
184: interpret(toEval);
185: }
186: }
187:
188: /** Performs pre-interpretation preparation of the interactions document and notifies the view. */
189: private void _prepareToInterpret(String text) {
190: addNewLine();
191: _notifyInteractionStarted();
192: _document.setInProgress(true);
193: _toAddToHistory = text; // _document.addToHistory(text);
194: //Do not add to history immediately in case the user is not finished typing when they press return
195: }
196:
197: public void addNewLine() {
198: append(_newLine, InteractionsDocument.DEFAULT_STYLE);
199: }
200:
201: /** Interprets the given command.
202: * @param toEval command to be evaluated. */
203: public final void interpret(String toEval) {
204: synchronized (_interpreterLock) {
205: _interpret(toEval);
206: }
207: }
208:
209: /** Interprets the given command. This should only be called from interpret, never directly.
210: * @param toEval command to be evaluated
211: */
212: protected abstract void _interpret(String toEval);
213:
214: /** Notifies the view that the current interaction is incomplete. */
215: protected abstract void _notifyInteractionIncomplete();
216:
217: /** Notifies listeners that an interaction has started. (Subclasses must maintain listeners.) */
218: protected abstract void _notifyInteractionStarted();
219:
220: /** Gets the string representation of the value of a variable in the current interpreter.
221: * @param var the name of the variable
222: */
223: public abstract String getVariableToString(String var);
224:
225: /** Gets the class name of a variable in the current interpreter.
226: * @param var the name of the variable
227: */
228: public abstract String getVariableClassName(String var);
229:
230: /** Resets the Java interpreter with working directry wd. */
231: public final void resetInterpreter(File wd) {
232: _workingDirectory = wd;
233: _resetInterpreter(wd);
234: }
235:
236: /** Resets the Java interpreter. This should only be called from resetInterpreter, never directly. */
237: protected abstract void _resetInterpreter(File wd);
238:
239: /** Returns the working directory for the current interpreter. */
240: public File getWorkingDirectory() {
241: return _workingDirectory;
242: }
243:
244: /** These add the given path to the classpaths used in the interpreter.
245: * @param path Path to add
246: */
247: public abstract void addProjectClassPath(File f);
248:
249: public abstract void addBuildDirectoryClassPath(File f);
250:
251: public abstract void addProjectFilesClassPath(File f);
252:
253: public abstract void addExternalFilesClassPath(File f);
254:
255: public abstract void addExtraClassPath(File f);
256:
257: /** Handles a syntax error being returned from an interaction
258: * @param offset the first character of the error in the InteractionsDocument
259: * @param length the length of the error.
260: */
261: protected abstract void _notifySyntaxErrorOccurred(int offset,
262: int length);
263:
264: /** Opens the files chosen in the given file selector, and returns an ArrayList with one history string
265: * for each selected file.
266: * @param selector A file selector supporting multiple file selection
267: * @return a list of histories (one for each selected file)
268: */
269: protected ArrayList<String> _getHistoryText(
270: FileOpenSelector selector) throws IOException,
271: OperationCanceledException {
272: File[] files = selector.getFiles();
273: if (files == null)
274: throw new IOException("No Files returned from FileSelector");
275:
276: ArrayList<String> histories = new ArrayList<String>();
277: ArrayList<String> strings = new ArrayList<String>();
278:
279: for (File f : files) {
280: if (f == null)
281: throw new IOException(
282: "File name returned from FileSelector is null");
283: try {
284: FileInputStream fis = new FileInputStream(f);
285: InputStreamReader isr = new InputStreamReader(fis);
286: BufferedReader br = new BufferedReader(isr);
287: while (true) {
288: String line = br.readLine();
289: if (line == null)
290: break;
291: strings.add(line);
292: }
293: br.close(); // win32 needs readers closed explicitly!
294: } catch (IOException ioe) {
295: throw new IOException(
296: "File name returned from FileSelector is null");
297: }
298:
299: // Create a single string with all formatted lines from this history
300: final StringBuilder text = new StringBuilder();
301: boolean firstLine = true;
302: int formatVersion = 1;
303: for (String s : strings) {
304: int sl = s.length();
305: if (sl > 0) {
306:
307: // check for format version string. NOTE: the original file format did not have a version string
308: if (firstLine
309: && (s.trim()
310: .equals(History.HISTORY_FORMAT_VERSION_2
311: .trim())))
312: formatVersion = 2;
313:
314: switch (formatVersion) {
315: case (1):
316: // When reading this format, we need to make sure each line ends in a semicolon.
317: // This behavior can be buggy; that's why the format was changed.
318: text.append(s);
319: if (s.charAt(sl - 1) != ';')
320: text.append(';');
321: text.append(StringOps.EOL);
322: break;
323: case (2):
324: if (!firstLine)
325: text.append(s).append(StringOps.EOL); // omit version string from output
326: break;
327: }
328: firstLine = false;
329: }
330: }
331:
332: // Add the entire formatted text to the list of histories
333: histories.add(text.toString());
334: }
335: return histories;
336: }
337:
338: /** Removes the interaction-separator comments from a history, so that they will not appear when executing
339: * the history.
340: * @param text The full, formatted text of an interactions history (obtained from _getHistoryText)
341: * @return A list of strings representing each interaction in the history. If no separators are present,
342: * the entire history is treated as one interaction.
343: */
344: protected ArrayList<String> _removeSeparators(String text) {
345: String sep = History.INTERACTION_SEPARATOR;
346: int len = sep.length();
347: ArrayList<String> interactions = new ArrayList<String>();
348:
349: // Loop while there are still separators, adding the text between separators
350: // as separate elements to the interactions list
351: int index = text.indexOf(sep);
352: int lastIndex = 0;
353: while (index != -1) {
354: interactions.add(text.substring(lastIndex, index).trim());
355: lastIndex = index + len;
356: index = text.indexOf(sep, lastIndex);
357: }
358:
359: // get last interaction
360: String last = text.substring(lastIndex, text.length()).trim();
361: if (!"".equals(last))
362: interactions.add(last);
363: return interactions;
364: }
365:
366: /** Interprets the files selected in the FileOpenSelector. Assumes all strings have no trailing whitespace.
367: * Interprets the array all at once so if there are any errors, none of the statements after the first
368: * erroneous one are processed.
369: */
370: public void loadHistory(FileOpenSelector selector)
371: throws IOException {
372: ArrayList<String> histories;
373: try {
374: histories = _getHistoryText(selector);
375: } catch (OperationCanceledException oce) {
376: return;
377: }
378: _document.clearCurrentInteraction();
379:
380: // Insert into the document and interpret
381: final StringBuilder buf = new StringBuilder();
382: for (String hist : histories) {
383: ArrayList<String> interactions = _removeSeparators(hist);
384: for (String curr : interactions) {
385: int len = curr.length();
386: buf.append(curr);
387: if (len > 0 && curr.charAt(len - 1) != ';')
388: buf.append(';');
389: buf.append(StringOps.EOL);
390: }
391: }
392: append(buf.toString().trim(),
393: InteractionsDocument.DEFAULT_STYLE);
394: interpretCurrentInteraction();
395: }
396:
397: /* Loads the contents of the specified file(s) into the histories buffer. */
398: public InteractionsScriptModel loadHistoryAsScript(
399: FileOpenSelector selector) throws IOException,
400: OperationCanceledException {
401: ArrayList<String> histories = _getHistoryText(selector);
402: ArrayList<String> interactions = new ArrayList<String>();
403: for (String hist : histories)
404: interactions.addAll(_removeSeparators(hist));
405: return new InteractionsScriptModel(this , interactions);
406: }
407:
408: /** Returns the port number to use for debugging the interactions JVM. Generates an available port if one has
409: * not been set manually.
410: * @throws IOException if unable to get a valid port number.
411: */
412: public int getDebugPort() throws IOException {
413: if (!_debugPortSet)
414: _createNewDebugPort();
415: return _debugPort;
416: }
417:
418: /** Generates an available port for use with the debugger.
419: * @throws IOException if unable to get a valid port number.
420: */
421: protected void _createNewDebugPort() throws IOException {
422: // Utilities.showDebug("InteractionsModel: _createNewDebugPort() called");
423: try {
424: ServerSocket socket = new ServerSocket(0);
425: _debugPort = socket.getLocalPort();
426: socket.close();
427: } catch (java.net.SocketException se) {
428: // something wrong with sockets, can't use for debugger
429: _debugPort = -1;
430: }
431: _debugPortSet = true;
432: System.setProperty("drjava.debug.port", String
433: .valueOf(_debugPort));
434: }
435:
436: /** Sets the port number to use for debugging the interactions JVM.
437: * @param port Port to use to debug the interactions JVM
438: */
439: public void setDebugPort(int port) {
440: _debugPort = port;
441: _debugPortSet = true;
442: }
443:
444: /** Called when the repl prints to System.out. Includes a delay to prevent flooding the interactions document.
445: * @param s String to print
446: */
447: public void replSystemOutPrint(String s) {
448: _document.insertBeforeLastPrompt(s,
449: InteractionsDocument.SYSTEM_OUT_STYLE);
450: _writerDelay();
451: }
452:
453: /** Called when the repl prints to System.err. Includes a delay to prevent flooding the interactions document.
454: * @param s String to print
455: */
456: public void replSystemErrPrint(String s) {
457: _document.insertBeforeLastPrompt(s,
458: InteractionsDocument.SYSTEM_ERR_STYLE);
459: _writerDelay();
460: }
461:
462: /** Returns a line of text entered by the user at the equivalent of System.in. */
463: public String getConsoleInput() {
464: return _inputListener.getConsoleInput();
465: }
466:
467: /** Sets the listener for any type of single-source input event. The listener can only be changed with the
468: * changeInputListener method.
469: * @param listener a listener that reacts to input requests
470: * @throws IllegalStateException if the input listener is locked
471: */
472: public void setInputListener(InputListener listener) {
473: if (_inputListener == NoInputListener.ONLY) {
474: _inputListener = listener;
475: } else
476: throw new IllegalStateException(
477: "Cannot change the input listener until it is released.");
478: }
479:
480: /** Changes the input listener. Takes in the old listener to ensure that the owner
481: * of the original listener is aware that it is being changed. It is therefore
482: * important NOT to include a public accessor to the input listener on the model.
483: * @param oldListener the listener that was installed
484: * @param newListener the listener to be installed
485: */
486: public void changeInputListener(InputListener oldListener,
487: InputListener newListener) {
488: // synchronize to prevent concurrent modifications to the listener
489: synchronized (NoInputListener.ONLY) {
490: if (_inputListener == oldListener)
491: _inputListener = newListener;
492: else
493: throw new IllegalArgumentException(
494: "The given old listener is not installed!");
495: }
496: }
497:
498: /** Any common behavior when an interaction ends. Subclasses might want to additionally notify listeners
499: * here. (Do this after calling super()), public for testing purposes.
500: */
501: public void _interactionIsOver() {
502: _document.acquireWriteLock(); // TODO: encapsulate as method of InteractionsDocument
503: try {
504: _document.addToHistory(_toAddToHistory);
505: _document.setInProgress(false);
506: _document.insertPrompt();
507: } finally {
508: _document.releaseWriteLock();
509: }
510:
511: _notifyInteractionEnded();
512: }
513:
514: /** Notifies listeners that an interaction has ended. (Subclasses must maintain listeners.) */
515: protected abstract void _notifyInteractionEnded();
516:
517: /** Appends a string to the given document using a named style. Also waits for a small amount of time
518: * (_writeDelay) to prevent any one writer from flooding the model with print calls to the point that
519: * the user interface could become unresponsive.
520: * @param s String to append to the end of the document
521: * @param styleName Name of the style to use for s
522: */
523: public void append(String s, String styleName) {
524: _document.append(s, styleName);
525: _writerDelay();
526: }
527:
528: /** Waits for a small amount of time on a shared writer lock. */
529: public void _writerDelay() {
530: synchronized (_writerLock) {
531: try {
532: // Wait to prevent being flooded with println's
533: _writerLock.wait(_writeDelay);
534: } catch (EditDocumentException e) {
535: throw new UnexpectedException(e);
536: } catch (InterruptedException e) { /* Not a problem. continue */
537: }
538: }
539: }
540:
541: /** Signifies that the most recent interpretation completed successfully, returning no value. */
542: public void replReturnedVoid() {
543: _secondToLastError = _lastError;
544: _lastError = null;
545: _interactionIsOver();
546: }
547:
548: /** Signifies that the most recent interpretation completed successfully, returning a value.
549: * @param result The .toString-ed version of the value that was returned by the interpretation. We must return the
550: * String form because returning the Object directly would require the data type to be serializable.
551: */
552: public void replReturnedResult(String result, String style) {
553: // System.err.println("InteractionsModel.replReturned(...) passed '" + result + "'");
554: _secondToLastError = _lastError;
555: _lastError = null;
556: append(result + "\n", style);
557: _interactionIsOver();
558: }
559:
560: /** Signifies that the most recent interpretation was ended due to an exception being thrown.
561: * @param exceptionClass The name of the class of the thrown exception
562: * @param message The exception's message
563: * @param stackTrace The stack trace of the exception
564: */
565: public void replThrewException(String exceptionClass,
566: String message, String stackTrace, String shortMessage) {
567: if (shortMessage != null) {
568: if (shortMessage.endsWith("<EOF>\"")) {
569: interactionContinues();
570: return;
571: }
572: }
573: _document.appendExceptionResult(exceptionClass, message,
574: stackTrace, InteractionsDocument.ERROR_STYLE);
575: _secondToLastError = _lastError;
576: _lastError = new Pair<String, String>(exceptionClass, message);
577: _interactionIsOver();
578: }
579:
580: /** Signifies that the most recent interpretation was preempted by a syntax error. The integer parameters
581: * support future error highlighting.
582: * @param errorMessage The syntax error message
583: * @param startRow The starting row of the error
584: * @param startCol The starting column of the error
585: * @param endRow The end row of the error
586: * param endCol The end column of the error
587: */
588: public void replReturnedSyntaxError(String errorMessage,
589: String interaction, int startRow, int startCol, int endRow,
590: int endCol) {
591: _secondToLastError = _lastError;
592: _lastError = new Pair<String, String>(
593: "koala.dynamicjava.parser.ParserException",
594: errorMessage);
595: if (errorMessage != null) {
596: if (errorMessage.endsWith("<EOF>\"")) {
597: interactionContinues();
598: return;
599: }
600: }
601:
602: Pair<Integer, Integer> oAndL = StringOps.getOffsetAndLength(
603: interaction, startRow, startCol, endRow, endCol);
604:
605: _notifySyntaxErrorOccurred(_document.getPromptPos()
606: + oAndL.first().intValue(), oAndL.second().intValue());
607:
608: _document.appendSyntaxErrorResult(errorMessage, interaction,
609: startRow, startCol, endRow, endCol,
610: InteractionsDocument.ERROR_STYLE);
611:
612: _interactionIsOver();
613: }
614:
615: /** Signifies that the most recent interpretation contained a call to System.exit.
616: * @param status The exit status that will be returned.
617: */
618: public void replCalledSystemExit(int status) {
619: // Utilities.showDebug("InteractionsModel: replCalledSystemExit(" + status + ") called");
620: _notifyInterpreterExited(status);
621: }
622:
623: /** Notifies listeners that the interpreter has exited unexpectedly. (Subclasses must maintain listeners.)
624: * @param status Status code of the dead process
625: */
626: protected abstract void _notifyInterpreterExited(int status);
627:
628: /** Called when the interpreter starts to reset. */
629: public void interpreterResetting() {
630: // Utilities.showDebug("InteractionsModel: interpreterResetting called. _waitingForFirstInterpreter = " +
631: // _waitingForFirstInterpreter);
632: if (!_waitingForFirstInterpreter) {
633: _document.acquireWriteLock();
634: try {
635: _document.insertBeforeLastPrompt(
636: "Resetting Interactions ...\n",
637: InteractionsDocument.ERROR_STYLE);
638: _document.setInProgress(true);
639: } finally {
640: _document.releaseWriteLock();
641: }
642: // Utilities.showDebug("interpreter resetting in progress");
643:
644: // Change to a new debug port to avoid conflicts
645: try {
646: _createNewDebugPort();
647: } catch (IOException ioe) {
648: // Oh well, leave it at the previous port
649: }
650: _notifyInterpreterResetting();
651: // Utilities.showDebug("InteractionsModel: interpreterResetting notification complete");
652: }
653: }
654:
655: /** Notifies listeners that the interpreter is resetting. (Subclasses must maintain listeners.) */
656: protected abstract void _notifyInterpreterResetting();
657:
658: /** This method is called by the Main JVM if the Interpreter JVM cannot be exited
659: * @param t The Throwable thrown by System.exit
660: */
661: public void interpreterResetFailed(Throwable t) {
662: _interpreterResetFailed(t);
663: _document.setInProgress(false);
664: _notifyInterpreterResetFailed(t);
665: }
666:
667: /** Any extra action to perform (beyond notifying listeners) when the interpreter fails to reset.
668: * @param t The Throwable thrown by System.exit
669: */
670: protected abstract void _interpreterResetFailed(Throwable t);
671:
672: /** Notifies listeners that the interpreter reset failed. (Subclasses must maintain listeners.)
673: * @param t Throwable explaining why the reset failed.
674: */
675: protected abstract void _notifyInterpreterResetFailed(Throwable t);
676:
677: public String getBanner() {
678: return _banner;
679: }
680:
681: public String getStartUpBanner() {
682: return getBanner(_workingDirectory);
683: }
684:
685: public static String getBanner(File wd) {
686: return BANNER_PREFIX + " Working directory is " + wd + '\n';
687: }
688:
689: private String generateBanner(File wd) {
690: _banner = getBanner(wd);
691: return _banner;
692: }
693:
694: /** Called when a new Java interpreter has registered and is ready for use. */
695: public void interpreterReady(File wd) {
696: // System.err.println("interpreterReady(" + wd + ") called in InteractionsModel"); // DEBUG
697: // System.out.println("_waitingForFirstInterpreter = " + _waitingForFirstInterpreter); // DEBUG
698: if (!_waitingForFirstInterpreter) {
699: _document.reset(generateBanner(wd));
700: _document.setInProgress(false);
701: _notifyInterpreterReady(wd);
702: }
703: _waitingForFirstInterpreter = false;
704: }
705:
706: /** Notifies listeners that the interpreter is ready. (Subclasses must maintain listeners.) */
707: public abstract void _notifyInterpreterReady(File wd);
708:
709: /** Called when the slave JVM has been used for interpretation or unit testing. */
710: public void slaveJVMUsed() {
711: _notifySlaveJVMUsed();
712: }
713:
714: /** Notifies listeners that the slave JVM has been used. (Subclasses must maintain listeners.) */
715: protected abstract void _notifySlaveJVMUsed();
716:
717: /** Assumes a trimmed String. Returns a string of the main call that the interpretor can use. */
718: protected static String _testClassCall(String s) {
719: if (s.endsWith(";"))
720: s = _deleteSemiColon(s);
721: List<String> args = ArgumentTokenizer.tokenize(s, true);
722: boolean seenArg = false;
723: final String className = args.get(1);
724: final StringBuilder mainCall = new StringBuilder();
725: mainCall.append(className.substring(1, className.length() - 1));
726: mainCall.append(".main(new String[]{");
727: for (int i = 2; i < args.size(); i++) {
728: if (seenArg)
729: mainCall.append(",");
730: else
731: seenArg = true;
732: mainCall.append(args.get(i));
733: }
734: mainCall.append("});");
735: return mainCall.toString();
736: }
737:
738: /** Deletes the last character of a string. Assumes semicolon at the end, but does not check. Helper
739: * for _testClassCall(String).
740: * @param s the String containing the semicolon
741: * @return a substring of s with one less character
742: */
743: protected static String _deleteSemiColon(String s) {
744: return s.substring(0, s.length() - 1);
745: }
746:
747: /** Singleton InputListener which should never be asked for input. */
748: private static class NoInputListener implements InputListener {
749: public static final NoInputListener ONLY = new NoInputListener();
750:
751: private NoInputListener() {
752: }
753:
754: public String getConsoleInput() {
755: throw new IllegalStateException(
756: "No input listener installed!");
757: }
758: }
759:
760: /** Gets the console tab document for this interactions model */
761: public abstract ConsoleDocument getConsoleDocument();
762:
763: /** Return the last error as a pair (exception class name, message), or null if successful. */
764: public Pair<String, String> getLastError() {
765: return _lastError;
766: }
767:
768: /** Return the second to last error as a pair (exception class name, message), or null if successful. */
769: public Pair<String, String> getSecondToLastError() {
770: return _secondToLastError;
771: }
772:
773: /** Reset the information about the last and second to last error. */
774: public void resetLastErrors() {
775: _lastError = _secondToLastError = null;
776: }
777:
778: /** Returns the last history item and then removes it, or returns null if the history is empty. */
779: public String removeLastFromHistory() {
780: return _document.removeLastFromHistory();
781: }
782: }
|