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