001/* 002 * $Id: Runner.java,v 1.18 2011/03/24 16:06:33 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 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 031package edu.wisc.ssec.mcidasv.jython; 032 033import static edu.wisc.ssec.mcidasv.util.Contract.notNull; 034 035import java.util.Collections; 036import java.util.List; 037import java.util.concurrent.ArrayBlockingQueue; 038import java.util.concurrent.BlockingQueue; 039 040import org.python.core.PyObject; 041import org.python.core.PyStringMap; 042import org.python.core.PySystemState; 043 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047import edu.wisc.ssec.mcidasv.jython.OutputStreamDemux.OutputType; 048 049/** 050 * This class represents a specialized {@link Thread} that creates and executes 051 * {@link Command}s. A {@link BlockingQueue} is used to maintain thread safety 052 * and to cause a {@code Runner} to wait when the queue is at capacity or has 053 * no {@code Command}s to execute. 054 */ 055public class Runner extends Thread { 056 057 private static final Logger logger = LoggerFactory.getLogger(Runner.class); 058 059 /** The maximum number of {@link Command}s that can be queued. */ 060 private static final int QUEUE_CAPACITY = 10; 061 062 /** 063 * Acts like a global output stream that redirects data to whichever 064 * {@link Console} matches the current thread name. 065 */ 066 private final OutputStreamDemux STD_OUT; 067 068 /** 069 * Acts like a global error stream that redirects data to whichever 070 * {@link Console} matches the current thread name. 071 */ 072 private final OutputStreamDemux STD_ERR; 073 074 /** Queue of {@link Command}s awaiting execution. */ 075 private final BlockingQueue<Command> queue; 076 077 /** */ 078 private final Console console; 079 080 /** */ 081 private final PySystemState systemState; 082 083 /** The Jython interpreter that will actually run the queued commands. */ 084 private final Interpreter interpreter; 085 086 /** Not in use yet. */ 087 private boolean interrupted = false; 088 089 /** 090 * 091 * 092 * @param console 093 */ 094 public Runner(final Console console) { 095 this(console, Collections.<String>emptyList()); 096 } 097 098 /** 099 * 100 * 101 * @param console 102 * @param commands 103 */ 104 public Runner(final Console console, final List<String> commands) { 105 notNull(console, commands); 106 this.console = console; 107 this.STD_ERR = new OutputStreamDemux(); 108 this.STD_OUT = new OutputStreamDemux(); 109 this.queue = new ArrayBlockingQueue<Command>(QUEUE_CAPACITY, true); 110 this.systemState = new PySystemState(); 111 this.interpreter = new Interpreter(systemState, STD_OUT, STD_ERR); 112 for (String command : commands) { 113 queueLine(command); 114 } 115 } 116 117 /** 118 * Registers a new callback handler. Currently this only forwards the new 119 * handler to {@link Interpreter#setCallbackHandler(ConsoleCallback)}. 120 * 121 * @param newCallback The callback handler to register. 122 */ 123 protected void setCallbackHandler(final ConsoleCallback newCallback) { 124 queueCommand(new RegisterCallbackCommand(console, newCallback)); 125 } 126 127 /** 128 * Fetches, copies, and returns the {@link #interpreter}'s local namespace. 129 * 130 * @return Copy of the interpreter's local namespace. 131 */ 132 protected PyStringMap copyLocals() { 133 return ((PyStringMap)interpreter.getLocals()).copy(); 134 } 135 136 /** 137 * Takes commands out of the queue and executes them. We get a lot of 138 * mileage out of BlockingQueue; it's thread-safe and will block if the 139 * queue is at capacity or empty. 140 * 141 * <p>Please note that this method <b>needs</b> to be the first method that 142 * gets called after creating a {@code Runner}. 143 */ 144 public void run() { 145 synchronized (this) { 146 STD_OUT.addStream(console, interpreter, OutputType.NORMAL); 147 STD_ERR.addStream(console, interpreter, OutputType.ERROR); 148 } 149 while (true) { 150 try { 151 // woohoo for BlockingQueue!! 152 Command command = queue.take(); 153 command.execute(interpreter); 154 } catch (Exception e) { 155 System.err.println("Runner.run: badness: " + e.getMessage()); 156 e.printStackTrace(); 157 } 158 } 159 } 160 161 /** 162 * Queues up a series of Jython statements. Currently each command is 163 * treated as though the current user just entered it; the command appears 164 * in the input along with whatever output the command generates. 165 * 166 * @param console Console where the command originated. 167 * @param source Batched command source. Anything but null is acceptable. 168 * @param batch The actual commands to execute. 169 */ 170 public void queueBatch(final String source, 171 final List<String> batch) 172 { 173 queueCommand(new BatchCommand(console, source, batch)); 174 } 175 176 /** 177 * Queues up a line of Jython for execution. 178 * 179 * @param console Console where the command originated. 180 * @param line Text of the command. 181 */ 182 public void queueLine(final String line) { 183 queueCommand(new LineCommand(console, line)); 184 } 185 186 /** 187 * Queues the addition of an object to {@code interpreter}'s local 188 * namespace. 189 * 190 * @param console Likely not needed! 191 * @param name Object name as it will appear to {@code interpreter}. 192 * @param pyObject Object to put in {@code interpreter}'s local namespace. 193 */ 194// public void queueObject(final String name, 195// final PyObject pyObject) 196// { 197// queueCommand(new InjectCommand(console, name, pyObject)); 198// } 199 public void queueObject(final String name, final Object object) { 200 queueCommand(new InjectCommand(console, name, object)); 201 } 202 203 /** 204 * Queues the removal of an object from {@code interpreter}'s local 205 * namespace. 206 * 207 * @param console Console Of Origin! 208 * @param name Name of the object to be removed, <i>as it appears to 209 * Jython</i>. 210 * 211 * @see #queueObject(Console, String, PyObject) 212 */ 213 public void queueRemoval(final String name) { 214 queueCommand(new EjectCommand(console, name)); 215 } 216 217 /** 218 * Queues up a Jython file to be run by {@code interpreter}. 219 * 220 * @param console Likely not needed! 221 * @param name {@code __name__} attribute to use for loading {@code path}. 222 * @param path The path to the Jython file. 223 */ 224 public void queueFile(final String name, 225 final String path) 226 { 227 queueCommand(new LoadFileCommand(console, name, path)); 228 } 229 230 /** 231 * Queues up a command for execution. 232 * 233 * @param command Command to place in the execution queue. 234 */ 235 private void queueCommand(final Command command) { 236 assert command != null : command; 237 try { 238 queue.put(command); 239 } catch (InterruptedException e) { 240 logger.warn("msg='{}' command='{}'", e.getMessage(), command); 241 } 242 } 243 244 @Override public String toString() { 245 return "[Runner@" + Integer.toHexString(hashCode()) + 246 ": interpreter=" + interpreter + ", interrupted=" + interrupted + 247 ", QUEUE_CAPACITY=" + QUEUE_CAPACITY + ", queue=" + queue + "]"; 248 } 249}