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.awt.print.*;
041:
042: import edu.rice.cs.drjava.model.print.DrJavaBook;
043:
044: import edu.rice.cs.drjava.model.FileSaveSelector;
045: import edu.rice.cs.util.UnexpectedException;
046: import edu.rice.cs.util.text.ConsoleDocumentInterface;
047: import edu.rice.cs.util.text.EditDocumentException;
048: import edu.rice.cs.util.text.ConsoleDocument;
049: import edu.rice.cs.drjava.config.OptionListener;
050:
051: /** A GUI toolkit-agnostic document that supports console-like interaction with a Java interpreter.
052: * This class assumes that the embedded document supports readers/writers locking and uses that locking
053: * protocol to ensure the integrity of the data added in this class
054: * @version $Id: InteractionsDocument.java 4264 2007-11-15 01:33:48Z mgricken $
055: */
056: public class InteractionsDocument extends ConsoleDocument {
057:
058: /** Default prompt. */
059: public static final String DEFAULT_PROMPT = "> ";
060:
061: /** Style for error messages */
062: public static final String ERROR_STYLE = "error";
063:
064: /** Style for debugger messages */
065: public static final String DEBUGGER_STYLE = "debugger";
066:
067: public static final String OBJECT_RETURN_STYLE = "object.return.style";
068:
069: public static final String STRING_RETURN_STYLE = "string.return.style";
070:
071: public static final String CHARACTER_RETURN_STYLE = "character.return.style";
072:
073: public static final String NUMBER_RETURN_STYLE = "number.return.style";
074:
075: /** Command-line history. It's not reset when the interpreter is reset. */
076: private final History _history;
077:
078: /* Constructors */
079:
080: /** Reset the document on startUp. Uses a history with configurable size.
081: * @param document the edit document to use for the model
082: */
083: public InteractionsDocument(ConsoleDocumentInterface document,
084: String banner) {
085: this (document, new History(), banner);
086: }
087:
088: /** Reset the document on startUp. Uses a history with the given maximum size. This history will not use the config
089: * framework.
090: * @param document EditDocumentInterface to use for the model
091: * @param maxHistorySize Number of commands to remember in the history
092: */
093: public InteractionsDocument(ConsoleDocumentInterface document,
094: int maxHistorySize, String banner) {
095: this (document, new History(maxHistorySize), banner);
096: }
097:
098: /** Creates and resets the interactions document on DrJava startUp. Uses the given history.
099: * @param document EditDocumentInterface to use for the model
100: * @param history History of commands
101: */
102: public InteractionsDocument(ConsoleDocumentInterface document,
103: History history, String banner) {
104: super (document); // initializes _document = document;
105: _history = history;
106: _document.setHasPrompt(true);
107: _prompt = DEFAULT_PROMPT;
108: reset(banner);
109: }
110:
111: /** Lets this document know whether an interaction is in progress.
112: * @param inProgress whether an interaction is in progress
113: */
114: public void setInProgress(boolean inProgress) {
115: _document.setHasPrompt(!inProgress);
116: }
117:
118: /** Returns whether an interaction is currently in progress. Should use ReadLock? */
119: public boolean inProgress() {
120: return !_document.hasPrompt();
121: }
122:
123: /** Resets the document to a clean state. Does not reset the history. */
124: public void reset(String banner) {
125: acquireWriteLock();
126: try {
127: // System.err.println("Resetting the interactions document with banner '" + banner + "'");
128: // Clear interactions document
129: setHasPrompt(false);
130: setPromptPos(0);
131: removeText(0, _document.getLength());
132: insertText(0, banner, OBJECT_RETURN_STYLE);
133: // System.err.println("Inserting prompt in cleared interactions pane");
134: insertPrompt();
135: _history.moveEnd();
136: setInProgress(false); // redundant? also done in InteractionsDocument.interpreterReady(...)
137: } catch (EditDocumentException e) {
138: throw new UnexpectedException(e);
139: } finally {
140: releaseWriteLock();
141: }
142: }
143:
144: /** Replaces any text entered past the prompt with the current item in the history. Assumes that WriteLock is
145: * already held! */
146: private void _replaceCurrentLineFromHistory() {
147: try {
148: _clearCurrentInputText();
149: append(_history.getCurrent(), DEFAULT_STYLE);
150: } catch (EditDocumentException ble) {
151: throw new UnexpectedException(ble);
152: }
153: }
154:
155: /** Accessor method for the history of commands. */
156: public OptionListener<Integer> getHistoryOptionListener() {
157: return _history.getHistoryOptionListener();
158: }
159:
160: /** Adds the given text to the history of commands. */
161: public void addToHistory(String text) {
162: acquireWriteLock();
163: try {
164: _history.add(text);
165: } finally {
166: releaseWriteLock();
167: }
168: }
169:
170: /** Returns the last history item and then removes it, or returns null if the history is empty. */
171: public String removeLastFromHistory() {
172: acquireWriteLock();
173: try {
174: return _history.removeLast();
175: } finally {
176: releaseWriteLock();
177: }
178: }
179:
180: /** Saves the unedited version of the current history to a file
181: * @param selector File to save to
182: */
183: public void saveHistory(FileSaveSelector selector)
184: throws IOException {
185: acquireReadLock(); // does not modify state of document including history
186: try {
187: _history.writeToFile(selector);
188: } finally {
189: releaseReadLock();
190: }
191: }
192:
193: /** Saves the edited version of the current history to a file
194: * @param selector File to save to
195: * @param editedVersion Edited verison of the history which will be
196: * saved to file instead of the lines saved in the history. The saved
197: * file will still include any tags needed to recognize it as a saved
198: * interactions file.
199: */
200: public void saveHistory(FileSaveSelector selector,
201: String editedVersion) throws IOException {
202: acquireReadLock(); // does not modify state of document including history
203: try {
204: _history.writeToFile(selector, editedVersion);
205: } finally {
206: releaseReadLock();
207: }
208: }
209:
210: /** Returns the entire history as a single string. Commands should be separated by semicolons. If an entire
211: * command does not end in a semicolon, one is added.
212: */
213: public String getHistoryAsStringWithSemicolons() {
214: acquireReadLock();
215: try {
216: return _history.getHistoryAsStringWithSemicolons();
217: } finally {
218: releaseReadLock();
219: }
220: }
221:
222: /** Returns the entire history as a single string. Commands should be separated by semicolons. */
223: public String getHistoryAsString() {
224: acquireReadLock();
225: try {
226: return _history.getHistoryAsString();
227: } finally {
228: releaseReadLock();
229: }
230: }
231:
232: /** Clears the history */
233: public void clearHistory() {
234: acquireWriteLock();
235: try {
236: _history.clear();
237: } finally {
238: releaseWriteLock();
239: }
240: }
241:
242: public String lastEntry() {
243: acquireReadLock();
244: try {
245: return _history.lastEntry();
246: } // may throw a RuntimeException if no such entry
247: finally {
248: releaseReadLock();
249: }
250: }
251:
252: /** Puts the previous line from the history on the current line and moves the history back one line.
253: * @param entry the current entry (perhaps edited from what is in history)
254: */
255: public void moveHistoryPrevious(String entry) {
256: acquireWriteLock();
257: try {
258: _history.movePrevious(entry);
259: _replaceCurrentLineFromHistory();
260: } finally {
261: releaseWriteLock();
262: }
263: }
264:
265: /** Puts the next line from the history on the current line and moves the history forward one line.
266: * @param entry the current entry (perhaps edited from what is in history)
267: */
268: public void moveHistoryNext(String entry) {
269: acquireWriteLock();
270: try {
271: _history.moveNext(entry);
272: _replaceCurrentLineFromHistory();
273: } finally {
274: releaseWriteLock();
275: }
276: }
277:
278: /** Returns whether there is a previous command in the history. Assumes that WriteLock is already held!*/
279: private boolean hasHistoryPrevious() {
280: return _history.hasPrevious();
281: }
282:
283: /** Returns whether there is a next command in the history. Assumes that WriteLock is already held!*/
284: public boolean hasHistoryNext() {
285: return _history.hasNext();
286: }
287:
288: /** Reverse searches the history for the given string.
289: * @param searchString the string to search for
290: */
291: public void reverseHistorySearch(String searchString) {
292: acquireWriteLock();
293: try {
294: _history.reverseSearch(searchString);
295: _replaceCurrentLineFromHistory();
296: } finally {
297: releaseWriteLock();
298: }
299: }
300:
301: /** Forward searches the history for the given string.
302: * @param searchString the string to search for
303: */
304: public void forwardHistorySearch(String searchString) {
305: acquireWriteLock();
306: try {
307: _history.forwardSearch(searchString);
308: _replaceCurrentLineFromHistory();
309: } finally {
310: releaseWriteLock();
311: }
312: }
313:
314: /** Gets the previous interaction in the history and replaces whatever is on the current interactions input
315: * line with this interaction. Assumes that the WriteLock is already held!
316: */
317: public boolean recallPreviousInteractionInHistory() {
318: if (hasHistoryPrevious()) {
319: moveHistoryPrevious(getCurrentInteraction());
320: return true;
321: }
322: _beep.run();
323: return false;
324: }
325:
326: /** Gets the next interaction in the history and replaces whatever is on the current interactions input line
327: * with this interaction.
328: */
329: public boolean recallNextInteractionInHistory() {
330: acquireWriteLock();
331: try {
332: if (hasHistoryNext()) {
333: moveHistoryNext(getCurrentInteraction());
334: return true;
335: }
336: _beep.run();
337: return false;
338: } finally {
339: releaseWriteLock();
340: }
341: }
342:
343: /** Reverse searches the history for interactions that started with the current interaction. */
344: public void reverseSearchInteractionsInHistory() {
345: acquireWriteLock();
346: try {
347: if (hasHistoryPrevious())
348: reverseHistorySearch(getCurrentInteraction());
349: else
350: _beep.run();
351: } finally {
352: releaseWriteLock();
353: }
354: }
355:
356: /** Forward searches the history for interactions that started with the current interaction. */
357: public void forwardSearchInteractionsInHistory() {
358: acquireWriteLock();
359: try {
360: if (hasHistoryNext())
361: forwardHistorySearch(getCurrentInteraction());
362: else
363: _beep.run();
364: } finally {
365: releaseWriteLock();
366: }
367: }
368:
369: /** Inserts the given exception data into the document with the given style.
370: * @param exceptionClass Name of the exception that was thrown
371: * @param message Message contained in the exception
372: * @param stackTrace String representation of the stack trace
373: * @param styleName name of the style for formatting the exception
374: */
375: public void appendExceptionResult(String exceptionClass,
376: String message, String stackTrace, String styleName) {
377: // TODO: should probably log this error, or figure out what causes it
378: // it does not seem to affect the program negatively, though
379: if (message != null
380: && (message
381: .equals("Connection refused to host: 127.0.0.1; nested exception is: \n"
382: + "\tjava.net.ConnectException: Connection refused: connect")))
383: return;
384:
385: if (null == message || "null".equals(message))
386: message = "";
387:
388: // Simplify the common error messages
389: if ("koala.dynamicjava.interpreter.error.ExecutionError"
390: .equals(exceptionClass)
391: || "edu.rice.cs.drjava.model.repl.InteractionsException"
392: .equals(exceptionClass)) {
393: exceptionClass = "Error";
394: }
395:
396: // The following is an ugly hack that should be fixed ASAP. The read/writelock methods need to be added to
397: // the EditDocumentInterface interface. This cast and a similar one in ConsoleDocument must be removed because they
398: // defeat the purpose of the EditDocumentInterface interface.
399:
400: String c = exceptionClass;
401: if (c.indexOf('.') != -1)
402: c = c.substring(c.lastIndexOf('.') + 1, c.length());
403:
404: acquireWriteLock();
405: try {
406: append(c + ": " + message + "\n", styleName);
407:
408: // An example stack trace:
409: //
410: // java.lang.IllegalMonitorStateException:
411: // at java.lang.Object.wait(Native Method)
412: // at java.lang.Object.wait(Object.java:425)
413: if (!stackTrace.trim().equals("")) {
414: BufferedReader reader = new BufferedReader(
415: new StringReader(stackTrace));
416:
417: String line;
418: // a line is parsable if it has ( then : then ), with some
419: // text between each of those
420: while ((line = reader.readLine()) != null) {
421: String fileName;
422: int lineNumber;
423:
424: // TODO: Why is this stuff here??
425: int openLoc = line.indexOf('(');
426: if (openLoc != -1) {
427: int closeLoc = line.indexOf(')', openLoc + 1);
428:
429: if (closeLoc != -1) {
430: int colonLoc = line.indexOf(':',
431: openLoc + 1);
432: if ((colonLoc > openLoc)
433: && (colonLoc < closeLoc)) {
434: // ok this line is parsable!
435: String lineNumStr = line.substring(
436: colonLoc + 1, closeLoc);
437: try {
438: lineNumber = Integer
439: .parseInt(lineNumStr);
440: fileName = line.substring(
441: openLoc + 1, colonLoc);
442: } catch (NumberFormatException nfe) {
443: // do nothing; we failed at parsing
444: }
445: }
446: }
447: }
448:
449: append(line, styleName);
450:
451: //JOptionPane.showMessageDialog(null, "\\n");
452: append("\n", styleName);
453:
454: } // end the while
455: }
456: } catch (IOException ioe) {
457: throw new UnexpectedException(ioe);
458: } catch (EditDocumentException ble) {
459: throw new UnexpectedException(ble);
460: } finally {
461: releaseWriteLock();
462: }
463: }
464:
465: public void appendSyntaxErrorResult(String message,
466: String interaction, int startRow, int startCol, int endRow,
467: int endCol, String styleName) {
468: try {
469: if (null == message || "null".equals(message))
470: message = "";
471:
472: if (message.indexOf("Lexical error") != -1) {
473: int i = message.lastIndexOf(':');
474: if (i != -1)
475: message = "Syntax Error:"
476: + message
477: .substring(i + 2, message.length());
478: }
479:
480: if (message.indexOf("Error") == -1)
481: message = "Error: " + message;
482:
483: append(message + "\n", styleName);
484: } catch (EditDocumentException ble) {
485: throw new UnexpectedException(ble);
486: }
487: }
488:
489: /** Clears the current input text and then moves to the end of the command history. */
490: public void clearCurrentInteraction() {
491: acquireWriteLock();
492: try {
493: super .clearCurrentInput();
494: _history.moveEnd();
495: } finally {
496: releaseWriteLock();
497: }
498: }
499:
500: /** Returns the string that the user has entered at the current prompt. Forwards to getCurrentInput(). */
501: public String getCurrentInteraction() {
502: return getCurrentInput();
503: }
504:
505: public String getDefaultStyle() {
506: return InteractionsDocument.DEFAULT_STYLE;
507: }
508:
509: /** This method tells the document to prepare all the DrJavaBook and PagePrinter objects. */
510: public void preparePrintJob() {
511: _book = new DrJavaBook(getDocText(0, getLength()),
512: "Interactions", new PageFormat());
513: }
514:
515: /* Only used for testing. */
516: protected History getHistory() {
517: return _history;
518: }
519: }
|