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 }