001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.sdm.spa;
031
032// Ptolemy packages
033import java.io.DataInputStream;
034import java.io.IOException;
035import java.util.Iterator;
036import java.util.List;
037
038import ptolemy.actor.IOPort;
039import ptolemy.actor.TypedAtomicActor;
040import ptolemy.actor.TypedIOPort;
041import ptolemy.actor.parameters.PortParameter;
042import ptolemy.data.BooleanToken;
043import ptolemy.data.StringToken;
044import ptolemy.data.Token;
045import ptolemy.data.expr.FileParameter;
046import ptolemy.data.expr.Parameter;
047import ptolemy.data.expr.SingletonParameter;
048import ptolemy.data.type.BaseType;
049import ptolemy.kernel.CompositeEntity;
050import ptolemy.kernel.util.Attribute;
051import ptolemy.kernel.util.IllegalActionException;
052import ptolemy.kernel.util.NameDuplicationException;
053
054//////////////////////////////////////////////////////////////////////////
055//// CommandLine
056/**
057 * Given a command string, the <I>CommandLine</I> actor executes it using the
058 * java Runtime class.
059 * <p>
060 * <b>SUPPORTED COMMAND TYPES:</b>
061 * <ul>
062 * <li>command
063 * <li>command < infile > outfile
064 * <li>command > outfile
065 * <li>command < infile
066 * <li>command [arg1..argn] > outfile
067 * <li>command [arg1..argn]
068 * <li>command [arg1..argn] < infile > outfile
069 * <li>command1 | command 2 <I>(<B>Warning</B>: This type of commands doesn't
070 * give the output of all the commands. Instead it outputs only the result of
071 * the last one.)</I>
072 * </ul>
073 * <p>
074 * <b>Example commands:</b>
075 * <ul>
076 * <li>C:/Program Files/Internet Explorer/IEXPLORE.EXE <br>
077 * To generate this command, just double click on the actor and type this in the
078 * <i>command</i> parameter field.
079 * <li>C:/cygwin/bin/perl.exe c:/project/kepler/test/workflows/example.pl >
080 * c:/project/kepler/test/workflows/example.out
081 * <li>C:/cygwin/bin/dir.exe > dirTemp.txt
082 * </ul>
083 * 
084 * @author Ilkay Altintas
085 * @version $Id: CommandLine.java 24234 2010-05-06 05:21:26Z welker $
086 * @category.name external execution
087 * @category.name local
088 */
089
090public class CommandLine extends TypedAtomicActor {
091
092        /**
093         * Construct a CommandLine actor with the given container and name.
094         * 
095         * @param container
096         *            The container.
097         * @param name
098         *            The name of this actor.
099         * @exception IllegalActionException
100         *                If the actor cannot be contained by the proposed
101         *                container.
102         * @exception NameDuplicationException
103         *                If the container already has an actor with this name.
104         */
105        public CommandLine(CompositeEntity container, String name)
106                        throws IllegalActionException, NameDuplicationException {
107                super(container, name);
108
109                // Uncomment the next line to see debugging statements
110                // addDebugListener(new ptolemy.kernel.util.StreamListener());
111
112                // Construct parameters.
113                command = new PortParameter(this, "command");
114
115                // command.setExpression("Please type your command here...");
116                outputFile = new FileParameter(this, "outputFile");
117                // Construct input ports.
118                arguments = new TypedIOPort(this, "arguments", true, false);
119                arguments.setMultiport(true);
120                infileHandle = new TypedIOPort(this, "infileHandle", true, false);
121                infileHandle.setTypeEquals(BaseType.STRING);
122                trigger = new TypedIOPort(this, "trigger", true, false);
123                new Attribute(arguments, "_showName");
124                new Attribute(infileHandle, "_showName");
125                new Attribute(command, "_showName");
126                // Attribute hide = new SingletonAttribute(trigger, "_hide");
127                // hide.setPersistent(false);
128                hide = new SingletonParameter(trigger, "_hide"); // DFH
129                hide.setToken(BooleanToken.TRUE); // DFH
130                // Construct output ports.
131                outfileHandle = new TypedIOPort(this, "outfileHandle", false, true);
132                outfileHandle.setTypeEquals(BaseType.STRING);
133                output = new TypedIOPort(this, "output", false, true);
134                output.setTypeEquals(BaseType.STRING);
135                exitCode = new TypedIOPort(this, "exitCode", false, true);
136                exitCode.setTypeEquals(BaseType.BOOLEAN);
137                new Attribute(output, "_showName");
138                new Attribute(outfileHandle, "_showName");
139                new Attribute(exitCode, "_showName");
140
141                // Set the trigger Flag.
142                outputLineByLine = new Parameter(this, "outputLineByLine",
143                                new BooleanToken(false));
144                outputLineByLine.setTypeEquals(BaseType.BOOLEAN);
145
146                hasTrigger = new Parameter(this, "hasTrigger", new BooleanToken(false));
147                hasTrigger.setTypeEquals(BaseType.BOOLEAN);
148
149                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
150                                + "width=\"60\" height=\"30\" " + "style=\"fill:white\"/>\n"
151                                + "<text x=\"20\" y=\"25\" "
152                                + "style=\"font-size:30; fill:blue; font-family:SansSerif\">"
153                                + "$</text>\n" + "</svg>\n");
154
155        } // constructor
156
157        // //////////////// Public ports and parameters ///////////////////////
158
159        public SingletonParameter hide;
160        /**
161         * @entity.description The command to execute. <BR>
162         *                     <I>FIX ME: </I>The style of the command will be noted
163         *                     here.
164         */
165        public PortParameter command;
166
167        /**
168         * @entity.description Needs to be filled in if the user wants the command to
169         *                     output to a file.
170         */
171        public FileParameter outputFile;
172
173        /**
174         * @entity.description The arguments to the command. Implemented as a
175         *                     multi/input port to support more than one argument.
176         *                     It concatanates the inputs in all the channels. <BR>
177         *                     <I>If there is an input file, this port can be
178         *                     empty.</I>
179         */
180        public TypedIOPort arguments;
181
182        /**
183         * @entity.description This is an optional port. Used if the file accepts an
184         *                     input file instead of a list of arguments.
185         */
186        public TypedIOPort infileHandle;
187
188        /**
189         * @entity.description The trigger port. The type of this port is undeclared,
190         *                     meaning that it will resolve to any data type.
191         */
192        public TypedIOPort trigger;
193
194        /**
195         * @entity.description A string that forwards the outputFile parameter if it
196         *                     exists.
197         */
198        public TypedIOPort outfileHandle;
199        /**
200         * @entity.description The result stream of the command.
201         */
202        public TypedIOPort output;
203
204        /**
205         * @entity.description Exit code will be 1 if the command executes
206         *                     successfully.
207         */
208        public TypedIOPort exitCode;
209
210        /**
211         * @entity.description If selected, broadcasts the output of the command
212         *                     line by line.
213         */
214        public Parameter outputLineByLine;
215
216        /**
217         * @entity.description Unhide the trigger port when this parameter is true.
218         *                     This Parameter is type of boolean. <BR>
219         *                     <b>NOTE: </b>in fact, user can use the port
220         *                     configuration window to hide or unhide a port. This
221         *                     paremeter is here to provide a more intuitive
222         *                     interface for this actor.
223         */
224        public Parameter hasTrigger;
225
226        // /////////////////////////////////////////////////////////////////
227        // // public methods ////
228
229        /**
230         * If the specified attribute is <i>showTriggerPort</i>, then get the value
231         * of it and re-render the trigger port. If it is true, show the trigger
232         * port; if it is false, hide the trigger port.
233         * 
234         * @param attribute
235         *            The attribute that has changed.
236         * @exception IllegalActionException.
237         */
238        public void attributeChanged(Attribute attribute)
239                        throws IllegalActionException {
240                if (attribute == hasTrigger) {
241                        _triggerFlag = ((BooleanToken) hasTrigger.getToken())
242                                        .booleanValue();
243                        _debug("<TRIGGER_FLAG>" + _triggerFlag + "</TRIGGER_FLAG>");
244                        if (_triggerFlag) {
245                                try {
246                                        trigger.setContainer(this);
247                                        // new Attribute(trigger, "_showName");
248                                        hide.setToken(BooleanToken.FALSE); // DFH
249                                } catch (NameDuplicationException ndex) {
250                                        _debug("111: " + ndex.getMessage());
251                                }
252                        } else {
253                                List inPortList = this.inputPortList();
254                                Iterator ports = inPortList.iterator();
255                                while (ports.hasNext()) {
256                                        IOPort p = (IOPort) ports.next();
257                                        if (p.isInput()) {
258                                                try {
259                                                        if (p.getName().equals("trigger")) {
260                                                                // new Attribute(trigger, "_hideName"); //DFH
261                                                                // p.setContainer(null); //DFH
262                                                                hide.setToken(BooleanToken.TRUE); // DFH
263                                                        }
264                                                } catch (Exception e) {
265                                                        throw new IllegalActionException(this, e
266                                                                        .getMessage());
267                                                }
268                                        }
269                                }// while
270                        }// else
271                } else {
272                        super.attributeChanged(attribute);
273                }
274        }
275
276        /**
277         * ... Send the exitCode and outputFileHandle(optional) to the result port.
278         * 
279         * @exception IllegalActionException
280         *                If there is no director.
281         */
282        public void fire() throws IllegalActionException {
283
284                _lineFlag = ((BooleanToken) outputLineByLine.getToken()).booleanValue();
285                _debug("<TRIGGER_FLAG>" + _lineFlag + "</TRIGGER_FLAG>");
286
287                // Get the main command from the command parameter.
288                // _commandStr = ((StringToken)command.getToken()).stringValue();
289                command.update();
290                String _commandStr = ((StringToken) command.getToken()).stringValue();
291
292                // simply consume the trigger token if there is some.
293                if (_triggerFlag) {
294                        if (trigger.getWidth() > 0) {
295                                if (trigger.hasToken(0)) {
296                                        trigger.get(0);
297                                        _debug("consume the token at the trigger port.");
298                                }
299                        }
300                }
301
302                /*
303                 * Consume the input file token if there's one.
304                 */
305                Token tokenFile = null;
306                String value = null;
307                try {
308                        tokenFile = infileHandle.get(0);
309                        if (tokenFile != null) {
310                                value = new String(tokenFile.toString());
311                                _debug("infileHandle(i) = " + value);
312                                value = value.substring(1, value.length() - 1);
313                                _commandStr += " < " + value;
314                        }
315                } catch (Exception ex) {
316                        _debug("Input file is null.");
317                }
318
319                /*
320                 * The arguments can only be accepted if there's no input file. So the
321                 * "value" of the infile handle is checked here and arguments are
322                 * consumed only if it is null.
323                 */
324                // if (value == null) {
325                /*
326                 * Create the argument string. Consume data in all the channels an
327                 * combine them.
328                 */
329                String argString = "";
330                value = new String("");
331                int i = 0;
332                int width = arguments.getWidth();
333                for (i = 0; i < width; i++) {
334                        if (arguments.hasToken(i)) {
335                                Token tokenArg = arguments.get(i);
336                                value = tokenArg.toString();
337                                _debug("arguments(i) = " + value);
338                                value = value.substring(1, value.length() - 1);
339
340                                while (value.indexOf("\\\"") != -1) {
341                                        int ind = value.indexOf("\\\"");
342                                        value = value.substring(0, ind)
343                                                        + value.substring(ind + 1, value.length());
344                                        _debug(value);
345                                }
346                                argString += value + " ";
347                                _debug("argString = " + argString);
348                        }
349                }
350                _commandStr += " " + argString;
351                // }
352
353                int commandCount = getSystemProps();
354
355                /*
356                 * Get the output file path if there's one and add it to the command
357                 * string.
358                 */
359                if (outputFile.asURL() == null) {
360                        _debug("Output file is null.");
361                } else {
362                        String outFilePath = outputFile.asURL().toString();
363                        if (outFilePath.startsWith("file:///")) {
364                                if (_charsToSkip == 6) {
365                                        _charsToSkip = 8;
366                                } else if (_charsToSkip == 5) {
367                                        _charsToSkip = 7;
368                                }
369                        }
370                        outFilePath = outputFile.asURL().toString().substring(_charsToSkip);
371                        _commandStr += " > " + outFilePath;
372                        outfileHandle.broadcast(new StringToken(outFilePath));
373                }
374
375                _commandArr[commandCount] = _commandStr;
376                _debug("<COMMAND>" + _commandArr[commandCount] + "</COMMAND>");
377                // EXECUTION OF THE GENERATED COMMAND.
378                _debug("Executing the command...");
379                try {
380
381                        Runtime rt = Runtime.getRuntime();
382                        Process proc = rt.exec(_commandArr);
383
384                        DataInputStream inStream = new DataInputStream(proc
385                                        .getInputStream());
386                        String result; // Temp for each line of output.
387                        StringBuffer outBuff = new StringBuffer("");
388                        try {
389                                while ((result = inStream.readLine()) != null) {
390                                        _debug(result);
391                                        if (_lineFlag) {
392                                                output.broadcast(new StringToken(result.toString()));
393                                        } else {
394                                                outBuff.append(result + "\n");
395                                        }
396                                }
397                        } catch (IOException ioe) {
398                                _debug("<IOException> when reading the input: " + ioe
399                                                + "</IOException>");
400                        }
401                        if (!_lineFlag) {
402                                output.broadcast(new StringToken(outBuff.toString()));
403                        }
404                        // any error?
405                        int exitVal = proc.waitFor();
406                        _debug("ExitValue: " + exitVal);
407                        // Broadcast the exit status.
408                        if (exitVal == 0) {
409                                exitCode.broadcast(new BooleanToken(true));
410                        } else {
411                                exitCode.broadcast(new BooleanToken(false));
412                        }
413                } catch (Exception ex) {
414                        _debug("<EXCEPTION> An exception occured when executing the command. "
415                                        + " \n\t Exception: " + ex + "\n</EXCEPTION>");
416                }
417        } // end-of-fire
418
419        public int getSystemProps() {
420                // Get OS name
421                String osName = System.getProperty("os.name");
422                _debug("<OS>" + osName + "</OS>");
423                if (osName.equals("Windows NT") || osName.equals("Windows XP")
424                                || osName.equals("Windows 2000")) {
425                        _commandArr[0] = "cmd.exe";
426                        _commandArr[1] = "/C";
427                        _charsToSkip = 6;
428                        return 2;
429                } else if (osName.equals("Windows 95")) {
430                        _commandArr[0] = "command.com";
431                        _commandArr[1] = "/C";
432                        _charsToSkip = 6;
433                        return 2;
434                }
435                /*
436                 * else if (osName.equals("Linux")) { _commandArr[0] = "/bin/sh";
437                 * _commandArr[1] = "-c"; _charsToSkip = 5; return 2; } else if
438                 * (osName.equals("Mac OS X")) { _commandArr[0] = "/bin/sh";
439                 * _commandArr[1] = "-c"; _charsToSkip = 5; return 2; }
440                 */
441                else {
442                        _commandArr[0] = "/bin/sh";
443                        _commandArr[1] = "-c";
444                        _charsToSkip = 5;
445                        return 2;
446                        /* return 0; */
447                }
448        } // end-of-getSystemProps
449
450        // ////////////////////////////////////////////////////////////////////
451        // // private variables ////
452
453        // The combined command to execute.
454        private String _commandStr = "";
455        private String _commandArr[] = new String[3];
456        private boolean _lineFlag = false;
457        private boolean _triggerFlag = false;
458        private int _charsToSkip = 6;
459
460} // end-of-class-CommandLine