001/* A tableau for evaluating Exec expression interactively. 002 003 Copyright (c) 2003-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 */ 027package ptolemy.actor.gui.exec; 028 029import java.awt.BorderLayout; 030import java.io.BufferedReader; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.InputStreamReader; 034import java.net.URL; 035 036import javax.swing.BoxLayout; 037import javax.swing.JPanel; 038 039import ptolemy.actor.gui.Effigy; 040import ptolemy.actor.gui.Tableau; 041import ptolemy.actor.gui.TableauFactory; 042import ptolemy.actor.gui.TableauFrame; 043import ptolemy.gui.ShellInterpreter; 044import ptolemy.gui.ShellTextArea; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.NameDuplicationException; 047import ptolemy.kernel.util.NamedObj; 048import ptolemy.util.StringUtilities; 049 050/////////////////////////////////////////////////////////////////// 051//// ExecShellTableau 052 053/** 054 A tableau that provides a Exec Shell for interacting with the Bash shell. 055 056 @author Christopher Hylands and Edward A. Lee 057 @version $Id$ 058 @since Ptolemy II 3.0 059 @Pt.ProposedRating Red (cxh) 060 @Pt.AcceptedRating Red (cxh) 061 */ 062public class ExecShellTableau extends Tableau implements ShellInterpreter { 063 /** Create a new tableau. 064 * The tableau is itself an entity contained by the effigy 065 * and having the specified name. The frame is not made visible 066 * automatically. You must call show() to make it visible. 067 * @param container The containing effigy. 068 * @param name The name of this tableau within the specified effigy. 069 * @exception IllegalActionException If the tableau is not acceptable 070 * to the specified container. 071 * @exception NameDuplicationException If the container already contains 072 * an entity with the specified name. 073 */ 074 public ExecShellTableau(ExecShellEffigy container, String name) 075 throws IllegalActionException, NameDuplicationException { 076 super(container, name); 077 frame = new ExecShellFrame(this); 078 setFrame(frame); 079 080 try { 081 _interpreter = Runtime.getRuntime().exec("bash -i"); 082 } catch (IOException ex) { 083 throw new IllegalActionException(this, ex, 084 "Failed to create Process"); 085 } 086 } 087 088 /////////////////////////////////////////////////////////////////// 089 //// public methods //// 090 091 /** Evaluate the specified command. 092 * @param command The command. 093 * @return The return value of the command, or null if there is none. 094 * @exception Exception If something goes wrong processing the command. 095 */ 096 @Override 097 public String evaluateCommand(String command) throws Exception { 098 _executeCommand(command); 099 100 // FIXME: this is _so_ wrong 101 return ""; 102 } 103 104 /** Return true if the specified command is complete (ready 105 * to be interpreted). 106 * @param command The command. 107 * @return True 108 */ 109 @Override 110 public boolean isCommandComplete(String command) { 111 return true; 112 } 113 114 /** Append the text message to text area. 115 * The output automatically gets a trailing newline appended. 116 * @param text The test to be appended. 117 */ 118 public void stderr( /*final*/String text) { 119 frame.shellTextArea.appendJTextArea(text + "\n"); 120 } 121 122 /** Append the text message to the text area. 123 * The output automatically gets a trailing newline appended. 124 * @param text The test to be appended. 125 */ 126 public void stdout(final String text) { 127 frame.shellTextArea.appendJTextArea(text + "\n"); 128 } 129 130 /////////////////////////////////////////////////////////////////// 131 //// public variables //// 132 133 /** The frame in which text is written. */ 134 public ExecShellFrame frame; 135 136 /////////////////////////////////////////////////////////////////// 137 //// private variables //// 138 // The Exec interpreter 139 // FIXME: Perhaps the interpreter should be in its own thread? 140 private Process _interpreter; 141 142 /////////////////////////////////////////////////////////////////// 143 //// inner classes //// 144 145 /** The frame that is created by an instance of ExecShellTableau. 146 */ 147 @SuppressWarnings("serial") 148 public static class ExecShellFrame extends TableauFrame { 149 // FindBugs suggested refactoring this into a static class. 150 151 /** Construct a frame to display the ExecShell window. 152 * After constructing this, it is necessary 153 * to call setVisible(true) to make the frame appear. 154 * This is typically accomplished by calling show() on 155 * enclosing tableau. 156 * @param execShellTableau The tableau responsible for this frame. 157 * @exception IllegalActionException If the model rejects the 158 * configuration attribute. 159 * @exception NameDuplicationException If a name collision occurs. 160 */ 161 public ExecShellFrame(ExecShellTableau execShellTableau) 162 throws IllegalActionException, NameDuplicationException { 163 super(execShellTableau); 164 165 JPanel component = new JPanel(); 166 component.setLayout(new BoxLayout(component, BoxLayout.Y_AXIS)); 167 168 shellTextArea = new ShellTextArea(); 169 shellTextArea.setInterpreter(execShellTableau); 170 shellTextArea.mainPrompt = "% "; 171 component.add(shellTextArea); 172 getContentPane().add(component, BorderLayout.CENTER); 173 } 174 175 /////////////////////////////////////////////////////////////////// 176 //// public variables //// 177 178 /** The text area tableau used for input and output. */ 179 public ShellTextArea shellTextArea; 180 181 /////////////////////////////////////////////////////////////////// 182 //// protected methods //// 183 @Override 184 protected void _help() { 185 try { 186 URL doc = getClass().getClassLoader() 187 .getResource("ptolemy/actor/gui/ptjacl/help.htm"); 188 getConfiguration().openModel(null, doc, doc.toExternalForm()); 189 } catch (Exception ex) { 190 System.out.println("ExecShellTableau._help(): " + ex); 191 _about(); 192 } 193 } 194 } 195 196 /** A factory that creates a control panel to display a Exec Shell. 197 */ 198 public static class Factory extends TableauFactory { 199 /** Create a factory with the given name and container. 200 * @param container The container. 201 * @param name The name. 202 * @exception IllegalActionException If the container is incompatible 203 * with this attribute. 204 * @exception NameDuplicationException If the name coincides with 205 * an attribute already in the container. 206 */ 207 public Factory(NamedObj container, String name) 208 throws IllegalActionException, NameDuplicationException { 209 super(container, name); 210 } 211 212 /////////////////////////////////////////////////////////////////// 213 //// public methods //// 214 215 /** Create a new instance of ExecShellTableau in the specified 216 * effigy. It is the responsibility of callers of 217 * this method to check the return value and call show(). 218 * @param effigy The model effigy. 219 * @return A new control panel tableau if the effigy is 220 * a PtolemyEffigy, or null otherwise. 221 * @exception Exception If the factory should be able to create a 222 * tableau for the effigy, but something goes wrong. 223 */ 224 @Override 225 public Tableau createTableau(Effigy effigy) throws Exception { 226 // NOTE: Can create any number of tableaux within the same 227 // effigy. Is this what we want? 228 if (effigy instanceof ExecShellEffigy) { 229 return new ExecShellTableau((ExecShellEffigy) effigy, 230 "ExecShellTableau"); 231 } else { 232 return null; 233 } 234 } 235 } 236 237 /////////////////////////////////////////////////////////////////// 238 //// private methods //// 239 // Execute the command. Update the output with 240 // the command being run and the output. 241 private String _executeCommand(String command) { 242 if (command == null || command.length() == 0) { 243 return ""; 244 } 245 246 try { 247 Runtime runtime = Runtime.getRuntime(); 248 249 try { 250 //if (_process != null) { 251 // _process.destroy(); 252 //} 253 // Preprocess by removing lines that begin with '#' 254 // and converting substrings that begin and end 255 // with double quotes into one array element. 256 final String[] commandTokens = StringUtilities 257 .tokenizeForExec(command); 258 259 //stdout("About to execute:\n"); 260 StringBuffer statusCommand = new StringBuffer(); 261 262 for (String commandToken : commandTokens) { 263 //stdout(" " + commandTokens[i]); 264 // Accumulate the first 50 chars for use in 265 // the status buffer. 266 if (statusCommand.length() < 50) { 267 if (statusCommand.length() > 0) { 268 statusCommand.append(" "); 269 } 270 271 statusCommand.append(commandToken); 272 } 273 } 274 275 if (statusCommand.length() >= 50) { 276 statusCommand.append(" . . ."); 277 } 278 279 //updateStatusBar("Executing: " 280 // + statusCommand.toString()); 281 _interpreter = runtime.exec(commandTokens); 282 283 // Set up a Thread to read in any error messages 284 _StreamReaderThread errorGobbler = new _StreamReaderThread( 285 _interpreter.getErrorStream(), this); 286 287 // Set up a Thread to read in any output messages 288 _StreamReaderThread outputGobbler = new _StreamReaderThread( 289 _interpreter.getInputStream(), this); 290 291 // Start up the Threads 292 errorGobbler.start(); 293 outputGobbler.start(); 294 295 try { 296 /*int processReturnCode = */_interpreter.waitFor(); 297 298 synchronized (this) { 299 _interpreter = null; 300 } 301 302 //if (processReturnCode != 0) break; 303 } catch (InterruptedException interrupted) { 304 stderr("InterruptedException: " + interrupted); 305 throw interrupted; 306 } 307 } catch (final IOException io) { 308 stderr("IOException: " + ptolemy.kernel.util.KernelException 309 .stackTraceToString(io)); 310 } 311 } catch (InterruptedException e) { 312 //_interpreter.destroy(); 313 return "Interrupted"; // SwingWorker.get() returns this 314 } 315 316 return "All Done"; // or this 317 } 318 319 /////////////////////////////////////////////////////////////////// 320 //// inner classes //// 321 // Private class that reads a stream in a thread and updates the 322 // JTextArea. 323 private static class _StreamReaderThread extends Thread { 324 // FindBugs suggests making this class static so as to decrease 325 // the size of instances and avoid dangling references. 326 327 _StreamReaderThread(InputStream inputStream, 328 ExecShellTableau execShellTableau) { 329 _inputStream = inputStream; 330 _execShellTableau = execShellTableau; 331 } 332 333 // Read lines from the _inputStream and output them. 334 @Override 335 public void run() { 336 try { 337 InputStreamReader inputStreamReader = new InputStreamReader( 338 _inputStream); 339 BufferedReader bufferedReader = new BufferedReader( 340 inputStreamReader); 341 String line = null; 342 343 while ((line = bufferedReader.readLine()) != null) { 344 _execShellTableau.stdout( /*_streamType + ">" +*/ 345 line); 346 } 347 } catch (IOException ioe) { 348 _execShellTableau.stderr("IOException: " + ioe); 349 } 350 } 351 352 // Stream to read from. 353 private InputStream _inputStream; 354 355 private ExecShellTableau _execShellTableau; 356 } 357}