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}