001 /* 002 * $Id: Command.java,v 1.21 2012/02/19 17:35:46 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.File; 034 import java.io.InputStream; 035 import java.net.URL; 036 import java.util.ArrayList; 037 import java.util.List; 038 039 import org.python.core.Py; 040 import org.python.core.PyObject; 041 import org.python.core.PyString; 042 import org.python.core.PyStringMap; 043 044 /** 045 * A {@code Command} is an action that can alter the state of an 046 * {@link Interpreter}. 047 */ 048 public abstract class Command { 049 /** Console that created this command. */ 050 protected Console console; 051 052 /** 053 * Creates a command. 054 * 055 * @param console Console that created this command. 056 */ 057 public Command(final Console console) { 058 this.console = console; 059 } 060 061 /** 062 * Hook to provide various implementations of command execution. 063 * 064 * @param interpreter Jython interpreter that will execute the command. 065 * 066 * @throws Exception An error was encountered executing the command. Jython 067 * will catch three standard Python exceptions: SyntaxError, ValueError, 068 * and OverflowError. Other exceptions are thrown. 069 */ 070 public abstract void execute(final Interpreter interpreter) 071 throws Exception; 072 073 /** 074 * Creates a {@link InputStream} using {@code path}. It's here entirely for 075 * convenience. 076 * 077 * @param path Path to the desired file. 078 * 079 * @return {code InputStream} for {@code path}. 080 * 081 * @throws Exception if there was badness. 082 */ 083 protected InputStream getInputStream(final String path) throws Exception { 084 File f = new File(path); 085 if (f.exists()) { 086 return f.toURI().toURL().openStream(); 087 } 088 URL url = getClass().getResource(path); 089 if (url != null) { 090 return url.openStream(); 091 } 092 return null; 093 } 094 } 095 096 /** 097 * This class is a type of {@link Command} that represents a line of Jython. 098 * These sorts of commands are only created by user input in a {@link Console}. 099 */ 100 class LineCommand extends Command { 101 /** The line of jython that needs to be passed to the interpreter */ 102 private String command; 103 104 /** 105 * Creates a command based upon the contents of {@code command}. 106 * 107 * @param console Console where the specified text came from. 108 * @param command Text that will be passed to an {@link Interpreter} for 109 * execution. 110 */ 111 public LineCommand(final Console console, final String command) { 112 super(console); 113 this.command = command; 114 } 115 116 /** 117 * Attempts to execute a line of Jython. Displays the appropriate prompt 118 * on {@link Command#console}, depending upon whether Jython requires more 119 * input. 120 * 121 * @param interpreter Interpreter that will execute this command. 122 * 123 * @throws Exception See {@link Command#execute(Interpreter)}. 124 */ 125 public void execute(final Interpreter interpreter) throws Exception { 126 if (!interpreter.push(console, command)) { 127 interpreter.handleStreams(console, command); 128 console.prompt(); 129 } else { 130 console.moreInput(); 131 } 132 } 133 134 @Override public String toString() { 135 return "[LineCommand@" + Integer.toHexString(hashCode()) + 136 ": command=" + command + "]"; 137 } 138 } 139 140 /** 141 * This class represents a {@link Command} that injects a standard Java 142 * variable into the local namespace of an {@link Interpreter}. This is useful 143 * for allowing Jython to manipulate objects created by the IDV or McIDAS-V. 144 */ 145 //class InjectCommand extends Command { 146 // /** Name Jython will use to refer to {@link #pyObject}. */ 147 // private String name; 148 // 149 // /** Wrapper around the Java object that is being injected. */ 150 // private PyObject pyObject; 151 // 152 // /** 153 // * Creates an injection command based upon the specified name and object. 154 // * 155 // * @param console Likely not required in this context! 156 // * @param name Name Jython will use to refer to {@code pyObject}. 157 // * @param pyObject Wrapper around the Java object that is being injected. 158 // */ 159 // public InjectCommand(final Console console, final String name, 160 // final PyObject pyObject) 161 // { 162 // super(console); 163 // this.name = name; 164 // this.pyObject = pyObject; 165 // } 166 // 167 // /** 168 // * Attempts to inject a variable created in Java into the local namespace 169 // * of {@code interpreter}. 170 // * 171 // * @param interpreter Interpreter that will execute this command. 172 // * 173 // * @throws Exception if {@link Interpreter#set(String, PyObject)} had 174 // * problems. 175 // */ 176 // public void execute(final Interpreter interpreter) throws Exception { 177 // interpreter.set(name, pyObject); 178 // } 179 // 180 // @Override public String toString() { 181 // return "[InjectCommand@" + Integer.toHexString(hashCode()) + 182 // ": name=" + name + ", pyObject=" + pyObject + "]"; 183 // } 184 //} 185 class InjectCommand extends Command { 186 /** Name Jython will use to refer to {@link #object}. */ 187 private String name; 188 189 /** Wrapper around the Java object that is being injected. */ 190 private Object object; 191 192 /** 193 * Creates an injection command based upon the specified name and object. 194 * 195 * @param console Likely not required in this context! 196 * @param name Name Jython will use to refer to {@code object}. 197 * @param object Wrapper around the Java object that is being injected. 198 */ 199 public InjectCommand(final Console console, final String name, 200 final Object object) 201 { 202 super(console); 203 this.name = name; 204 this.object = object; 205 } 206 207 /** 208 * Attempts to inject a variable created in Java into the local namespace 209 * of {@code interpreter}. 210 * 211 * @param interpreter Interpreter that will execute this command. 212 * 213 * @throws Exception if {@link Interpreter#set(String, PyObject)} had 214 * problems. 215 */ 216 public void execute(final Interpreter interpreter) throws Exception { 217 interpreter.set(name, object); 218 } 219 220 @Override public String toString() { 221 return "[InjectCommand@" + Integer.toHexString(hashCode()) + 222 ": name=" + name + ", object=" + object + "]"; 223 } 224 } 225 226 /** 227 * This class represents a {@link Command} that removes an object from the 228 * local namespace of an {@link Interpreter}. These commands can remove any 229 * Jython objects, while {@link InjectCommand} may only inject Java objects. 230 */ 231 class EjectCommand extends Command { 232 /** Name of the Jython object to remove. */ 233 private String name; 234 235 /** 236 * Creates an ejection command for {@code name}. 237 * 238 * @param console Console that requested {@code name}'s removal. 239 * @param name Name of the Jython object that needs removin'. 240 */ 241 public EjectCommand(final Console console, final String name) { 242 super(console); 243 this.name = name; 244 } 245 246 /** 247 * Attempts to remove whatever Jython knows as {@code name} from the local 248 * namespace of {@code interpreter}. 249 * 250 * @param interpreter Interpreter whose local namespace is required. 251 * 252 * @throws Exception if {@link PyObject#__delitem__(PyObject)} had some 253 * second thoughts about ejection. 254 */ 255 public void execute(final Interpreter interpreter) throws Exception { 256 interpreter.getLocals().__delitem__(name); 257 } 258 259 @Override public String toString() { 260 return String.format("[EjectCommand@%x: name=%s]", hashCode(), name); 261 } 262 } 263 264 // TODO(jon): when documenting this, make sure to note that the commands appear 265 // in the console as "normal" user input. 266 class BatchCommand extends Command { 267 private final String bufferSource; 268 private final List<String> commandBuffer; 269 270 public BatchCommand(final Console console, final String bufferSource, 271 final List<String> buffer) 272 { 273 super(console); 274 this.bufferSource = bufferSource; 275 this.commandBuffer = new ArrayList<String>(buffer); 276 } 277 278 public void execute(final Interpreter interpreter) throws Exception { 279 PyStringMap locals = (PyStringMap)interpreter.getLocals(); 280 PyObject currentName = locals.__getitem__(new PyString("__name__")); 281 locals.__setitem__("__name__", new PyString("__main__")); 282 283 for (String command : commandBuffer) { 284 console.insert(Console.TXT_NORMAL, command); 285 if (!interpreter.push(console, command)) { 286 interpreter.handleStreams(console, command); 287 console.prompt(); 288 } else { 289 console.moreInput(); 290 } 291 } 292 locals.__setitem__("__name__", currentName); 293 commandBuffer.clear(); 294 } 295 296 @Override public String toString() { 297 return String.format("[BatchCommand@%x: bufferSource=%s, commandBuffer=%s]", 298 hashCode(), bufferSource, commandBuffer); 299 } 300 } 301 302 class RegisterCallbackCommand extends Command { 303 private final ConsoleCallback callback; 304 public RegisterCallbackCommand(final Console console, final ConsoleCallback callback) { 305 super(console); 306 this.callback = callback; 307 } 308 309 public void execute(final Interpreter interpreter) throws Exception { 310 if (interpreter == null) { 311 throw new NullPointerException("Interpreter is null!"); 312 } 313 interpreter.setCallbackHandler(callback); 314 } 315 } 316 317 /** 318 * This class is a type of {@link Command} that represents a request to use 319 * Jython to run a file containing Jython statements. This is conceptually a 320 * bit similar to importing a module, but the loading is done behind the scenes 321 * and you may specify whatever namespace you like (be careful!). 322 */ 323 class LoadFileCommand extends Command { 324 /** Namespace to use when executing {@link #path}. */ 325 private String name; 326 327 /** Path to the Jython file awaiting execution. */ 328 private String path; 329 330 /** 331 * Creates a command that will attempt to execute a Jython file in the 332 * namespace given by {@code name}. 333 * 334 * @param console Originating console. 335 * @param name Namespace to use when executing {@code path}. 336 * @param path Path to a Jython file. 337 */ 338 public LoadFileCommand(final Console console, final String name, 339 final String path) 340 { 341 super(console); 342 this.name = name; 343 this.path = path; 344 } 345 346 /** 347 * Tries to load the file specified by {@code path} using {@code moduleName} 348 * for the {@code __name__} attribute. Note that this command does not 349 * currently display any results in the originating {@link Console}. 350 * 351 * <p>If {@code moduleName} is not {@code __main__}, this command is 352 * basically the same thing as doing {@code from moduleName import *}. 353 * 354 * <p>If {@code moduleName} <b>is</b> {@code __main__}, then this command 355 * will work for {@code if __name__ == '__main__'} and will run main 356 * functions as expected. 357 * 358 * @param interpreter Interpreter to use to load the specified file. 359 * 360 * @throws Exception if Jython has a problem with running {@code path}. 361 */ 362 public void execute(final Interpreter interpreter) throws Exception { 363 InputStream stream = getInputStream(path); 364 if (stream == null) { 365 return; 366 } 367 PyStringMap locals = (PyStringMap)interpreter.getLocals(); 368 PyObject currentName = locals.__getitem__(new PyString("__name__")); 369 locals.__setitem__("__name__", new PyString(name)); 370 interpreter.execfile(stream, path); 371 locals.__setitem__("__name__", currentName); 372 373 Py.getSystemState().stdout.invoke("flush"); 374 Py.getSystemState().stderr.invoke("flush"); 375 // interpreter.handleStreams(console, " "); 376 // console.prompt(); 377 } 378 379 @Override public String toString() { 380 return "[LoadFileCommand@" + Integer.toHexString(hashCode()) + 381 ": path=" + path + "]"; 382 } 383 }