001/*
002 * Copyright (c) 2003-2017 The Regents of the University of California.
003 * All rights reserved.
004 *
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 */
025
026package ptolemy.actor.lib.r;
027
028import java.io.BufferedWriter;
029import java.io.File;
030import java.io.FileOutputStream;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.InputStreamReader;
034import java.io.OutputStreamWriter;
035import java.io.PrintWriter;
036import java.io.StringReader;
037import java.io.UnsupportedEncodingException;
038import java.net.URL;
039import java.text.NumberFormat;
040import java.util.ArrayList;
041import java.util.Iterator;
042import java.util.List;
043import java.util.Set;
044
045import org.apache.commons.lang.StringEscapeUtils;
046import org.apache.commons.logging.Log;
047import org.apache.commons.logging.LogFactory;
048//import org.kepler.util.DotKeplerManager;
049
050import ptolemy.actor.TypedAtomicActor;
051import ptolemy.actor.TypedIOPort;
052import ptolemy.actor.gui.BrowserLauncher;
053import ptolemy.actor.gui.style.TextStyle;
054import ptolemy.data.ArrayToken;
055import ptolemy.data.BooleanMatrixToken;
056import ptolemy.data.BooleanToken;
057import ptolemy.data.DoubleMatrixToken;
058import ptolemy.data.DoubleToken;
059import ptolemy.data.FloatToken;
060import ptolemy.data.IntMatrixToken;
061import ptolemy.data.MatrixToken;
062import ptolemy.data.RecordToken;
063import ptolemy.data.StringToken;
064import ptolemy.data.Token;
065import ptolemy.data.expr.Parameter;
066import ptolemy.data.expr.StringParameter;
067import ptolemy.data.type.ArrayType;
068import ptolemy.data.type.BaseType;
069import ptolemy.data.type.Type;
070import ptolemy.kernel.CompositeEntity;
071import ptolemy.kernel.util.IllegalActionException;
072import ptolemy.kernel.util.InternalErrorException;
073import ptolemy.kernel.util.NameDuplicationException;
074import ptolemy.kernel.util.Nameable;
075import ptolemy.kernel.util.StringAttribute;
076import ptolemy.kernel.util.Workspace;
077import ptolemy.util.StringUtilities;
078import ptolemy.vergil.toolbox.TextEditorTableauFactory;
079//import util.WorkflowExecutionListener;
080
081////RExpression
082/**
083 * The RExpression actor is an actor designed to run an R script or function
084 * with inputs and outputs determined by the ports created by the user. Port
085 * names will correspond to R object names. The RExpression actor is modeled
086 * after the Ptolemy expression actor, except that that instead of using a
087 * single mathematical expression in Ptolemy's expression language, it uses a
088 * set of the more powerful, higher order expressions available under R. Both
089 * input and output port will usually be added to the actor; The names of these
090 * ports correspond to R variables used in the R script.
091 *
092 * @author Dan Higgins, NCEAS, UC Santa Barbara
093 * @version $Id$
094 * @since Ptolemy II 11.0
095 */
096
097public class RExpression extends TypedAtomicActor {
098
099    /**
100     * Construct an actor with the given container and name.
101     *
102     * @param container
103     *            The container.
104     * @param name
105     *            The name of this actor.
106     * @exception IllegalActionException
107     *                If the actor cannot be contained by the proposed
108     *                container.
109     * @exception NameDuplicationException
110     *                If the container already has an actor with this name.
111     */
112    public RExpression(CompositeEntity container, String name)
113            throws NameDuplicationException, IllegalActionException {
114        super(container, name);
115
116        expression = new StringAttribute(this, "expression");
117        expression.setDisplayName("R function or script");
118        new TextStyle(expression, "R Expression"); //looks odd, but gives us the larger text area
119        expression.setExpression("a <- c(1,2,3,5)\nplot(a)");
120
121        // use the text editor when we "open" the actor
122        TextEditorTableauFactory _editorFactory = new TextEditorTableauFactory(
123                this, "_editorFactory");
124        _editorFactory.attributeName.setExpression("expression");
125
126        Rcwd = new StringParameter(this, "Rcwd");
127        Rcwd.setDisplayName("R working directory");
128        //Rcwd.setExpression( DotKeplerManager.getInstance()
129        //                .getTransientModuleDirectory("r").toString() );
130
131        save_nosave = new StringParameter(this, "save_nosave");
132        save_nosave.setDisplayName("Save or not");
133        save_nosave.setExpression(_NO_SAVE);
134        save_nosave.addChoice(_NO_SAVE);
135        save_nosave.addChoice(_SAVE);
136
137        graphicsFormat = new StringParameter(this, "graphicsFormat");
138        graphicsFormat.setDisplayName("Graphics Format");
139        graphicsFormat.setExpression("png");
140        graphicsFormat.addChoice("pdf");
141        graphicsFormat.addChoice("png");
142        graphicsFormat.addChoice("jpg");
143        graphicsFormat.addChoice("bmp");
144        graphicsFormat.addChoice("tiff");
145        graphicsFormat.addChoice("eps");
146        graphicsFormat.addChoice("ps");
147        //graphicsFormat.addChoice("wmf");
148        graphicsFormat.addChoice("svg");
149        //graphicsFormat.addChoice("fig");
150        graphicsFormat.addChoice("ghostscript bitmap type pngalpha");
151        graphicsFormat.addChoice("ghostscript bitmap type png16m");
152        graphicsFormat.addChoice("ghostscript bitmap type png256");
153
154        // restore parameter is removed for now because it doesn't work
155        // .RData is saved in the working directory by 'save' but R doesn't look
156        // there
157        // To restore a saved workspace, add the command 'load(".RData') to the
158        // script
159        // restore_norestore = new StringParameter(this, "restore or not");
160        // restore_norestore.setExpression(_NO_RESTORE);
161        // restore_norestore.addChoice(_NO_RESTORE);
162        // restore_norestore.addChoice(RESTORE);
163
164        graphicsOutput = new Parameter(this, "graphicsOutput");
165        graphicsOutput.setDisplayName("Graphics Output");
166        graphicsOutput.setTypeEquals(BaseType.BOOLEAN);
167        graphicsOutput.setToken(BooleanToken.TRUE);
168
169        displayGraphicsOutput = new Parameter(this, "displayGraphicsOutput");
170        displayGraphicsOutput.setDisplayName("Automatically display graphics");
171        displayGraphicsOutput.setTypeEquals(BaseType.BOOLEAN);
172        displayGraphicsOutput.setToken(BooleanToken.FALSE);
173
174        numXPixels = new StringParameter(this, "numXPixels");
175        numXPixels.setDisplayName("Number of X pixels in image");
176        numXPixels.setExpression("480");
177        numYPixels = new StringParameter(this, "numYPixels");
178        numYPixels.setDisplayName("Number of Y pixels in image");
179        numYPixels.setExpression("480");
180
181        graphicsFileName = new TypedIOPort(this, "graphicsFileName", false,
182                true);
183        graphicsFileName.setTypeEquals(BaseType.STRING);
184
185        output = new TypedIOPort(this, "output", false, true);
186        output.setTypeEquals(BaseType.STRING);
187
188    }
189
190    /** The log. */
191    public static Log log = LogFactory.getLog(RExpression.class);
192
193    ///////////////////////////////////////////////////////////////////
194    ////            ports and parameters                           ////
195
196    /**
197     * The output port.
198     */
199    public TypedIOPort output;
200
201    /**
202     * The expression that is evaluated to produce the output.
203     */
204    public StringAttribute expression;
205
206    /**
207     * This setting determines whether or not to save the R workspace when R is
208     * closed; set to '--save' if you need to retreive the workspace later in a
209     * workflow in another RExpression actor.
210     */
211    public StringParameter save_nosave;
212
213    /**
214     * The 'R' working directory (home dir by default).
215     */
216    public StringParameter Rcwd;
217
218    /**
219     * If <i>true</i>, then display plot. If <i>false</i>, then don't. (the
220     * default).
221     */
222    public Parameter displayGraphicsOutput;
223
224    /**
225     * The graphics output format. Currently the format is either a *.pdf or a
226     * *.png
227     */
228    public StringParameter graphicsFormat;
229
230    /**
231     * If <i>true</i>, then create a graphics output port. (the default); If
232     * <i>false</i>, then don't.
233     */
234    public Parameter graphicsOutput;
235
236    /**
237     *The width of the output graphics bitmap in pixels.
238     */
239    public StringParameter numXPixels;
240
241    /**
242     * The height of the output graphics bitmap in pixels.
243     */
244    public StringParameter numYPixels;
245
246    /**
247     * The name of the default graphics output file created by the actor.
248     */
249    public TypedIOPort graphicsFileName;
250
251    /**
252     * Override the base class to set type constraints.
253     *
254     * @param workspace
255     *            The workspace for the new object.
256     * @return A new instance of RExpression.
257     * @exception CloneNotSupportedException
258     *                If a derived class contains an attribute that cannot be
259     *                cloned.
260     */
261    @Override
262    public Object clone(Workspace workspace) throws CloneNotSupportedException {
263        RExpression newObject = (RExpression) super.clone(workspace);
264        newObject.output.setTypeEquals(BaseType.STRING);
265        newObject.graphicsFileName.setTypeEquals(BaseType.STRING);
266        return newObject;
267    }
268
269    /*
270     * The fire method should first call the superclass. Then all the input
271     * ports should be scanned to see which ones have tokens. The names of those
272     * ports should be used to create a named R object (array?). R script for
273     * creating objects corresponding to these ports should be inserted before
274     * the script in the expressions parameter. Then the R engine should be
275     * started and run, with the output sent to the output port.
276     */
277    @Override
278    public synchronized void fire() throws IllegalActionException {
279        String newline = System.getProperty("line.separator");
280
281        super.fire();
282
283        boolean graphicsOutputValue = ((BooleanToken) graphicsOutput.getToken())
284                .booleanValue();
285
286        boolean displayGraphicsOutputValue = ((BooleanToken) displayGraphicsOutput
287                .getToken()).booleanValue();
288
289        _saveString = save_nosave.stringValue();
290        // _restoreString = restore_norestore.stringValue();
291
292        String graphicsFormatString = graphicsFormat.stringValue();
293
294        // following line insures that graphics is pdf if automatically
295        // displayed
296        // NOT going to automatically do this anymore. Do what the user asks.
297        //if (displayGraphicsOutputValue)
298        //        graphicsFormatString = "pdf";
299
300        // force file format to 'pdf' is this is a Mac
301        // NOT going to force PDF for Mac - not sure why this was in place (legacy?)
302        //                String lcOSName = System.getProperty("os.name").toLowerCase();
303        //                boolean MAC_OS_X = lcOSName.startsWith("mac os x");
304        //                if (MAC_OS_X) {
305        //                        graphicsFormatString = "pdf";
306        //                }
307
308        String nxs = numXPixels.stringValue();
309        try {
310            (new Integer(nxs)).intValue();
311        } catch (Exception w) {
312            nxs = "480";
313        }
314
315        String nys = numYPixels.stringValue();
316        try {
317            (new Integer(nys)).intValue();
318        } catch (Exception w1) {
319            nys = "480";
320        }
321
322        String setCWD = "setwd('" + _home + "')\n";
323        _graphicsOutputFile = _getUniqueFileName(graphicsFormatString);
324        String graphicsDevice = "";
325
326        if (graphicsOutputValue) {
327            // Why not move this stuff up to the try statements for nxs and nys?
328            // It looks like we're doing this twice. --Oliver
329            int nxp = (new Integer(nxs)).intValue();
330            double nxd = nxp / 72.0;
331            int nyp = (new Integer(nys)).intValue();
332            double nyd = nyp / 72.0;
333
334            if (graphicsFormatString.equals("pdf")) {
335                graphicsDevice = "pdf(file = '" + _graphicsOutputFile + "'"
336                        + ", width = " + nxd + ", height = " + nyd + ")";
337            } else if (graphicsFormatString.equals("jpeg")
338                    || graphicsFormatString.equals("jpg")) {
339                graphicsDevice = "jpeg(filename = '" + _graphicsOutputFile + "'"
340                        + ", width = " + nxs + ", height = " + nys + ")";
341            } else if (graphicsFormatString.equals("png")) {
342                graphicsDevice = "png(file = '" + _graphicsOutputFile + "'"
343                        + ", width = " + nxs + ", height = " + nys + ")";
344            } else if (graphicsFormatString.equals("bmp")) {
345                graphicsDevice = "bmp(filename = '" + _graphicsOutputFile + "'"
346                        + ", width = " + nxs + ", height = " + nys + ")";
347            } else if (graphicsFormatString.equals("tiff")
348                    || graphicsFormatString.equals("tif")) {
349                graphicsDevice = "tiff(filename = '" + _graphicsOutputFile + "'"
350                        + ", width = " + nxs + ", height = " + nys + ")";
351            } else if (graphicsFormatString.equals("postscript")
352                    || graphicsFormatString.equals("ps")) {
353                _graphicsOutputFile = _getUniqueFileName("ps");
354                graphicsDevice = "postscript(file = '" + _graphicsOutputFile
355                        + "'" + ", width = " + nxd + ", height = " + nyd + ")";
356            } else if (graphicsFormatString.equals("eps")) {
357                graphicsDevice = "setEPS()\n";
358                graphicsDevice += "postscript(file = '" + _graphicsOutputFile
359                        + "'" + ", width = " + nxd + ", height = " + nyd + ")";
360            } else if (graphicsFormatString.equals("win.metafile")
361                    || graphicsFormatString.equals("wmf")) {
362                _graphicsOutputFile = _getUniqueFileName("wmf");
363                graphicsDevice = "win.metafile(filename = '"
364                        + _graphicsOutputFile + "'" + ", width = " + nxd
365                        + ", height = " + nyd + ")";
366            } else if (graphicsFormatString.equals("svg")) {
367                graphicsDevice = "svg(filename = '" + _graphicsOutputFile + "'"
368                        + ", width = " + nxd + ", height = " + nyd + ")";
369            } else if (graphicsFormatString.equals("xfig")
370                    || graphicsFormatString.equals("fig")) {
371                _graphicsOutputFile = _getUniqueFileName("fig");
372                graphicsDevice = "xfig(file = '" + _graphicsOutputFile + "'"
373                        + ", width = " + nxd + ", height = " + nyd + ")";
374            } else if (graphicsFormatString
375                    .equals("ghostscript bitmap type pngalpha")) {
376                graphicsDevice = "bitmap(file = '" + _graphicsOutputFile
377                        + "', type = \"pngalpha\", width = " + nxd
378                        + ", height = " + nyd + ")";
379            } else if (graphicsFormatString
380                    .equals("ghostscript bitmap type png16m")) {
381                graphicsDevice = "bitmap(file = '" + _graphicsOutputFile
382                        + "', type = \"png16m\", width = " + nxd + ", height = "
383                        + nyd + ")";
384            } else if (graphicsFormatString
385                    .equals("ghostscript bitmap type png256")) {
386                graphicsDevice = "bitmap(file = '" + _graphicsOutputFile
387                        + "', type = \"png256\", width = " + nxd + ", height = "
388                        + nyd + ")";
389            }
390        }
391        List ipList = inputPortList();
392        Iterator iter_i = ipList.iterator();
393        _opList = outputPortList();
394        _iterO = _opList.iterator();
395        String RPortInfo = "";
396        RPortInfo = setCWD + graphicsDevice + "\n";
397        Token at;
398        String temp1;
399        while (iter_i.hasNext()) {
400            TypedIOPort tiop = (TypedIOPort) iter_i.next();
401            int multiPortSize = tiop.numberOfSources();
402            List sourcePorts = tiop.sourcePortList();
403            for (int i = 0; i < multiPortSize; i++) {
404                try {
405                    if (tiop.hasToken(i)) {
406                        String finalPortName = tiop.getName();
407                        String sourcePortName = ((TypedIOPort) sourcePorts
408                                .get(i)).getName();
409                        String tempPortName = tiop.getName();
410                        String temp = tiop.getName();
411                        Token token = tiop.get(i);
412                        String token_type_string = token.getType().toString();
413                        String token_class_name = token.getType()
414                                .getTokenClass().getName();
415                        // if this is a multiport, use the upstream source for
416                        // the variable name
417                        if (tiop.isMultiport()) {
418                            temp = temp + i;
419                            tempPortName = temp;
420                        }
421                        // log.debug("token_type_string - " +
422                        // token_type_string);
423                        // log.debug("token_class_name - " +
424                        // token_class_name);
425                        // check token type and convert to R appropriately
426                        if (token_type_string.equals("string")) {
427                            // check for special strings that indicate dataframe
428                            // file reference
429                            at = token;
430                            temp1 = at.toString();
431                            temp1 = temp1.substring(1, temp1.length() - 1); // remove
432                            // quotes
433                            if (temp1.startsWith("_dataframe_:")) {
434                                // assume that the string for a dataframe file
435                                // reference is of the form
436                                // '_dataframe_:"+<filename>
437                                temp1 = temp1.substring(12); // should be
438                                // filename
439                                // temp = "`" + temp + "` <- " +
440                                // "read.table(file='"+temp1+"')";
441                                // RPortInfo = RPortInfo + temp + "\n";
442                                // use binary version that was serialized
443                                RPortInfo = RPortInfo + "conn <- file('" + temp1
444                                        + "', 'rb');\n`" + temp
445                                        + "` <- unserialize(conn);\n"
446                                        + "close(conn);\n";
447
448                                // remove the transfer file when we are done
449                                // consuming it
450                                // this is problematic when dataframes are
451                                // output to multiple sinks!
452                                // String removeCommand = "file.remove('" +
453                                // temp1 + "')";
454                                // RPortInfo = RPortInfo + removeCommand + "\n";
455                                continue; // stop for this token and go to the
456                                // next
457                            } else if (temp1.startsWith("_object_:")) {
458                                // assume that the string for an object file
459                                // reference is of the form
460                                // '_object_:"+<filename>
461                                temp1 = temp1.substring(9); // should be
462                                // filename
463                                // use binary version that was serialized
464                                RPortInfo = RPortInfo + "conn <- file('" + temp1
465                                        + "', 'rb');\n`" + temp
466                                        + "` <- unserialize(conn);\n"
467                                        + "close(conn);\n";
468                                // remove the transfer file when we are done
469                                // consuming it
470                                // this is problematic when objects are output
471                                // to multiple sinks!
472                                // String removeCommand = "file.remove('" +
473                                // temp1 + "')";
474                                // RPortInfo = RPortInfo + removeCommand + "\n";
475                                continue; // stop for this token and go to the
476                                // next
477                            }
478                        }
479                        if (token instanceof RecordToken) {
480                            String Rcommands = _recordToDataFrame(
481                                    (RecordToken) token, temp);
482                            Rcommands = _breakIntoLines(Rcommands);
483                            RPortInfo = RPortInfo + Rcommands + "\n";
484                        }
485
486                        // convert Kepler matrices to R matrices
487                        else if ((token_class_name
488                                .indexOf("IntMatrixToken") > -1)
489                                || (token_class_name
490                                        .indexOf("DoubleMatrixToken") > -1)
491                                || (token_class_name
492                                        .indexOf("BooleanMatrixToken") > -1)) {
493                            int rows = ((MatrixToken) token).getRowCount();
494                            int cols = ((MatrixToken) token).getColumnCount();
495                            temp1 = token.toString();
496                            temp1 = temp1.replace('\\', '/');
497                            temp1 = temp1.replace('[', '(');
498                            temp1 = temp1.replace(']', ')');
499                            temp1 = temp1.replace(';', ',');
500                            temp1 = temp1.replace('"', '\'');
501                            // assume that the token's string value might be
502                            // 'nil' for a missing value
503                            temp1 = temp1.replaceAll("nil", "NA");
504                            // TO DO:if string is long, should create a temp
505                            // file for passing array data
506                            temp = "`" + temp + "` <- matrix(c" + temp1
507                                    + ", nrow=" + rows + ",ncol=" + cols + ")";
508                            temp = _breakIntoLines(temp);
509                            RPortInfo = RPortInfo + temp + "\n";
510                        } else if ((token_type_string.equals("double"))
511                                || (token_type_string.equals("int"))
512                                || (token_type_string.equals("string"))) {
513
514                            at = token;
515                            temp1 = at.toString();
516                            // we need to check here if we are passing a string
517                            // like '/t' (tab)
518                            // Note that quotes are returned around string
519                            // tokens
520                            // The string "/t" is particularly meaningful when
521                            // passed as a seperator
522                            // for R expressions -- DFH April 19
523                            // Note that previous versions of PTII returned
524                            // slightly different format
525                            // strings, so this was not necessary.
526                            if (!temp1.equals("\"\\t\"")) {
527                                temp1 = temp1.replace('\\', '/');
528                                // assume that the token's string value might be
529                                // 'nil' for a missing value
530                                temp1 = temp1.replaceAll("nil", "NA");
531                            }
532                            temp = "`" + temp + "` <- " + temp1;
533                            RPortInfo = RPortInfo + temp + "\n";
534                        } else if ((token_type_string.equals("boolean"))) {
535                            at = token;
536                            temp1 = at.toString();
537                            // ensure uppercase for boolean
538                            temp1 = temp1.toUpperCase();
539                            temp = "`" + temp + "` <- " + temp1;
540                            RPortInfo = RPortInfo + temp + "\n";
541                        } else if ((token_type_string.equals("float"))) {
542                            FloatToken ft = (FloatToken) token;
543                            DoubleToken dt = new DoubleToken(ft.doubleValue());
544                            at = token;
545                            temp1 = dt.toString();
546                            // we need to check here if we are passing a string
547                            // like '/t' (tab)
548                            // Note that quotes are returned around string
549                            // tokens
550                            // The string "/t" is particularly meaningful when
551                            // passed as a seperator
552                            // for R expressions -- DFH April 19
553                            // Note that previous versions of PTII returned
554                            // slightly different format
555                            // strings, so this was not necessary.
556                            if (!temp1.equals("\"\\t\"")) {
557                                temp1 = temp1.replace('\\', '/');
558                                // assume that the token's string value might be
559                                // 'nil' for a missing value
560                                temp1 = temp1.replaceAll("nil", "NA");
561                            }
562                            temp = "`" + temp + "` <- " + temp1;
563                            RPortInfo = RPortInfo + temp + "\n";
564                        } else if ((token_type_string.equals("{double}"))
565                                || (token_type_string.equals("{int}"))
566                                || (token_type_string
567                                        .startsWith("arrayType(double"))
568                                || (token_type_string
569                                        .startsWith("arrayType(int"))
570                                || (token_type_string
571                                        .startsWith("arrayType(niltype"))
572                                || (token_type_string.startsWith(
573                                        "arrayType(arrayType(double"))
574                                || (token_type_string.startsWith(
575                                        "arrayType(arrayType(int"))) {
576                            // token is an arrayToken !!!
577                            at = token;
578                            temp1 = at.toString();
579                            temp1 = temp1.replace('\\', '/');
580                            temp1 = temp1.replaceFirst("\\{", "(");
581                            temp1 = temp1.replaceAll("\\{", "c(");
582                            temp1 = temp1.replace('}', ')');
583                            temp1 = temp1.replace('"', '\'');
584                            // assume that the token's string value might be
585                            // 'nil' for a missing value
586                            temp1 = temp1.replaceAll("nil", "NA");
587                            // if string is long, create a temp file for passing
588                            // array data
589                            if (temp1.length() > _maxCommandLineLength
590                                    && (!token_type_string.startsWith(
591                                            "arrayType(arrayType(double"))
592                                    && (!token_type_string.startsWith(
593                                            "arrayType(arrayType(int"))) {
594                                temp1 = temp1.replace('(', ' ');
595                                temp1 = temp1.replace(')', ' ');
596                                String filename = _writeDataFile(temp1);
597                                temp = "`" + temp + "` <- scan('" + filename
598                                        + "', sep=',')";
599                                temp = temp + "\n" + "file.remove('" + filename
600                                        + "')";
601                                RPortInfo = RPortInfo + temp + "\n";
602                            } else { // otherwise use the modified string
603                                if (token_type_string
604                                        .startsWith("arrayType(arrayType(int")
605                                        || token_type_string.startsWith(
606                                                "arrayType(arrayType(double")) {
607                                    temp = "`" + temp + "` <- list" + temp1;
608                                } else {
609                                    temp = "`" + temp + "` <- c" + temp1;
610                                }
611                                temp = _breakIntoLines(temp);
612                                RPortInfo = RPortInfo + temp + "\n";
613                            }
614                        } else if ((token_type_string.equals("{float}"))
615                                || (token_type_string
616                                        .startsWith("arrayType(float"))
617                                || token_type_string.startsWith(
618                                        "arrayType(arrayType(float")) {
619                            // token is an arrayToken !!!
620                            ArrayToken arrtok = (ArrayToken) token;
621                            StringBuffer buffer = new StringBuffer("{");
622                            for (int j = 0; j < arrtok.length(); j++) {
623                                FloatToken ft = (FloatToken) arrtok
624                                        .getElement(j);
625                                buffer.append(new DoubleToken(ft.doubleValue())
626                                        .toString());
627                                if (j < (arrtok.length() - 1)) {
628                                    buffer.append(", ");
629                                }
630                            }
631                            buffer.append("}");
632
633                            temp1 = buffer.toString();
634                            temp1 = temp1.replace('\\', '/');
635                            temp1 = temp1.replaceFirst("\\{", "(");
636                            temp1 = temp1.replaceAll("\\{", "c(");
637                            temp1 = temp1.replace('}', ')');
638                            temp1 = temp1.replace('"', '\'');
639                            // assume that the token's string value might be
640                            // 'nil' for a missing value
641                            temp1 = temp1.replaceAll("nil", "NA");
642                            // if string is long, create a temp file for passing
643                            // array data
644                            if (temp1.length() > _maxCommandLineLength
645                                    && (!token_type_string.startsWith(
646                                            "arrayType(arrayType(float"))) {
647                                temp1 = temp1.replace('(', ' ');
648                                temp1 = temp1.replace(')', ' ');
649                                String filename = _writeDataFile(temp1);
650                                temp = "`" + temp + "` <- scan('" + filename
651                                        + "', sep=',')";
652                                temp = temp + "\n" + "file.remove('" + filename
653                                        + "')";
654                                RPortInfo = RPortInfo + temp + "\n";
655                            } else { // otherwise use the modified string
656                                if (token_type_string.startsWith(
657                                        "arrayType(arrayType(float")) {
658                                    temp = "`" + temp + "` <- list" + temp1;
659                                } else {
660                                    temp = "`" + temp + "` <- c" + temp1;
661                                }
662                                temp = _breakIntoLines(temp);
663                                RPortInfo = RPortInfo + temp + "\n";
664                            }
665                        } else if ((token_type_string.equals("{string}"))
666                                || (token_type_string
667                                        .startsWith("arrayType(string")
668                                        || (token_type_string.startsWith(
669                                                "arrayType(arrayType(string")))) {
670                            // token is an arrayToken !!!
671                            at = token;
672                            temp1 = at.toString();
673                            temp1 = temp1.replace('\\', '/');
674                            temp1 = temp1.replaceFirst("\\{", "(");
675                            temp1 = temp1.replaceAll("\\{", "c(");
676                            temp1 = temp1.replace('}', ')');
677                            temp1 = temp1.replace('"', '\'');
678                            // assume that the token's string value might be
679                            // 'nil' for a missing value
680                            temp1 = temp1.replaceAll("nil", "NA");
681                            // if string is long, create a temp file for passing
682                            // array data ONLY
683                            if ((temp1.length() > _maxCommandLineLength)
684                                    && (!token_type_string.startsWith(
685                                            "arrayType(arrayType(string"))) {
686                                temp1 = temp1.replace('(', ' ');
687                                temp1 = temp1.replace(')', ' ');
688                                String filename = _writeDataFile(temp1);
689                                temp = "`" + temp + "` <- scan('" + filename
690                                        + "', what='character', sep=',', strip.white=TRUE)";
691                                temp = temp + "\n" + "file.remove('" + filename
692                                        + "')";
693                                RPortInfo = RPortInfo + temp + "\n";
694                            } else { // otherwise use the modified string
695                                //for arrays of arrays, use list()
696                                if (token_type_string.startsWith(
697                                        "arrayType(arrayType(string")) {
698                                    temp = "`" + temp + "` <- list" + temp1;
699                                } else {
700                                    temp = "`" + temp + "` <- c" + temp1;
701                                }
702                                temp = _breakIntoLines(temp);
703                                RPortInfo = RPortInfo + temp + "\n";
704                            }
705                        } else if (token_type_string.equals("niltype")) {
706                            at = token;
707                            temp1 = at.toString();
708                            temp1 = temp1.replaceAll("nil", "NA");
709                            temp = "`" + temp + "` <- " + temp1;
710                            RPortInfo = RPortInfo + temp + "\n";
711                        }
712                        // set metadata on the R objects
713                        // String metadataCommand = null;
714                        if (tiop.isMultiport()) {
715                            // set the metadata on each list item
716                            // ("tempPortName") before adding it to the list
717                            /* metadataCommand = */_createMetadataCommand(
718                                    tempPortName, "name", sourcePortName);
719                        } else {
720                            // just set the metadata attribute for the final
721                            // variable name
722                            /* metadataCommand = */_createMetadataCommand(
723                                    finalPortName, "name", sourcePortName);
724                        }
725                        // add the metadata attribute to the R object
726                        // leinfelder, 4/14/2008:
727                        // do not include the metadata as it introduces
728                        // incompatibility with
729                        // certain R methods that expect attributeless objects
730                        // (barplot(vector))
731                        // RPortInfo = RPortInfo + metadataCommand + "\n";
732
733                        // use lists for making multiport input available in R
734                        if (tiop.isMultiport()) {
735                            String commandList = null;
736                            if (i == 0) {
737                                // create list
738                                commandList = "`" + finalPortName + "` <- list("
739                                        + tempPortName + ")";
740                            } else if (i > 0) {
741                                // append to list
742                                commandList = "`" + finalPortName + "` <- c("
743                                        + finalPortName + ", list("
744                                        + tempPortName + ") )";
745                            }
746                            RPortInfo = RPortInfo + commandList + "\n";
747                        }
748                    }
749                } catch (IllegalActionException iae) {
750                    // just continue (port is probably not connected)
751                }
752            } // for multiport
753        }
754        // log.debug("RPortInfo: "+RPortInfo);
755        // The following command casues R to output a series of 4 dashes which
756        // are used as a marker
757        // Any R output after this marker is used to construct information for
758        // the actor output
759        // ports. This information is removed from the R output text displayed
760        // to the user.
761        StringBuffer r_out = new StringBuffer("cat('----\\n')\n");
762
763        // Ensure that output is echoed from this point on
764        // We don't need to echo before cat('----\\n') because the cat statement forces output.
765        // This way, the options(echo = TRUE) isn't sent to the "output" port
766        r_out.append("options(echo = TRUE)\n");
767        // The following creates an R function called 'myput' to output port
768        // info to output ports
769        // r_out = r_out +
770        // "if (class(x)=='data.frame') {write.table(x,file='"+df_fn+"');cat('_dataframe_:"+df_fn+"')}\n";
771        r_out.append("myput <- function(x, filename) {\n"
772                // I'm wrapping the serialization into the doserialize function
773                // because it's gotten big.  Unique filename generation is
774                // done here because this is where file creation is actually done.
775                // This code relies on the replaceAll code and the added - in the
776                // auto-generated .sav filename.  Remember that a \ in the regular
777                // expression is quadrupled for passing through both Java and R.
778                + "  doserialize <- function(x, filename) {\n"
779                + "    if (file.exists(filename)) {"
780                + "      path <- dirname(filename); "
781                + "      filename <- basename(filename); "
782                + "      base <- sub('^(.*-)([0-9*])\\\\.(.*)$', '\\\\1', filename); "
783                + "      ext <- sub('^(.*-)([0-9*])\\\\.(.*)$', '\\\\3', filename); "
784                + "      dir_base_ext <- dir(pattern = paste(base, '[0-9]*\\\\.', ext, sep = '')); "
785                + "      cnt <- max(as.numeric(sub('^(.*-)([0-9*])\\\\.(.*)$', '\\\\2', dir_base_ext)), na.rm = TRUE) + 1; "
786                + "      filename <- file.path(path, paste(base, cnt, '.', ext, sep = ''))"
787                + "    }\n" + "    conn <- file(filename, 'wb');"
788                + "    serialize(x, conn);" + "    close(conn);"
789                + "    filename" + "  }\n"
790                // use a binary serialization for data frames
791                + "  if (class(x)=='data.frame') {cat('_dataframe_:', doserialize(x, filename), '\\n', sep = '')}\n"
792                + "  else if (class(x)=='matrix') {cat('_matrix_:',deparse(x, control=c('keepNA', 'showAttributes')), '\\n', sep = '') }\n"
793                + "  else if (mode(x)=='numeric' && substr(deparse(x)[1], 1, 9) != \"structure\") {dput(as.double(x), control = NULL)}\n"
794                + "  else if (mode(x)=='character' && substr(deparse(x)[1], 1, 9) != \"structure\") {dput(x)}\n"
795                + "  else if (mode(x)=='logical' && substr(deparse(x)[1], 1, 9) != \"structure\") {dput(x)}\n"
796                // use R serialization for other unknown objects
797                + "  else {cat('_object_:', doserialize(x, filename), '\\n', sep = '')}"
798                + "}\n");
799
800        // Controlled newline test
801        r_out.append("cat(\"before newline\\nafter newline\\n\")\n");
802
803        while (_iterO.hasNext()) {
804            TypedIOPort tiop_o = (TypedIOPort) _iterO.next();
805            String temp_o = tiop_o.getName();
806            // now need to create an R script that returns info about an R
807            // object with the
808            // port name for use in creating Kepler output object
809            if ((!temp_o.equals("output"))
810                    && (!temp_o.equals("graphicsFileName"))) {
811                String df_fn = _getUniqueFileName(temp_o, "sav");
812                String temp_o_escaped = temp_o;
813                // Doing some basic escaping for the exists statement,
814                // although I'm not 100% sure all of these characters
815                // might occur. --Oliver
816                temp_o_escaped = temp_o_escaped.replace("\\", "\\\\");
817                temp_o_escaped = temp_o_escaped.replace("'", "\'");
818                r_out.append("if (exists('" + temp_o_escaped
819                        + "', .GlobalEnv)) {" + "cat(\"portName: " + temp_o
820                        + "\\nvectorVal: \"); " + "myput(get(\""
821                        + temp_o_escaped + "\", .GlobalEnv),'" + df_fn + "'); "
822                        + "cat(\"endVectorVal\\n\")" + "}\n");
823            }
824        }
825
826        String script = expression.getExpression();
827        script = RPortInfo + script + "\n" + r_out + "\nquit()\n";
828        try {
829            _exec();
830        } catch (Exception w) {
831            log.error("Error in _exec()");
832        }
833
834        String outputString = "";
835        String errorString = "";
836        String noRErrorMessage = "There has been a problem running the R script!\n"
837                + "It may be due to an error in your script, it may be that R is not\n"
838                + "installed on your system, or it may not be on your path and cannot\n"
839                + "be located by Kepler.  Please make sure R is installed and the\n"
840                + "R command line executable is in the path."
841                + "For more information, see \n section 8.2.2 of the Kepler User Manual.";
842        try {
843            _inputBufferedWriter.write(script);
844            _inputBufferedWriter.flush();
845            _inputBufferedWriter.close();
846        } catch (IOException ex) {
847            log.error("IOException while executing R script.");
848            // Commenting out this loop--this can cause an infinite loop on XP,
849            // (when R is not on user's PATH), which keeps the noRErrorMessage
850            // from ever showing. See bugs #4985 and #5025.
851            //while (outputString.equals("")) {
852            //        outputString = _outputGobbler.getAndReset();
853            //        errorString = _errorGobbler.getAndReset();
854            //        log.debug("R standard output: " + newline + outputString);
855            //        log.debug("R standard error: " + newline + errorString);
856            //}
857            throw new IllegalActionException(this, ex,
858                    "Problem writing input. " + noRErrorMessage);
859        } catch (NullPointerException npe) {
860            throw new IllegalActionException(this, npe, noRErrorMessage);
861        }
862        try {
863            int result = _process.waitFor();
864            log.debug("Process complete: " + result);
865            if (result != 0) {
866                throw new IllegalActionException(this,
867                        "R returned with value " + result + ", likely caused "
868                                + newline
869                                + "by an error while executing the script.");
870            }
871        } catch (IllegalActionException e) {
872            log.error(e.getMessage());
873            while (outputString.equals("")) {
874                outputString = _outputGobbler.getAndReset();
875                errorString = _errorGobbler.getAndReset();
876                if (_debugging) {
877                    _debug("R standard output: " + newline + outputString);
878                    _debug("R standard error: " + newline + errorString);
879                }
880            }
881            throw e;
882        } catch (Exception www) {
883            log.error("Exception waiting for _process to end!");
884        }
885
886        while (outputString.equals("")) {
887            try {
888                Thread.sleep(100);
889            } catch (Exception e) {
890                log.error("Error in TestApp while sleeping!");
891            }
892            outputString = _outputGobbler.getAndReset();
893            errorString = _errorGobbler.getAndReset();
894            int loc = outputString.lastIndexOf("cat('----\\n')");
895            int loc1 = outputString.lastIndexOf("----");
896            String outputStringDisp = outputString;
897            if (loc1 > -1) {
898                if (loc < 0) {
899                    loc = loc1;
900                }
901                outputStringDisp = outputString.substring(0, loc);
902                String rem = outputString.substring(loc1,
903                        outputString.length());
904                _getOutput(rem);
905            }
906            output.send(0,
907                    new StringToken(outputStringDisp + "\n" + errorString));
908            if (displayGraphicsOutputValue && (!graphicsDevice.equals(""))) {
909                try {
910                    File fout = new File(_home + _graphicsOutputFile);
911                    URL furl = fout.toURL();
912                    BrowserLauncher.openURL(furl.toString());
913                } catch (Exception e) {
914                    log.warn("problem launching browser:" + e);
915                }
916            }
917            if (!graphicsDevice.equals("")) {
918                graphicsFileName.send(0,
919                        new StringToken(_home + _graphicsOutputFile));
920            }
921
922        }
923    }
924
925    @Override
926    public void initialize() throws IllegalActionException {
927        super.initialize();
928        // reset the tempfile counter
929        _counter = 0;
930
931        // set the home
932        _home = Rcwd.stringValue();
933        File homeFile = new File(_home);
934
935        // if not a directory, use 'home'
936        if (!homeFile.isDirectory()) {
937            throw new IllegalActionException(this,
938                    "Rcwd = \"" + _home + "\", which is not a directory?");
939            //home = DotKeplerManager.getInstance()
940            //.getTransientModuleDirectory("r").toString();
941        }
942
943        _home = _home.replace('\\', '/');
944        if (!_home.endsWith("/")) {
945            _home = _home + "/";
946        }
947
948        // reset the name when workflow execution completes
949        //this.getManager().addExecutionListener(
950        //WorkflowExecutionListener.getInstance());
951
952        String workflowName = this.toplevel().getName();
953        // workflowName = workflowName.replace(' ','_');
954        // workflowName = workflowName.replace('-','_');
955        String execDir = _home + workflowName + "_";
956        //+ WorkflowExecutionListener.getInstance().getId(toplevel());
957
958        File dir = new File(execDir);
959        if (!dir.exists()) {
960            if (!dir.mkdir()) {
961                throw new IllegalActionException(null, this,
962                        "Failed to make directory " + dir);
963            }
964            ;
965        }
966        _home = execDir + "/";
967
968    }
969
970    @Override
971    public boolean postfire() throws IllegalActionException {
972        if (_errorGobbler != null) {
973            // If R was not in the path, then there is a chance that
974            // errorGobbler is null.
975            // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3735
976            _errorGobbler.quit();
977        }
978        if (_outputGobbler != null) {
979            _outputGobbler.quit();
980        }
981        return super.postfire();
982    }
983
984    @Override
985    public void preinitialize() throws IllegalActionException {
986        super.preinitialize();
987
988        // Check for "unknown"-to-"unknown" port connections that are "unacceptable"
989        // See: http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3985
990        List _opList = outputPortList();
991        Iterator outputIter = _opList.iterator();
992        while (outputIter.hasNext()) {
993            TypedIOPort outputPort = (TypedIOPort) outputIter.next();
994            if (outputPort.getType().equals(BaseType.UNKNOWN)) {
995                List connectedPorts = outputPort.connectedPortList();
996                Iterator connectedPortIter = connectedPorts.iterator();
997                while (connectedPortIter.hasNext()) {
998                    TypedIOPort connectedPort = (TypedIOPort) connectedPortIter
999                            .next();
1000                    if (connectedPort.getType().equals(BaseType.UNKNOWN)) {
1001                        outputPort.setTypeEquals(BaseType.GENERAL);
1002                        break;
1003                    }
1004                }
1005            }
1006        }
1007
1008    }
1009
1010    // remove for now since it causes problems with ENMs
1011    /*
1012     * public void preinitialize() throws IllegalActionException {
1013     * super.preinitialize(); _opList = outputPortList(); _iterO =
1014     * _opList.iterator(); while (_iterO.hasNext()) { TypedIOPort tiop_o =
1015     * (TypedIOPort)_iterO.next(); tiop_o.setTypeEquals(BaseType.GENERAL); } }
1016     */
1017    /** The pathname of the graphics output file. */
1018    protected String _graphicsOutputFile = "";
1019
1020    /** The home. */
1021    protected String _home;
1022
1023    ///////////////////////////////////////////////////////////////////
1024    ////                         private methods                   ////
1025    private void _getOutput(String str) throws IllegalActionException {
1026        // Newline behavior is inconsistent.  Using an inline test.
1027        //String newline = System.getProperty("line.separator");
1028        int nlp1 = -1;
1029        int nlp2 = -1;
1030        String beforeNL = "\nbefore newline";
1031        String afterNL = "\nafter newline";
1032        nlp1 = str.indexOf(beforeNL);
1033        nlp2 = str.indexOf(afterNL);
1034        String newline = str.substring(nlp1 + beforeNL.length(), nlp2 + 1); // nlp2 + 1 for the \n in afterNL
1035
1036        // log.debug("output-"+str);
1037        // These are the strings we use to find the port and token information
1038        String findStr1 = newline + "portName: ";
1039        String findStr2 = newline + "vectorVal: ";
1040        String findStr3 = newline + "endVectorVal";
1041
1042        int pos1 = -1;
1043        int pos1n = -1;
1044        int pos2e = -1;
1045        int pos3 = -1;
1046        pos1 = str.indexOf(findStr1);
1047        while (pos1 > -1) {
1048            pos1 = pos1 + findStr1.length();
1049            pos1n = str.indexOf(newline, pos1);
1050            String portName = str.substring(pos1, pos1n);
1051
1052            pos2e = str.indexOf(findStr2, pos1) + findStr2.length();
1053            pos3 = str.indexOf(findStr3, pos2e);
1054            String vectorVal = str.substring(pos2e, pos3);
1055            // log.debug("portName: "+portName+ " value: " +vectorVal);
1056            _setOutputToken(portName, vectorVal);
1057
1058            pos1 = str.indexOf(findStr1, pos3);
1059        }
1060    }
1061
1062    private void _setOutputToken(String portName, String tokenValue)
1063            throws IllegalActionException {
1064        _opList = outputPortList();
1065        _iterO = _opList.iterator();
1066        while (_iterO.hasNext()) {
1067            TypedIOPort tiop_o = (TypedIOPort) _iterO.next();
1068            String temp_o = tiop_o.getName();
1069            Token token = null;
1070            if ((!temp_o.equals("output"))
1071                    && (!temp_o.equals("graphicsFileName"))) {
1072                if (temp_o.equals(portName)) {
1073                    try {
1074                        if (tokenValue.equals("TRUE")) {
1075                            BooleanToken btt = BooleanToken.getInstance(true);
1076                            tiop_o.setTypeEquals(BaseType.BOOLEAN);
1077                            token = btt;
1078                        } else if (tokenValue.equals("FALSE")) {
1079                            BooleanToken btf = BooleanToken.getInstance(false);
1080                            tiop_o.setTypeEquals(BaseType.BOOLEAN);
1081                            token = btf;
1082                        } else if (tokenValue.equals("NA")) {
1083                            tiop_o.setTypeEquals(BaseType.STRING);
1084                            token = StringToken.NIL;
1085                            // this solution just sends a string token with
1086                            // value 'nil'
1087                            // in R, 'NA' is considered a boolean state
1088                            // i.e. 3 state logic, so this isn't really correct
1089                            // but nil support for Ptolemy BooleanTokens not yet
1090                            // available
1091
1092                        } else if (tokenValue.startsWith("_dataframe_:")) {
1093                            StringToken st = new StringToken(tokenValue);
1094                            tiop_o.setTypeEquals(BaseType.STRING);
1095                            token = st;
1096                        } else if (tokenValue.startsWith("_object_:")) {
1097                            StringToken st = new StringToken(tokenValue);
1098                            tiop_o.setTypeEquals(BaseType.STRING);
1099                            token = st;
1100                        } else if (tokenValue.startsWith("_matrix_:")) {
1101                            int pos1, pos2;
1102                            pos1 = tokenValue.indexOf(".Dim");
1103                            pos1 = tokenValue.indexOf("c(", pos1);
1104                            pos2 = tokenValue.indexOf(",", pos1);
1105                            String nrowS = tokenValue.substring(pos1 + 2, pos2);
1106                            String ncolS = tokenValue.substring(pos2 + 1,
1107                                    tokenValue.indexOf(")", pos2 + 1));
1108                            int nrows = Integer.parseInt(nrowS.trim());
1109                            int ncols = Integer.parseInt(ncolS.trim());
1110                            pos1 = "_matrix_:".length();
1111                            pos1 = tokenValue.indexOf("c(", pos1);
1112                            pos2 = tokenValue.indexOf(")", pos1);
1113                            String valS = tokenValue.substring(pos1 + 2, pos2);
1114                            pos1 = 0;
1115                            for (int j = 0; j < nrows - 1; j++) {
1116                                for (int i = 0; i < ncols; i++) {
1117                                    pos2 = valS.indexOf(",", pos1);
1118                                    pos1 = pos2 + 1;
1119                                }
1120                                valS = valS.substring(0, pos1 - 1) + ";"
1121                                        + valS.substring(pos1, valS.length());
1122                            }
1123                            valS = "[" + valS + "]";
1124                            MatrixToken mt = null;
1125                            try {
1126                                mt = new IntMatrixToken(valS);
1127                            } catch (Exception ee) {
1128                                try {
1129                                    mt = new DoubleMatrixToken(valS);
1130                                } catch (Exception eee) {
1131                                    mt = new BooleanMatrixToken();
1132                                }
1133                            }
1134                            token = mt;
1135                            tiop_o.setTypeEquals(mt.getType());
1136                        } else if (tokenValue.startsWith("\"")) { // these are strings
1137                            // now remove the start and end quotes
1138                            tokenValue = tokenValue.substring(1,
1139                                    tokenValue.length() - 1);
1140                            //remove the escapes that dput() added
1141                            tokenValue = StringEscapeUtils
1142                                    .unescapeJava(tokenValue);
1143                            StringToken st = new StringToken(tokenValue);
1144                            tiop_o.setTypeEquals(BaseType.STRING);
1145                            token = st;
1146                        }
1147                        NumberFormat nf = NumberFormat.getInstance();
1148                        try {
1149                            nf.parse(tokenValue);
1150                            DoubleToken dt = new DoubleToken(tokenValue);
1151                            tiop_o.setTypeEquals(BaseType.DOUBLE);
1152                            token = dt;
1153                        } catch (Exception eee) {
1154                            // just continue if not a number
1155                        }
1156
1157                        if (tokenValue.startsWith("c(")) { // handles R vectors
1158                            // hack alert! this does not support R's c(1:10)
1159                            // syntax for arrays
1160                            String temp = "{" + tokenValue.substring(2,
1161                                    tokenValue.length());
1162                            temp = temp.replace(')', '}');
1163                            // convert NA values to 'nil'
1164                            temp = temp.replaceAll("NA", "nil");
1165                            ArrayToken at = new ArrayToken(temp);
1166                            tiop_o.setTypeEquals(
1167                                    new ArrayType(at.getElementType()));//, at.length()));
1168                            token = at;
1169                        }
1170
1171                        // check for empty arrays
1172                        if (tokenValue.equals("character(0)")) {
1173                            token = new ArrayToken(BaseType.STRING);
1174                        } else if (tokenValue.equals("numeric(0)")) {
1175                            token = new ArrayToken(BaseType.DOUBLE);
1176                        } else if (tokenValue.equals("logical(0)")) {
1177                            token = new ArrayToken(BaseType.BOOLEAN);
1178                        }
1179
1180                        // verify that we have a token
1181                        if (token == null) {
1182                            log.warn("No token could be created on portName: "
1183                                    + portName + ", for tokenValue: "
1184                                    + tokenValue);
1185                            return;
1186                        }
1187                        // send whatever token we happened to generate - all
1188                        // channels
1189                        // (note: sinkPortList size does not necessarily == port
1190                        // width)
1191                        int numSinkPorts = tiop_o.sinkPortList().size();
1192                        int portWidth = tiop_o.getWidth();
1193
1194                        // check the types of the sink ports for compatibility
1195                        for (int channelIndex = 0; channelIndex < numSinkPorts; channelIndex++) {
1196                            Type sinkType = ((TypedIOPort) tiop_o.sinkPortList()
1197                                    .get(channelIndex)).getType();
1198                            // if (!sinkType.isCompatible(token.getType())) {
1199                            // change to equals for bug #3451:
1200                            if (!sinkType.equals(token.getType())) {
1201                                log.debug("[re]Setting sink type to: "
1202                                        + token.getType().toString());
1203                                // set the Type for the sinks
1204                                // POSSIBLE BUG - not sure why the automatic
1205                                // type resolution was failing for downstream
1206                                // port
1207
1208                                // NOTE: if the token is an array, set the type to be
1209                                // an unbounded array type, since the length may change
1210                                // in the next execution.
1211                                Type tokenType = token.getType();
1212                                if (tokenType instanceof ArrayType) {
1213                                    tokenType = new ArrayType(
1214                                            ((ArrayType) tokenType)
1215                                                    .getElementType());
1216                                }
1217                                ((TypedIOPort) tiop_o.sinkPortList()
1218                                        .get(channelIndex))
1219                                                .setTypeEquals(tokenType);
1220                            }
1221                        }
1222
1223                        // send the token to the channel[s] of the port
1224                        for (int channelIndex = 0; channelIndex < portWidth; channelIndex++) {
1225                            tiop_o.send(channelIndex, token);
1226                        }
1227
1228                    } catch (Exception w) {
1229                        log.error("Problem sending to output port! " + w);
1230                        w.printStackTrace();
1231                        throw new IllegalActionException(this, w,
1232                                "Problem sending to output port.");
1233                    }
1234
1235                    return;
1236                }
1237            }
1238        }
1239    }
1240
1241    // given a recordToken and a portName, create the R script to make a
1242    // dataframe with the
1243    // portName as its R name. Should check that all the items in the record are
1244    // the same length
1245    private String _recordToDataFrame(RecordToken recordToken,
1246            String portName) {
1247        boolean isDataframe = true;
1248        String ret = "";
1249        String temp = "";
1250        String tempA = "";
1251        String labellist = "";
1252        int arrayLength = -1;
1253        Set labels = recordToken.labelSet();
1254        Iterator iter_l = labels.iterator();
1255        ret = "`" + portName + "` <- local({\n";
1256        while (iter_l.hasNext()) {
1257            String label = (String) iter_l.next();
1258            Token labelvaltoken = (recordToken).get(label);
1259            String token_type_string = labelvaltoken.getType().toString();
1260            if ((token_type_string.equals("{double}"))
1261                    || (token_type_string.equals("{int}"))
1262                    || (token_type_string.equals("{string}"))
1263                    || (token_type_string.startsWith("arrayType"))
1264                    || (token_type_string.equals("double"))
1265                    || (token_type_string.equals("int"))
1266                    || (token_type_string.equals("string"))) {
1267                labellist = labellist + "`" + label + "`,";
1268                if (token_type_string.equals("double")
1269                        || token_type_string.equals("int")
1270                        || token_type_string.equals("string")) {
1271                    if (arrayLength == -1) {
1272                        arrayLength = 1;
1273                    } else {
1274                        if (arrayLength != 1) {
1275                            log.warn(
1276                                    "record elements are not all the same length!");
1277                            isDataframe = false;
1278                            //return "";
1279                        }
1280                    }
1281                } else {
1282                    if (arrayLength == -1) {
1283                        arrayLength = ((ArrayToken) labelvaltoken).length();
1284                    } else {
1285                        int a_len = ((ArrayToken) labelvaltoken).length();
1286                        if (a_len != arrayLength) {
1287                            log.warn(
1288                                    "record elements are not all the same length!");
1289                            isDataframe = false;
1290                            //return "";
1291                        }
1292                    }
1293                }
1294                temp = labelvaltoken.toString();
1295                if (token_type_string.equals("double")
1296                        || token_type_string.equals("int")
1297                        || token_type_string.equals("string")) {
1298                    temp = "(" + temp + ")";
1299                }
1300                temp = temp.replace('{', '(');
1301                temp = temp.replace('}', ')');
1302                // using double quotes for strings so that single quotes work
1303                // within them
1304                // temp = temp.replace('"', '\'');
1305                // assume that the token's string value might be 'nil' for a
1306                // missing value
1307                temp = temp.replaceAll("nil", "NA");
1308                // if string is long, create a temp file for passing array data
1309                String temp1 = temp;
1310                // need to estimate the total number of characters that this
1311                // record might have
1312                int estimatedTotalLength = temp1.length() * labels.size();
1313                log.debug("column length: " + temp1.length()
1314                        + " * number of columns: " + labels.size()
1315                        + " = estimated total record length: "
1316                        + estimatedTotalLength + ", _maxCommandLineLength: "
1317                        + _maxCommandLineLength);
1318                if (estimatedTotalLength > _maxCommandLineLength) {
1319                    temp1 = temp1.replace('(', ' ');
1320                    temp1 = temp1.replace(')', ' ');
1321                    String filename = _writeDataFile(temp1);
1322                    if (token_type_string.indexOf("string") > -1) {
1323                        tempA = "`" + label + "` <- scan('" + filename
1324                                + "', sep=',', strip.white=TRUE, what='character' )";
1325                    } else {
1326                        tempA = "`" + label + "` <- scan('" + filename
1327                                + "', sep=',')";
1328                    }
1329                    tempA = tempA + "\n" + "file.remove('" + filename + "')";
1330                    ret = ret + tempA + "\n";
1331                } else { // otherwise use the modified string
1332                    tempA = "`" + label + "` <- c" + temp;
1333                    ret = ret + tempA + "\n";
1334                }
1335
1336            }
1337        }
1338        labellist = labellist.substring(0, labellist.length() - 1); // remove
1339        // last ','
1340        if (isDataframe) {
1341            ret = ret + "data.frame(" + labellist + ", check.names = FALSE)\n";
1342        } else {
1343            ret = ret + "list(" + labellist + ")\n";
1344        }
1345        ret += "})\n";
1346        // log.debug("ret: "+ret);
1347        return ret;
1348    }
1349
1350    // there is ta problem when length of lines sent to R are too long
1351    // thus, break the line into pieces with newlines;
1352    // assume pieces of are approx. 512 chars but must be separate at ','
1353    // (R will accept multiple lines but the seperation cannot be arbitrart;
1354    // i.e. not in
1355    // middle of floating point number)
1356
1357    private String _breakIntoLines(String temp) {
1358        int size = 512;
1359        int pieces = temp.length() / size;
1360        int start = size;
1361        int indx = 0;
1362        for (int k = 0; k < pieces - 1; k++) {
1363            indx = temp.indexOf(",", start);
1364            temp = temp.substring(0, indx) + "\n"
1365                    + temp.substring(indx, temp.length());
1366            start = start + size;
1367        }
1368        return temp;
1369    }
1370
1371    // Execute a command, set _process to point to the subprocess
1372    // and set up _errorGobbler and _outputGobbler to read data.
1373    private void _exec() throws IllegalActionException {
1374        Runtime runtime = Runtime.getRuntime();
1375        String[] commandArray;
1376
1377        String osName = System.getProperty("os.name");
1378        if (osName.equals("Windows NT") || osName.equals("Windows XP")
1379                || osName.equals("Windows 2000")) {
1380            // checkRLocation is commented out for now since it slows down the
1381            // first execution of a
1382            // workflow with an RExpression actor too much (>= 30 sec for a
1383            // 'cold' machine)
1384            _checkRLocation();
1385            commandArray = new String[6];
1386            commandArray[0] = "cmd.exe";
1387            commandArray[1] = "/C";
1388            commandArray[2] = _rDotExe;
1389            commandArray[3] = "--silent";
1390            commandArray[4] = _restoreString;
1391            commandArray[5] = _saveString;
1392        } else if (osName.equals("Windows 95")) {
1393            _checkRLocation();
1394            commandArray = new String[6];
1395            commandArray[0] = "command.com";
1396            commandArray[1] = "/C";
1397            commandArray[2] = _rDotExe;
1398            commandArray[3] = "--silent";
1399            commandArray[4] = _restoreString;
1400            commandArray[5] = _saveString;
1401        } else {
1402            commandArray = new String[4];
1403            commandArray[0] = _rDotExe;
1404            commandArray[1] = "--silent";
1405            commandArray[2] = _restoreString;
1406            commandArray[3] = _saveString;
1407        }
1408
1409        // log.debug("commandArray :"+commandArray);
1410        try {
1411            if (_debugging) {
1412                _debug("ready to create _process!");
1413            }
1414            _process = runtime.exec(commandArray);
1415            if (_debugging) {
1416                _debug("Process :" + _process);
1417            }
1418        } catch (Exception ex) {
1419            throw new IllegalActionException(this, ex,
1420                    "Problem with creating process in RExpression!");
1421        }
1422        if (_debugging) {
1423            _debug("Ready to create threads");
1424        }
1425        // Create two threads to read from the subprocess.
1426        try {
1427            _outputGobbler = new _StreamReaderThread(_process.getInputStream(),
1428                    "Exec Stdout Gobbler-" + _streamReaderThreadCount++, this);
1429            _errorGobbler = new _StreamReaderThread(_process.getErrorStream(),
1430                    "Exec Stderr Gobbler-" + _streamReaderThreadCount++, this);
1431        } catch (UnsupportedEncodingException ex) {
1432            throw new IllegalActionException(this, ex,
1433                    "Failed to open exec process gobblers.");
1434        }
1435        _errorGobbler.start();
1436        _outputGobbler.start();
1437
1438        if (_streamReaderThreadCount > 1000) {
1439            // Avoid overflow in the thread count.
1440            _streamReaderThreadCount = 0;
1441        }
1442
1443        try {
1444            OutputStreamWriter inputStreamWriter = new OutputStreamWriter(
1445                    _process.getOutputStream(), "UTF-8");
1446            _inputBufferedWriter = new BufferedWriter(inputStreamWriter);
1447        } catch (UnsupportedEncodingException ex) {
1448            throw new IllegalActionException(this, ex,
1449                    "Failed to open the output stream: " + _process);
1450        }
1451
1452    }
1453
1454    private void _checkRLocation() {
1455        if (_rDotExe.equals("R")) {
1456            List<File> l = new ArrayList<File>();
1457            // check '$KEPLER/R'
1458            String keplerDir = StringUtilities.getProperty("KEPLER");
1459            _findFile(new File(keplerDir + "/R"), "R.exe", l);
1460            if (!l.isEmpty()) {
1461                _rDotExe = l.get(0) + "";
1462                log.debug(_rDotExe);
1463            }
1464        }
1465    }
1466
1467    private String _getUniqueFileName(String extender) {
1468        int cnt = 1;
1469        // String usr_name = System.getProperty("user.name");
1470        String actor_name = this.getName();
1471        actor_name = actor_name.replaceAll("[^a-zA-Z0-9.]", "_");
1472        String fn = actor_name + "-" + cnt + "." + extender;
1473        String path = _home;
1474        while (new File(path, fn).exists()) {
1475            cnt++;
1476            fn = actor_name + "-" + cnt + "." + extender;
1477        }
1478        return fn;
1479    }
1480
1481    //
1482    // overloaded version for use with new form of .sav files that have portname
1483    // prefix
1484    //
1485    private String _getUniqueFileName(String portname, String extender) {
1486        int cnt = 1;
1487        // String usr_name = System.getProperty("user.name");
1488        String actor_name = this.getName();
1489        // These replaceAll statements will make this operation OS-independent,
1490        // but will increase the likelihood of a .sav file collision since this
1491        // is evaluated before any files are created.  In other words,
1492        // "input 1" and "input_1" will collide with the same .sav file as both
1493        // will find input_1-RExpression-1.sav uncreated.  There is now R code to
1494        // handle this.
1495        actor_name = actor_name.replaceAll("[^a-zA-Z0-9.]", "_");
1496        portname = portname.replaceAll("[^a-zA-Z0-9.]", "_");
1497        String fn = portname + "-" + actor_name + "-" + cnt + "." + extender;
1498        String path = _home;
1499        while (new File(path, fn).exists()) {
1500            cnt++;
1501            fn = portname + "-" + actor_name + "-" + cnt + "." + extender;
1502        }
1503        //make the filename play nice
1504        String retPath = new File(path, fn).getAbsolutePath();
1505        retPath = retPath.replace('\\', '/');
1506        return retPath;
1507    }
1508
1509    private String _createMetadataCommand(String objectName,
1510            String attributeName, String attributeValue) {
1511        String retVal = "attr(`" + objectName + "`, " + "\"" + attributeName
1512                + "\"" + ") <- " + "\"" + attributeValue + "\"";
1513        return retVal;
1514    }
1515
1516    private String _writeDataFile(String dat) {
1517        String fn = "";
1518        StringReader stringReader = null;
1519        PrintWriter printWriter = null;
1520        try {
1521            String home = System.getProperty("user.home");
1522            home = home.replace('\\', '/');
1523            fn = home + "/" + _getUniqueFileName("dat") + _counter;
1524            stringReader = new StringReader(dat);
1525            File dataFile = new File(fn);
1526            OutputStreamWriter w = new OutputStreamWriter(
1527                    new FileOutputStream(dataFile), "UTF-8");
1528            printWriter = new PrintWriter(w);
1529            int c;
1530            while ((c = stringReader.read()) != -1) {
1531                printWriter.write(c);
1532            }
1533        } catch (Exception exc) {
1534            log.error("error writing data file! - RExpression");
1535        } finally {
1536            try {
1537                if (stringReader != null) {
1538                    stringReader.close();
1539                }
1540            } finally {
1541                if (printWriter != null) {
1542                    printWriter.close();
1543                }
1544            }
1545        }
1546        _counter++;
1547        return fn;
1548    }
1549
1550    private void _findFile(File f, String name, List<File> r) {
1551        if (f.isDirectory()) {
1552            File[] files = f.listFiles();
1553            if (files == null) {
1554                return;
1555            }
1556            for (int i = 0; i < files.length; i++) {
1557                // log.debug(files[i]+"");
1558                _findFile(files[i], name, r);
1559            }
1560        } else {
1561            String fn = f + "";
1562            // log.debug("fn: "+fn);
1563            if (fn.indexOf(name) > -1) {
1564                r.add(f);
1565            }
1566        }
1567    }
1568
1569    ///////////////////////////////////////////////////////////////////
1570    ////                         inner classes                     ////
1571
1572    // Private class that reads a stream in a thread and updates the
1573    // stringBuffer.
1574    private class _StreamReaderThread extends Thread {
1575
1576        /**
1577         * Create a _StreamReaderThread.
1578         *
1579         * @param inputStream
1580         *            The stream to read from.
1581         * @param name
1582         *            The name of this StreamReaderThread, which is useful for
1583         *            debugging.
1584         * @param actor
1585         *            The parent actor of this thread, which is used in error
1586         *            messages.
1587         */
1588        _StreamReaderThread(InputStream inputStream, String name,
1589                Nameable actor) throws UnsupportedEncodingException {
1590            super(name);
1591            _inputStream = inputStream;
1592            _inputStreamReader = new InputStreamReader(_inputStream, "UTF-8");
1593            _stringBuffer = new StringBuffer();
1594            _keepRunning = true;
1595            chars = new char[100001];
1596        }
1597
1598        /**
1599         * Read any remaining data in the input stream and return the data read
1600         * thus far. Calling this method resets the cache of data read thus far.
1601         */
1602        public String getAndReset() {
1603            if (_debugging) {
1604                try {
1605                    _debug("getAndReset: Gobbler '" + getName());
1606                    // + "' Ready: " + _inputStreamReader.ready()
1607                    // + " Available: " + _inputStream.available());
1608                    // the previous lines (now commented out) cause a thread
1609                    // problem because (?)
1610                    // the inputStreamReader is used by the threads monitoring
1611                    // process io.
1612
1613                } catch (Exception ex) {
1614                    throw new InternalErrorException(ex);
1615                }
1616            }
1617
1618            // do a final _read before clearing buffer in case some characters
1619            // are available; this was added to collect information that was
1620            // sometimes missing on a newer, faster computer ! -- DFH 11/2005
1621            _read(); // DFH - last chance to read
1622
1623            String results = _stringBuffer.toString();
1624            _stringBuffer = new StringBuffer();
1625
1626            return results;
1627        }
1628
1629        /**
1630         * Read lines from the inputStream and append them to the stringBuffer.
1631         */
1632        @Override
1633        public void run() {
1634            while (_keepRunning) {
1635                // log.debug("Starting read");
1636                _read();
1637                try {
1638                    Thread.sleep(100);
1639                } catch (Exception e) {
1640                    log.error("Error in StreamReaderThread while sleeping!");
1641                }
1642
1643                // log.debug("Finishing read");
1644            }
1645        }
1646
1647        public void quit() {
1648            _keepRunning = false;
1649        }
1650
1651        // Read from the stream until we get to the end of the stream
1652        private void _read() {
1653            // We read the data as a char[] instead of using readline()
1654            // so that we can get strings that do not end in end of
1655            // line chars.
1656
1657            // char [] chars = new char[20001];
1658            int length; // Number of characters read.
1659
1660            try {
1661                // Oddly, InputStreamReader.read() will return -1
1662                // if there is no data present, but the string can still
1663                // read.
1664                length = _inputStreamReader.read(chars, 0, 20000);
1665                if (_debugging) {
1666                    // Note that ready might be false here since
1667                    // we already read the data.
1668                    _debug("_read(): Gobbler '" + getName() + "' Ready: "
1669                            + _inputStreamReader.ready() + " Value: '"
1670                            + String.valueOf(chars, 0, length) + "'");
1671                }
1672                if (length > 0) {
1673                    String temp = String.valueOf(chars, 0, length);
1674                    // _stringBuffer.append(chars, 0, length);
1675                    _stringBuffer.append(temp);
1676                }
1677            } catch (Throwable throwable) {
1678                log.warn("In catch block of _read: " + throwable.getMessage());
1679                _keepRunning = false;
1680            }
1681        }
1682
1683        // character array
1684        private char[] chars;
1685
1686        // StringBuffer to update.
1687        private StringBuffer _stringBuffer;
1688
1689        // Stream from which to read.
1690        private InputStream _inputStream;
1691
1692        // Stream from which to read.
1693        private InputStreamReader _inputStreamReader;
1694
1695        // this thread
1696        private boolean _keepRunning;
1697    }
1698
1699    ///////////////////////////////////////////////////////////////////
1700    ////                         private variables                 ////
1701
1702    // The subprocess gets its input from this BufferedWriter.
1703    private BufferedWriter _inputBufferedWriter;
1704
1705    // StreamReader with which we read stderr.
1706    private _StreamReaderThread _errorGobbler;
1707
1708    // StreamReader with which we read stdout.
1709    private _StreamReaderThread _outputGobbler;
1710
1711    // The Process that we are running.
1712    private Process _process;
1713
1714    // Instance count of output and error threads, used for debugging.
1715    // When the value is greater than 1000, we reset it to 0.
1716    private static int _streamReaderThreadCount = 0;
1717
1718    private List _opList;
1719    private Iterator _iterO;
1720
1721    private static String _NO_SAVE = "--no-save";
1722    private static String _SAVE = "--save";
1723    private static String _NO_RESTORE = "--no-restore";
1724
1725    private static String _rDotExe = "R";
1726
1727    // Temporary file counter.
1728    private int _counter = 0;
1729
1730    private String _saveString;
1731    private String _restoreString = _NO_RESTORE;
1732
1733    // if arrays sent to R are longer than this value, then use a file
1734    // rather than a string on the command line to pass the data
1735    // This is necessary because apparently R has a fixed buffer
1736    // for passing long commands
1737    private int _maxCommandLineLength = 30000;
1738}