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