Java tutorial
/* * Copyright (c) 2003-2010 The Regents of the University of California. * All rights reserved. * * '$Author: berkley $' * '$Date: 2010-04-27 17:12:36 -0700 (Tue, 27 Apr 2010) $' * '$Revision: 24000 $' * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies * of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * */ package org.ecoinformatics.seek.R; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kepler.util.DotKeplerManager; import org.rosuda.JRI.RBool; import org.rosuda.JRI.REXP; import org.rosuda.JRI.RFactor; import org.rosuda.JRI.RList; import org.rosuda.JRI.RVector; import org.rosuda.JRI.Rengine; import ptolemy.actor.NoTokenException; import ptolemy.actor.TypedAtomicActor; import ptolemy.actor.TypedIOPort; import ptolemy.actor.gui.BrowserLauncher; import ptolemy.actor.gui.style.TextStyle; import ptolemy.data.ArrayToken; import ptolemy.data.BooleanMatrixToken; import ptolemy.data.BooleanToken; import ptolemy.data.DoubleMatrixToken; import ptolemy.data.DoubleToken; import ptolemy.data.IntMatrixToken; import ptolemy.data.IntToken; import ptolemy.data.MatrixToken; import ptolemy.data.OrderedRecordToken; import ptolemy.data.RecordToken; import ptolemy.data.StringToken; import ptolemy.data.Token; import ptolemy.data.expr.Parameter; import ptolemy.data.expr.StringParameter; import ptolemy.data.type.BaseType; import ptolemy.data.type.Type; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.StringAttribute; import ptolemy.kernel.util.Workspace; import util.WorkflowExecutionListener; ////////////////////////////////////////////////////////////////////////// //// RExpression /** * The RExpression actor is an actor designed to run an R script or function * with inputs and outputs determined by the ports created by the user. Port * names will correspond to R object names. The RExpression actor is modeled * after the Ptolemy expression actor, except that that instead of using a * single mathematical expression in Ptolemy's expression language, it uses a * set of the more powerful, higher order expressions available under R. Both * input and output port will usually be added to the actor; The names of these * ports correspond to R variables used in the R script. * * @author Dan Higgins and Matt Jones, NCEAS, UC Santa Barbara * @version 3/3/2006 * @UserLevelDocumentation This actor let the user insert R scripts in a Kepler * workflow. It requires the R system to be installed on * the computer executing the workflow */ public class RExpression2 extends TypedAtomicActor { public static Log log = LogFactory.getLog(RExpression2.class); private static String noRErrorMessage = "There has been a problem launching R!\n" + "It may be that R is not installed on your system, or it\n" + "may not be on your path and cannot be located by Kepler.\n Please" + "make sure R is installed and the R command line \n executable is in the path." + "For more information, see \n section 8.2.2 of the Kepler User Manual."; private Rengine re = null; private RConsole console = null; // ///////////////////////////////////////////////////////////////// // // ports and parameters //// /** * The output port. */ public TypedIOPort output; /** * The expression that is evaluated to produce the output. */ public StringAttribute expression; /** * The 'R' working directory (home dir by default) */ public StringParameter Rcwd; private static String NO_SAVE = "--no-save"; private static String NO_RESTORE = "--no-restore"; /** * If <i>true</i>, then shoe debugging information about script. * If <i>false</i>, then don't. (the default) */ public Parameter showDebug; /** * If <i>true</i>, then daata frames (and other complexe data objects * will be transferred by serialization to disk. * If <i>false</i>, then they will be converted as losslessly as possible * to a Ptolemy data structure */ public Parameter serializeData; /** * If <i>true</i>, then display plot. If <i>false</i>, then don't. (the * default) */ public Parameter displayGraphicsOutput; /** * The graphics output format. Currently the format is either a *.pdf or a * *.png */ public StringParameter graphicsFormat; /** * If <i>true</i>, then create a graphics output port. (the default); If * <i>false</i>, then don't. */ public Parameter graphicsOutput; /** *The width of the output graphics bitmap in pixels */ public StringParameter numXPixels; /** * The height of the output graphics bitmap in pixels */ public StringParameter numYPixels; /** * The name of the default graphics output file created by the actor */ public TypedIOPort graphicsFileName; /** * Construct an actor with the given container and name. * * @param container * The container. * @param name * The name of this actor. * @exception IllegalActionException * If the actor cannot be contained by the proposed * container. * @exception NameDuplicationException * If the container already has an actor with this name. */ public RExpression2(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException { super(container, name); expression = new StringAttribute(this, "expression"); expression.setDisplayName("R function or script"); new TextStyle(expression, "R Expression"); //keep this for larger text area expression.setExpression("a <- c(1,2,3,5)\nplot(a)"); Rcwd = new StringParameter(this, "Rcwd"); Rcwd.setDisplayName("R working directory"); Rcwd.setExpression(DotKeplerManager.getInstance().getTransientModuleDirectory("r").toString()); graphicsFormat = new StringParameter(this, "graphicsFormat"); graphicsFormat.setDisplayName("Graphics Format"); graphicsFormat.setExpression("png"); graphicsFormat.addChoice("pdf"); graphicsFormat.addChoice("png"); showDebug = new Parameter(this, "showDebug"); showDebug.setDisplayName("Debug"); showDebug.setTypeEquals(BaseType.BOOLEAN); showDebug.setToken(BooleanToken.TRUE); serializeData = new Parameter(this, "serializeData"); serializeData.setDisplayName("Serialize Data Frame"); serializeData.setTypeEquals(BaseType.BOOLEAN); serializeData.setToken(BooleanToken.TRUE); graphicsOutput = new Parameter(this, "graphicsOutput"); graphicsOutput.setDisplayName("Graphics Output"); graphicsOutput.setTypeEquals(BaseType.BOOLEAN); graphicsOutput.setToken(BooleanToken.TRUE); displayGraphicsOutput = new Parameter(this, "displayGraphicsOutput"); displayGraphicsOutput.setDisplayName("Automatically display graphics"); displayGraphicsOutput.setTypeEquals(BaseType.BOOLEAN); displayGraphicsOutput.setToken(BooleanToken.FALSE); numXPixels = new StringParameter(this, "numXPixels"); numXPixels.setDisplayName("Number of X pixels in image"); numXPixels.setExpression("480"); numYPixels = new StringParameter(this, "numYPixels"); numYPixels.setDisplayName("Number of Y pixels in image"); numYPixels.setExpression("480"); graphicsFileName = new TypedIOPort(this, "graphicsFileName", false, true); graphicsFileName.setTypeEquals(BaseType.STRING); output = new TypedIOPort(this, "output", false, true); output.setTypeEquals(BaseType.STRING); } /** * Override the base class to set type constraints. * * @param workspace * The workspace for the new object. * @return A new instance of RExpression. * @exception CloneNotSupportedException * If a derived class contains an attribute that cannot be * cloned. */ public Object clone(Workspace workspace) throws CloneNotSupportedException { RExpression2 newObject = (RExpression2) super.clone(workspace); String lcOSName = System.getProperty("os.name").toLowerCase(); boolean MAC_OS_X = lcOSName.startsWith("mac os x"); if (MAC_OS_X) { try { newObject.graphicsFormat.setExpression("pdf"); newObject.displayGraphicsOutput.setToken(BooleanToken.TRUE); } catch (Exception w) { System.out.println("Error in special Mac response in clone"); } } newObject.output.setTypeEquals(BaseType.STRING); newObject.graphicsFileName.setTypeEquals(BaseType.STRING); return newObject; } /* * The fire method should first call the superclass. Then all the input * ports should be scanned to see which ones have tokens. The names of those * ports should be used to create a named R object (array?). R script for * creating objects corresponding to these ports should be inserted before * the script in the expressions parameter. Then the R engine should be * started and run, with the output sent to the output port. */ public synchronized void fire() throws IllegalActionException { super.fire(); // _fireUsingCommandLine(); _fireUsingJRI(); } public void initialize() throws IllegalActionException { super.initialize(); // set the home home = Rcwd.stringValue(); File homeFile = new File(home); // if not a directory, use 'home' if (!homeFile.isDirectory()) home = DotKeplerManager.getInstance().getTransientModuleDirectory("r").toString(); home = home.replace('\\', '/'); if (!home.endsWith("/")) home = home + "/"; // reset the name when workflow execution completes this.getManager().addExecutionListener(WorkflowExecutionListener.getInstance()); String workflowName = this.toplevel().getName(); // workflowName = workflowName.replace(' ','_'); // workflowName = workflowName.replace('-','_'); String execDir = home + workflowName + "_" + WorkflowExecutionListener.getInstance().getId(toplevel()) + "/"; File dir = new File(execDir); if (!dir.exists()) { dir.mkdir(); } home = execDir; } public boolean postfire() throws IllegalActionException { // _errorGobbler.quit(); // _outputGobbler.quit(); return super.postfire(); } // NOTE: there is a note about commenting out this method: // "remove for now since it causes problems with ENMs" // TODO: see if this causes problems (there are problems without it, too) public void preinitialize() throws IllegalActionException { super.preinitialize(); // set all the ports to unknown for type resolution? opList = outputPortList(); iter_o = opList.iterator(); while (iter_o.hasNext()) { TypedIOPort tiop = (TypedIOPort) iter_o.next(); if (tiop.getName().equals("output") || tiop.getName().equals("graphicsFileName")) { continue; } tiop.setTypeEquals(BaseType.GENERAL); } } //assign the array of arrays directly in RNI private void _convertArrayTokenToObject(ArrayToken token, String varName) { int arrayLength = -1; List columnRefs = new ArrayList(); for (int i = 0; i < token.length(); i++) { Token genericToken = token.getElement(i); String token_type_string = genericToken.getType().toString(); ArrayToken arrayToken = null; if (genericToken instanceof ArrayToken) { arrayToken = (ArrayToken) genericToken; } long columnRef = 0; if ((token_type_string.equals("{double}")) || (token_type_string.equals("{int}")) || (token_type_string.equals("{string}")) || (token_type_string.equals("{boolean}")) || (token_type_string.startsWith("arrayType"))) { // make primative arrays for R if ((token_type_string.equals("{double}")) || (token_type_string.startsWith("arrayType(double"))) { double[] values = _convertArrayToken(arrayToken); columnRef = re.rniPutDoubleArray(values); } else if ((token_type_string.equals("{int}")) || (token_type_string.startsWith("arrayType(int"))) { int[] values = _convertArrayTokenToInt(arrayToken); columnRef = re.rniPutIntArray(values); } else if ((token_type_string.equals("{string}")) || (token_type_string.startsWith("arrayType(string"))) { String[] values = _convertArrayTokenToString(arrayToken); columnRef = re.rniPutStringArray(values); } else if ((token_type_string.equals("{boolean}")) || (token_type_string.startsWith("arrayType(boolean"))) { boolean[] values = _convertArrayTokenToBoolean(arrayToken); columnRef = re.rniPutBoolArray(values); } } else if (token_type_string.equals("string")) { columnRef = re.rniPutStringArray(new String[] { ((StringToken) genericToken).stringValue() }); } else if (token_type_string.equals("double")) { columnRef = re.rniPutDoubleArray(new double[] { ((DoubleToken) genericToken).doubleValue() }); } else if (token_type_string.equals("int")) { columnRef = re.rniPutIntArray(new int[] { ((IntToken) genericToken).intValue() }); } else if (token_type_string.equals("boolean")) { columnRef = re.rniPutBoolArray(new boolean[] { ((BooleanToken) genericToken).booleanValue() }); } columnRefs.add(columnRef); } // while // capture the column references in a "vector" long[] columns = new long[columnRefs.size()]; for (int i = 0; i < columnRefs.size(); i++) { columns[i] = (Long) columnRefs.get(i); } // assemble the dataframe reference long tableRef = re.rniPutVector(columns); // add the column names to the dataframe // String[] columnNames = (String[]) labels.toArray(new String[0]); // long columnNamesRef = re.rniPutStringArray(columnNames); // re.rniSetAttr(tableRef, "names", columnNamesRef); // set the class as data.frame long classNameRef = re.rniPutString("data.frame"); re.rniSetAttr(tableRef, "class", classNameRef); // set assign the data.frame to a variable re.rniAssign(varName, tableRef, 0); } /** * * Given a recordToken and a portName, create the R script to make a * dataframe with the portName as its R name. * * @param recordToken * the record to convert to R dataframe * @param portName * will become the object name used for the R dataframe */ private void _recordToDataFrame(RecordToken recordToken, String portName) { int arrayLength = -1; Set labels = recordToken.labelSet(); Iterator iter_l = labels.iterator(); List columnRefs = new ArrayList(); while (iter_l.hasNext()) { String label = (String) iter_l.next(); // System.out.println("Label: "+label); Token genericToken = recordToken.get(label); String token_type_string = genericToken.getType().toString(); ArrayToken arrayToken = null; if (genericToken instanceof ArrayToken) { arrayToken = (ArrayToken) genericToken; } long columnRef = 0; if ((token_type_string.equals("{double}")) || (token_type_string.equals("{int}")) || (token_type_string.equals("{string}")) || (token_type_string.equals("{boolean}")) || (token_type_string.startsWith("arrayType"))) { // for now, assume that token is an arrayToken !!! // other token types are just ignored if (arrayLength == -1) { arrayLength = arrayToken.length(); } else { int a_len = arrayToken.length(); if (a_len != arrayLength) { log.error("record elements are not all the same length!"); return; } } // make primative arrays for R if ((token_type_string.equals("{double}")) || (token_type_string.startsWith("arrayType(double"))) { double[] values = _convertArrayToken(arrayToken); columnRef = re.rniPutDoubleArray(values); } else if ((token_type_string.equals("{int}")) || (token_type_string.startsWith("arrayType(int"))) { int[] values = _convertArrayTokenToInt(arrayToken); columnRef = re.rniPutIntArray(values); } else if ((token_type_string.equals("{string}")) || (token_type_string.startsWith("arrayType(string"))) { String[] values = _convertArrayTokenToString(arrayToken); columnRef = re.rniPutStringArray(values); } else if ((token_type_string.equals("{boolean}")) || (token_type_string.startsWith("arrayType(boolean"))) { boolean[] values = _convertArrayTokenToBoolean(arrayToken); columnRef = re.rniPutBoolArray(values); } } else if (token_type_string.equals("string")) { columnRef = re.rniPutStringArray(new String[] { ((StringToken) genericToken).stringValue() }); arrayLength = 1; } else if (token_type_string.equals("double")) { columnRef = re.rniPutDoubleArray(new double[] { ((DoubleToken) genericToken).doubleValue() }); arrayLength = 1; } else if (token_type_string.equals("int")) { columnRef = re.rniPutIntArray(new int[] { ((IntToken) genericToken).intValue() }); arrayLength = 1; } else if (token_type_string.equals("boolean")) { columnRef = re.rniPutBoolArray(new boolean[] { ((BooleanToken) genericToken).booleanValue() }); arrayLength = 1; } columnRefs.add(columnRef); } // while // capture the column references in a "vector" long[] columns = new long[columnRefs.size()]; for (int i = 0; i < columnRefs.size(); i++) { columns[i] = (Long) columnRefs.get(i); } // assemble the dataframe reference long tableRef = re.rniPutVector(columns); // add the column names to the dataframe String[] columnNames = (String[]) labels.toArray(new String[0]); long columnNamesRef = re.rniPutStringArray(columnNames); re.rniSetAttr(tableRef, "names", columnNamesRef); // set the row names (just 1,2,3...n) // this works now - and is very important! String[] rowNames = new String[arrayLength]; for (int i = 0; i < rowNames.length; i++) { rowNames[i] = "" + (i + 1); } long rowNamesRef = re.rniPutStringArray(rowNames); re.rniSetAttr(tableRef, "row.names", rowNamesRef); // set the class as data.frame long classNameRef = re.rniPutString("data.frame"); re.rniSetAttr(tableRef, "class", classNameRef); // set assign the data.frame to a variable re.rniAssign(portName, tableRef, 0); //make the character columns into factors (default DF behavior) re.eval(portName + "[sapply(" + portName + ", is.character)] <- lapply(" + portName + "[sapply(" + portName + ", is.character)], as.factor)"); } /** * The main execution of the actor as follows: Reads the input data, sets up * the graphic device (as needed), executes the script, sends output to * ports, optionally shows generated graphics, cleans up. * * @throws IllegalActionException */ private void _fireUsingJRI() throws IllegalActionException { _initializeRengine(); if (re != null) { _readInputData(); _setupJRIGraphicsDevice(); _executeRModel(); _writeOutputData(); _showGraphics(); _teardownJRI(); } else { throw new IllegalActionException(noRErrorMessage); } } /** * Start up the R system by initalizing an instance of the JRI Rengine. This * REngine can be used to execute R scripts and to retrieve the results of * these execution events. * * @return the REngine to be sued for executing R scripts * @throws IllegalActionException */ private void _initializeRengine() throws IllegalActionException { log.warn("RNI version: " + Rengine.rniGetVersion()); log.warn("API version: " + Rengine.getVersion()); if (Rengine.getMainEngine() != null) { re = Rengine.getMainEngine(); console = new RConsole(); re.addMainLoopCallbacks(console); // clear all objects for safety's sake re.eval("rm(list=ls())"); return; } if (!Rengine.versionCheck()) { String msg = "** Version mismatch - Java files don't match R library version."; log.error(msg); throw new IllegalActionException(msg); } log.debug("Creating Rengine (with arguments)"); // 1) we pass the arguments from the command line // 2) we won't use the main loop at first, we'll start it later // (that's the "false" as second argument) // 3) the callbacks are implemented by the TextConsole class above String args[] = new String[2]; args[0] = NO_SAVE; args[1] = NO_RESTORE; console = new RConsole(); re = new Rengine(args, false, console); log.debug("Rengine created, waiting for R"); // the engine creates R in a new thread, so we should wait until it's // ready if (!re.waitForR()) { String msg = "Cannot load R." + "\n " + noRErrorMessage; log.error(msg); throw new IllegalActionException(msg); } } /** * Read the input data from the actors input ports, and for each port * convert the data from a Kepler Token into an appropriate R object that * can be loaded into the REngine before R scripts that depend on this input * data can be executed. * * @param re * the REngine used for loading input data */ private void _readInputData() { log.debug("reading input form ports"); List ipList = inputPortList(); Iterator iter_i = ipList.iterator(); String RPortInfo = ""; Token at; String tokenValue; while (iter_i.hasNext()) { TypedIOPort tiop = (TypedIOPort) iter_i.next(); int multiportSize = tiop.numberOfSources(); for (int i = 0; i < multiportSize; i++) { try { if (tiop.hasToken(i)) { String portName = tiop.getName(); String finalPortName = portName; // for use with // multiports if (tiop.isMultiport()) { portName = portName + i; // temporary variable for // list item } Token token = tiop.get(i); String token_type_string = token.getType().toString(); String token_class_name = token.getType().getTokenClass().getName(); log.debug("setting: " + portName + "=" + token); // check token type and convert to R appropriately // RecordTokens if (token instanceof RecordToken) { // write it to the R environment _recordToDataFrame((RecordToken) token, portName); } // STRINGS else if (token_type_string.equals("string")) { // check for special strings that indicate dataframe // file reference at = (Token) token; tokenValue = at.toString(); tokenValue = tokenValue.substring(1, tokenValue.length() - 1); // remove quotes // DATAFRAME (old) if (tokenValue.startsWith("_dataframe_:")) { // assume that the string for a dataframe file // reference is of the form // '_dataframe_:"+<filename> tokenValue = tokenValue.substring(12); // should be filename REXP x = null; Token myToken = null; RPortInfo = "conn <- file('" + tokenValue + "', 'rb');"; x = re.eval(RPortInfo, true); myToken = _convertToToken(x, null); log.debug("myToken=" + myToken); RPortInfo = portName + " <- unserialize(conn);"; x = re.eval(RPortInfo, true); myToken = _convertToToken(x, null); log.debug("myToken=" + myToken); RPortInfo = "close(conn);"; x = re.eval(RPortInfo, true); myToken = _convertToToken(x, null); log.debug("myToken=" + myToken); continue; // stop for this token and go to the // next } // assume that the token's string value might be // 'nil' for a missing value tokenValue = tokenValue.replaceAll("nil", "NA"); if (tokenValue.startsWith("\\")) { //use this evaluation as a workaround for getting the escaped chars (like tabs) re.eval(portName + " <- '" + tokenValue + "'"); } else { // set the string in the r engine directly re.assign(portName, tokenValue); } } // BOOLEAN else if (token_type_string.equals("boolean")) { at = (Token) token; tokenValue = at.toString(); //use a boolean - JRI uses arrays... boolean booleanValue = Boolean.parseBoolean(tokenValue); re.assign(portName, new boolean[] { booleanValue }); //re.assign(portName, tokenValue); } // NUMBERS else if (token_type_string.equals("double") || token_type_string.equals("float") || token_type_string.equals("int")) { at = (Token) token; tokenValue = at.toString(); // TODO need support for assigning numeric scalars re.eval(portName + " <- " + tokenValue); // re.assign(portName, tokenValue); } // ARRAYS else if ((token_type_string.equals("{double}")) || (token_type_string.startsWith("arrayType(double"))) { double[] values = _convertArrayToken((ArrayToken) token); re.assign(portName, values); } else if ((token_type_string.equals("{int}")) || (token_type_string.startsWith("arrayType(int"))) { int[] values = _convertArrayTokenToInt((ArrayToken) token); re.assign(portName, values); } else if ((token_type_string.equals("{string}")) || (token_type_string.startsWith("arrayType(string"))) { String[] values = _convertArrayTokenToString((ArrayToken) token); re.assign(portName, values); } else if ((token_type_string.equals("{boolean}")) || (token_type_string.startsWith("arrayType(boolean"))) { boolean[] values = _convertArrayTokenToBoolean((ArrayToken) token); re.assign(portName, values); } else if ((token_type_string.equals("{scalar}")) || (token_type_string.startsWith("arrayType(scalar"))) { //TODO: keep specific types for each item Object[] values = _convertScalarArrayTokenToString((ArrayToken) token); REXP objVal = new REXP(REXP.XT_ARRAY_STR, values); re.assign(portName, objVal); } else if (token_type_string.startsWith("arrayType(arrayType")) { //handle arrays of arrays _convertArrayTokenToObject((ArrayToken) token, portName); } // MATRIX else if ((token_class_name.indexOf("IntMatrixToken") > -1) || (token_class_name.indexOf("DoubleMatrixToken") > -1) || (token_class_name.indexOf("BooleanMatrixToken") > -1)) { int rows = ((MatrixToken) token).getRowCount(); int cols = ((MatrixToken) token).getColumnCount(); ArrayToken matrixAsArray = ((MatrixToken) token).toArrayColumnMajor(); String matrixType = matrixAsArray.getType().toString(); if (matrixType.startsWith("arrayType(int")) { int[] values = _convertArrayTokenToInt((ArrayToken) matrixAsArray); re.assign(portName, values); } else if (matrixType.startsWith("arrayType(double")) { double[] values = _convertArrayToken((ArrayToken) matrixAsArray); re.assign(portName, values); } else if (matrixType.startsWith("arrayType(boolean")) { boolean[] values = _convertArrayTokenToBoolean((ArrayToken) matrixAsArray); re.assign(portName, values); } REXP x = re.eval(portName, true); log.debug(portName + "=" + x); // make a matrix from the array String cmd = portName + " <- " + "matrix(" + portName + ", nrow=" + rows + ", ncol=" + cols + ")"; re.eval(cmd, false); // REXP x = re.eval(cmd, true); // re.assign(portName, x); } // CONSTRUCT LIST for objects on multiport if (tiop.isMultiport()) { String commandList = null; if (i == 0) { // create list commandList = finalPortName + " <- list(" + portName + ")"; } else if (i > 0) { // append to list commandList = finalPortName + " <- c(" + finalPortName + ", list(" + portName + ") )"; } // set in the R environment re.eval(commandList); // remove temporary objects that are now in the list re.eval("rm(" + portName + ")"); } } } catch (NoTokenException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalActionException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // multiport loop } log.debug("Done reading data from input ports"); } private double[] _convertArrayToken(ArrayToken token) { double[] returnArray = new double[token.length()]; Token[] tokens = token.arrayValue(); for (int i = 0; i < tokens.length; i++) { double value = ((DoubleToken) tokens[i]).doubleValue(); returnArray[i] = value; } return returnArray; } private int[] _convertArrayTokenToInt(ArrayToken token) { int[] returnArray = new int[token.length()]; Token[] tokens = token.arrayValue(); for (int i = 0; i < tokens.length; i++) { int value = ((IntToken) tokens[i]).intValue(); returnArray[i] = value; } return returnArray; } private String[] _convertArrayTokenToString(ArrayToken token) { String[] returnArray = new String[token.length()]; Token[] tokens = token.arrayValue(); for (int i = 0; i < tokens.length; i++) { String value = ((StringToken) tokens[i]).stringValue(); returnArray[i] = value; } return returnArray; } private Object[] _convertScalarArrayTokenToString(ArrayToken token) { Object[] returnArray = new String[token.length()]; Token[] tokens = token.arrayValue(); for (int i = 0; i < tokens.length; i++) { Token t = tokens[i]; Object value = t.toString(); //TODO: REXP doesn't seem to support mixed arrays - needs to stay as String until fixed if (t instanceof StringToken) { value = ((StringToken) t).stringValue(); } // else if (t instanceof IntToken) { // value = ((IntToken) t).intValue(); // } else if (t instanceof DoubleToken) { // value = ((DoubleToken) t).doubleValue(); // } else if (t instanceof BooleanToken) { // value = ((BooleanToken) t).booleanValue(); // } returnArray[i] = value; } return returnArray; } private boolean[] _convertArrayTokenToBoolean(ArrayToken token) { boolean[] returnArray = new boolean[token.length()]; Token[] tokens = token.arrayValue(); for (int i = 0; i < tokens.length; i++) { boolean value = ((BooleanToken) tokens[i]).booleanValue(); returnArray[i] = value; } return returnArray; } public static int[][] asIntMatrix(double[][] doubles) { int[][] returnArray = new int[doubles.length][doubles[0].length]; for (int i = 0; i < doubles.length; i++) { for (int j = 0; j < doubles[i].length; j++) { int value = Double.valueOf(doubles[i][j]).intValue(); returnArray[i][j] = value; } } return returnArray; } public static boolean[][] asBooleanMatrix(REXP x) { int[] ct = x.asIntArray(); if (ct == null) return null; REXP dim = x.getAttribute("dim"); if (dim == null || dim.getType() != REXP.XT_ARRAY_INT) return null; // we need dimension attr int[] ds = dim.asIntArray(); if (ds == null || ds.length != 2) return null; // matrix must be 2-dimensional int m = ds[0], n = ds[1]; boolean[][] r = new boolean[m][n]; if (ct == null) return null; // R stores matrices as matrix(c(1,2,3,4),2,2) = col1:(1,2), col2:(3,4) // we need to copy everything, since we create 2d array from 1d array int i = 0, k = 0; while (i < n) { int j = 0; while (j < m) { boolean val = ct[k++] == 0 ? false : true; r[j++][i] = val; } i++; } return r; } /** * Creates a graphics device in the R environment where plots will be sent. * Actor will emit [pointer to] the graphics file that is generated. TODO: * emit actual file (object), not just a path (string) * * @throws IllegalActionException */ private void _setupJRIGraphicsDevice() throws IllegalActionException { log.debug("setting up graphics device: "); boolean graphicsOutputValue = ((BooleanToken) graphicsOutput.getToken()).booleanValue(); boolean displayGraphicsOutputValue = ((BooleanToken) displayGraphicsOutput.getToken()).booleanValue(); String graphicsFormatString = graphicsFormat.stringValue(); // following line insures that graphics is pdf if automatically // displayed if (displayGraphicsOutputValue) { graphicsFormatString = "pdf"; } // force file format to 'pdf' is this is a Mac String lcOSName = System.getProperty("os.name").toLowerCase(); boolean MAC_OS_X = lcOSName.startsWith("mac os x"); if (MAC_OS_X) { graphicsFormatString = "pdf"; } String setCWD = "setwd('" + home + "')\n"; String graphicsDeviceCode = _generateGraphicsDeviceCode(graphicsFormatString, graphicsOutputValue); if (graphicsDeviceCode != null && graphicsDeviceCode.length() > 0) { log.debug(setCWD + graphicsDeviceCode); re.eval(setCWD); re.eval(graphicsDeviceCode); } log.debug("done setting up graphics device"); } /** * Turns off the graphics device that is created */ private void _teardownJRI() { String cmd = "dev.off()"; re.eval(cmd); // re.end(); // re = null; } /** * Write tokens onto each output port of the actor if it is available from * the execution of the R Script. The type of each R Object is inspected to * determine which subclass of Token should be used for the conversion. Port * names are used to locate objects in the R environment with the same name. * * @throws IllegalActionException */ private void _writeOutputData() throws IllegalActionException { log.debug("Writing R output..."); // Loop through all output ports, looking for R Objects of the same // name, and if they exist, convert the R object to a Kepler Token // and write it on the output port opList = outputPortList(); iter_o = opList.iterator(); while (iter_o.hasNext()) { TypedIOPort tiop = (TypedIOPort) iter_o.next(); if (tiop.getName().equals("output")) { tiop.send(0, new StringToken(console.getConsoleOutput())); } else if (tiop.getName().equals("graphicsFileName")) { tiop.send(0, new StringToken(graphicsOutputFile)); } else { // handle multiport int numSinks = tiop.numberOfSinks(); int width = tiop.getWidth(); REXP rDataObject = re.eval(tiop.getName()); if (rDataObject != null) { log.debug("Symbol found for port " + tiop.getName() + ": " + rDataObject.toString()); // get the token from R Token t = _convertToToken(rDataObject, tiop.getName()); // make sure that the sinks can handle the token for (int channelIndex = 0; channelIndex < numSinks; channelIndex++) { Type sinkType = ((TypedIOPort) tiop.sinkPortList().get(channelIndex)).getType(); if (!sinkType.isCompatible(t.getType())) { log.debug("[re]Setting sink type to: " + t.getType().toString()); // set the Type for the sinks // POSSIBLE BUG - not sure why the automatic type // resolution was failing for downstream port ((TypedIOPort) tiop.sinkPortList().get(channelIndex)).setTypeEquals(t.getType()); } } // send token to each channel for (int channelIndex = 0; channelIndex < width; channelIndex++) { tiop.setTypeEquals(t.getType()); tiop.send(channelIndex, t); } } else { log.debug("No symbol found for port: " + tiop.getName()); } } } log.debug("Done writing R output."); } private void _showGraphics() throws IllegalActionException { boolean displayGraphicsOutputValue = ((BooleanToken) displayGraphicsOutput.getToken()).booleanValue(); if (displayGraphicsOutputValue) { try { File fout = new File(graphicsOutputFile); URL furl = fout.toURL(); BrowserLauncher.openURL(furl.toString()); } catch (Exception e) { log.error("Oops!:" + e); } } } private Token _serializeRDataObject(REXP rDataObject, String name) { if (name != null) { String fileName = _getUniqueFileName("sav"); re.eval("conn <- file('" + fileName + "', 'wb')"); re.eval("serialize(" + name + ", conn)"); re.eval("close(conn)"); return new StringToken("_dataframe_:" + fileName); } return null; } /** * Convert the data from the R expression type by determining the type of * the R object returned and then convert this to an appropriate Kepler * Token object that can be sent on the output port. * * @param rDataObject * the R Object that should be converted to a subclass of * ptolemy.data.Token * @throws IllegalActionException */ private Token _convertToToken(REXP rDataObject, String name) throws IllegalActionException { Token t = null; Token[] tokenArray = null; if (rDataObject != null) { int xt = rDataObject.getType(); log.debug("Type found is: " + xt); //Object rawContent = rDataObject.getContent(); //log.debug("Raw content is: " + rawContent); switch (xt) { case REXP.XT_BOOL: RBool b = rDataObject.asBool(); if (b.isFALSE()) { t = new BooleanToken(false); } else if (b.isTRUE()) { t = new BooleanToken(true); } else { // b.isNA() t = new BooleanToken("nil"); } break; case REXP.XT_DOUBLE: t = new DoubleToken(rDataObject.asDouble()); break; case REXP.XT_FACTOR: log.debug("R object is XT_FACTOR"); RFactor factor = rDataObject.asFactor(); List factorValues = new ArrayList(); for (int i = 0; i < factor.size(); i++) { StringToken stringToken = new StringToken(factor.at(i)); factorValues.add(stringToken); } t = new ArrayToken((Token[]) factorValues.toArray(new Token[0])); break; case REXP.XT_INT: t = new IntToken(rDataObject.asInt()); break; case REXP.XT_LIST: log.debug("R object is XT_LIST"); RList list = rDataObject.asList(); String[] keys = list.keys(); if (keys != null) { List values = new ArrayList(); for (int i = 0; i < keys.length; i++) { REXP col = list.at(i); // recursion! Token token = _convertToToken(col, null); values.add(token); } // put it all together in a record t = new OrderedRecordToken(keys, (Token[]) values.toArray(new Token[0])); } break; case REXP.XT_NULL: t = Token.NIL; break; case REXP.XT_STR: t = new StringToken(rDataObject.asString()); break; case REXP.XT_SYM: log.debug("R object is XT_SYM"); break; case REXP.XT_UNKNOWN: log.debug("R object is XT_UNKNOWN"); break; case REXP.XT_VECTOR: log.debug("I am a XT_VECTOR!"); RVector vector = rDataObject.asVector(); // handle data.frame/Record structure List names = vector.getNames(); if (names != null) { // preserve _every_ aspect of the data object if (((BooleanToken) serializeData.getToken()).booleanValue()) { t = _serializeRDataObject(rDataObject, name); } else { List values = new ArrayList(); for (int i = 0; i < names.size(); i++) { String columnName = (String) names.get(i); REXP col = vector.at(columnName); // recursion! Token token = _convertToToken(col, null); values.add(token); } // put it all together in a record String[] namesArray = (String[]) names.toArray(new String[0]); Token[] valuesArray = (Token[]) values.toArray(new Token[0]); t = new OrderedRecordToken(namesArray, valuesArray); } } else { // handle a List List values = new ArrayList(); for (int i = 0; i < vector.size(); i++) { REXP value = vector.at(i); // recursion! Token token = _convertToToken(value, null); values.add(token); } // put it all together in an array if (values.isEmpty()) { t = new ArrayToken(Token.NIL.getType()); } else { t = new ArrayToken((Token[]) values.toArray(new Token[0])); } } break; case REXP.XT_ARRAY_BOOL: int[] xb = rDataObject.asIntArray(); tokenArray = new Token[xb.length]; for (int i = 0; i < xb.length; i++) { String val = xb[i] == 0 ? "false" : (xb[i] == 1 ? "true" : "nil"); BooleanToken bt = new BooleanToken(val); tokenArray[i] = bt; } t = new ArrayToken(tokenArray); break; case REXP.XT_ARRAY_BOOL_INT: // try matrix first boolean[][] bMatrix = asBooleanMatrix(rDataObject); if (bMatrix != null) { t = new BooleanMatrixToken(bMatrix); break; } int[] xbi = rDataObject.asIntArray(); tokenArray = new Token[xbi.length]; for (int i = 0; i < xbi.length; i++) { String val = xbi[i] == 0 ? "false" : (xbi[i] == 1 ? "true" : "nil"); BooleanToken bt = new BooleanToken(val); tokenArray[i] = bt; } t = new ArrayToken(tokenArray); break; case REXP.XT_ARRAY_DOUBLE: // try as matrix first double[][] matrix = rDataObject.asDoubleMatrix(); if (matrix != null) { t = new DoubleMatrixToken(matrix); break; } // otherwise it is a simple list double[] xd = rDataObject.asDoubleArray(); tokenArray = new Token[xd.length]; for (int i = 0; i < xd.length; i++) { DoubleToken dt = new DoubleToken(xd[i]); tokenArray[i] = dt; } t = new ArrayToken(tokenArray); break; case REXP.XT_ARRAY_INT: // try as matrix first double[][] matrixD = rDataObject.asDoubleMatrix(); if (matrixD != null) { t = new IntMatrixToken(asIntMatrix(matrixD)); break; } int[] xi = rDataObject.asIntArray(); tokenArray = new Token[xi.length]; for (int i = 0; i < xi.length; i++) { IntToken dt = new IntToken(xi[i]); tokenArray[i] = dt; } t = new ArrayToken(tokenArray); break; case REXP.XT_ARRAY_STR: String[] xs = rDataObject.asStringArray(); tokenArray = new Token[xs.length]; for (int i = 0; i < xs.length; i++) { StringToken st = new StringToken(xs[i]); tokenArray[i] = st; } t = new ArrayToken(tokenArray); break; } } //return a single token in cases when it's believed to be a list (JRI added this) //TODO: parameterize the switch boolean asArrayToken = true; if (!asArrayToken) { if (t instanceof ArrayToken) { if (((ArrayToken) t).length() == 1) { t = ((ArrayToken) t).getElement(0); } } } return t; } private String _generateGraphicsDeviceCode(String graphicsFormatString, boolean graphicsOutputValue) { String nxs = ""; String nys = ""; try { nxs = numXPixels.stringValue(); try { int nxp = (new Integer(nxs)).intValue(); } catch (Exception w) { nxs = "480"; } } catch (IllegalActionException iae) { } try { nys = numYPixels.stringValue(); try { int nyp = (new Integer(nys)).intValue(); } catch (Exception w1) { nys = "480"; } } catch (IllegalActionException iae) { } graphicsOutputFile = _getUniqueFileName(graphicsFormatString); String graphicsDevice = ""; if (graphicsOutputValue) { int nxp = (new Integer(nxs)).intValue(); double nxd = nxp / 72.0; int nyp = (new Integer(nys)).intValue(); double nyd = nyp / 72.0; if (graphicsFormatString.equals("pdf")) { graphicsDevice = "pdf(file = '" + graphicsOutputFile + "'" + ",width = " + nxd + ", height = " + nyd + ")"; } else { graphicsDevice = "png(filename = '" + graphicsOutputFile + "'" + ",width = " + nxs + ", height = " + nys + ", pointsize = 12, bg = 'white')"; } } return graphicsDevice; } private String _getUniqueFileName(String extender) { int cnt = 1; // String usr_name = System.getProperty("user.name"); String actor_name = this.getName(); actor_name = actor_name.replace(' ', '_'); String fn = actor_name + cnt + "." + extender; String path = home; while (new File(path, fn).exists()) { cnt++; fn = actor_name + cnt + "." + extender; } return new File(path, fn).getAbsolutePath(); } // ///////////////////////////////////////////////////////////////// // // private variables //// /** * Execute the R system and run the model found in the expression provided * in the RExpression "expression" attribute. When complete, the REngine can * be queried to obtain an REXP object with the results of the execution. * @throws IllegalActionException * */ private void _executeRModel() throws IllegalActionException { log.debug("Begin R script execution."); // get the lines of the script String script = expression.getExpression(); // file for the source String tempFile = null; try { tempFile = _getUniqueFileName("r"); // write to file FileWriter fw = new FileWriter(tempFile); fw.write(script); fw.close(); } catch (IOException e1) { log.error("could not create temp R source file"); throw new IllegalActionException(e1.getMessage()); } // simple! String line = "source('" + tempFile + "')"; // write the expression to the logging console console.rWriteConsole(re, "> " + line + "\n", REXP.XT_STR); boolean showEval = ((BooleanToken) showDebug.getToken()).booleanValue(); // evaluate the expression REXP x = re.eval(line, showEval); // show result if configured to do so if (showEval) { try { Token token = _convertToToken(x, null); console.rWriteConsole(re, "Result: " + token + "\n", x.rtype); } catch (Exception e) { log.debug("error showing debug results from R engine: " + e.getMessage()); } } log.debug(x); log.debug("Finished R execution."); } private String graphicsOutputFile = ""; private List opList; private Iterator iter_o; private String home; }