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