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