001 /*
002 * $Id: Interpreter.java,v 1.14 2012/02/19 17:35:47 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030
031 package edu.wisc.ssec.mcidasv.jython;
032
033 import java.io.ByteArrayOutputStream;
034
035 import org.python.core.PyModule;
036 import org.python.core.PyStringMap;
037 import org.python.core.PySystemState;
038 import org.python.core.imp;
039 import org.python.util.InteractiveInterpreter;
040
041 public class Interpreter extends InteractiveInterpreter {
042 /** Dummy filename for the interactive interpreter. */
043 private static final String CONSOLE_FILENAME = "<console>";
044
045 /** Stream used for error output. */
046 private ByteArrayOutputStream stderr;
047
048 /** Stream used for normal output. */
049 private ByteArrayOutputStream stdout;
050
051 /** Whether or not jython needs more input to run something. */
052 private boolean moreInput;
053
054 /** A hook that allows external classes to respond to events. */
055 private ConsoleCallback callback;
056
057 /** Whether or not Jython is working on something */
058 private boolean thinking;
059
060 /**
061 * Creates a Jython interpreter based upon the specified system state and
062 * whose output streams are mapped to the specified byte streams.
063 *
064 * <p>Additionally, the {@literal "__main__"} module is imported by
065 * default so that the locals namespace makes sense.
066 *
067 * @param state The system state you want to use with the interpreter.
068 * @param stdout The stream Jython will use for standard output.
069 * @param stderr The stream Jython will use for error output.
070 */
071 public Interpreter(final PySystemState state,
072 final ByteArrayOutputStream stdout,
073 final ByteArrayOutputStream stderr)
074 {
075 super(null, state);
076 this.stdout = stdout;
077 this.stderr = stderr;
078 this.callback = new DummyCallbackHandler();
079 this.moreInput = false;
080 this.thinking = false;
081
082 setOut(stdout);
083 setErr(stderr);
084
085 PyModule mod = imp.addModule("__main__");
086 PyStringMap locals = ((PyStringMap)mod.__dict__).copy();
087 setLocals(locals);
088 }
089
090 /**
091 * Registers a new callback handler with the interpreter. This mechanism
092 * allows external code to easily react to events taking place in the
093 * interpreter.
094 *
095 * @param newCallback The new callback handler.
096 */
097 protected void setCallbackHandler(final ConsoleCallback newCallback) {
098 callback = newCallback;
099 }
100
101 /**
102 * Here's the magic! Basically just accumulates a buffer that gets passed
103 * off to jython-land until it can run.
104 *
105 * @param line A Jython command.
106 * @return False if Jython did something. True if more input is needed.
107 */
108 public boolean push(Console console, final String line) {
109 if (buffer.length() > 0) {
110 buffer.append('\n');
111 }
112
113 thinking = true;
114 buffer.append(line);
115 moreInput = runsource(buffer.toString(), CONSOLE_FILENAME);
116 if (!moreInput) {
117 String bufferCopy = new String(buffer);
118 resetbuffer();
119 callback.ranBlock(bufferCopy);
120 }
121
122 thinking = false;
123 return moreInput;
124 }
125
126 /**
127 * Determines whether or not Jython is busy.
128 *
129 * @return {@code true} if busy, {@code false} otherwise.
130 */
131 public boolean isBusy() {
132 return thinking;
133 }
134
135 /**
136 *
137 *
138 * @return Whether or not Jython needs more input to run something.
139 */
140 public boolean needMoreInput() {
141 return moreInput;
142 }
143
144 /**
145 * Sends the contents of {@link #stdout} and {@link #stderr} on their
146 * merry way. Both streams are emptied as a result.
147 *
148 * @param console Console where the command originated.
149 * @param command The command that was executed. Null values are permitted,
150 * as they signify that no command was entered for any generated output.
151 */
152 public void handleStreams(final Console console, final String command) {
153 String output = clearStream(command, stdout);
154 if (output.length() != 0) {
155 if (command != null) {
156 console.result(output);
157 } else {
158 console.generatedOutput(output);
159 }
160 }
161
162 String error = clearStream(command, stderr);
163 if (error.length() != 0) {
164 if (command != null) {
165 console.error(error);
166 } else {
167 console.generatedError(error);
168 }
169 }
170 }
171
172 /**
173 * Removes and returns all existing text from {@code stream}.
174 *
175 * @param command Command that was executed. Null values are permitted and
176 * imply that no command is {@literal "associated"} with text in
177 * {@code stream}.
178 * @param stream Stream to be cleared out.
179 *
180 * @return The contents of {@code stream} before it was reset.
181 * @see #handleStreams(Console, String)
182 */
183 private static String clearStream(final String command, final ByteArrayOutputStream stream) {
184 String output = "";
185 if (command == null) {
186 output = stream.toString();
187 } else if (stream.size() > 1) {
188 String text = stream.toString();
189 int end = text.length() - ((command.length() == 0) ? 0 : 1);
190 output = text.substring(0, end);
191 }
192 stream.reset();
193 return output;
194 }
195
196 /**
197 * Sends error information to the specified console.
198 *
199 * @param console The console that caused the exception.
200 * @param e The exception!
201 */
202 public void handleException(final Console console, final Throwable e) {
203 handleStreams(console, " ");
204 console.error(e.toString());
205 console.prompt();
206 }
207 }