All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.j2r.net.RNetEngine Maven / Gradle / Ivy

/* *##% Nuiton Java-2-R library
 * Copyright (C) 2006 - 2009 CodeLutin
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * . ##%*/

/* *
 * RNetEngine.java
 *
 * Created: 22 aout 06
 *
 * @author Arnaud Thimel 
 */
package org.nuiton.j2r.net;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.j2r.REngine;
import org.nuiton.j2r.REngineAbstract;
import org.nuiton.j2r.RException;
import org.nuiton.j2r.RInstructions;
import org.nuiton.j2r.types.RDataFrame;
import org.rosuda.REngine.REXP;
import org.rosuda.REngine.REXPMismatchException;
import org.nuiton.j2r.types.RList;
import org.rosuda.REngine.Rserve.RConnection;
import org.rosuda.REngine.Rserve.RserveException;

/**
 * This class represents the network engine to access R. By default, it tries to
 * connect on address 127.0.0.1 and port 6311. Meanwhile, it is possible to
 * parametrize it.
 *
 * You only need to replace the launch option -DR.type=net by
 * -DR.type=net://192.168.99.122:6312 where 192.168.99.122 is the
 * distant machine adress and 6312 the port on which the server is running.
 */
public class RNetEngine extends REngineAbstract implements REngine {

    public static final int DEFAULT_PORT = 6311;
    public static final String DEFAULT_HOST = "127.0.0.1";
    private Log log = LogFactory.getLog(RNetEngine.class);
    private RConnection conn;
    /**
     * If true, commit each R instruction on the fly, if false, commit only when
     * the commit() method is called.
     */
    private Boolean autocommit = true;
    /**
     * List used to store all the R instructions when not in autocommit mode
     * (when autocommit == false).
     */
    private List rInstructions = new LinkedList();

    /**
     * Initialize the engine.
     * 
     * @see org.nuiton.j2r.REngine#init()
     */
    @Override
    public boolean init() {
        String typeProp = System.getProperty("R.type", "net");
        int urlPos = typeProp.indexOf("net://");
        String host = null;
        String portAsString = null;
        if (urlPos != -1) {
            String url = typeProp.substring(urlPos + 6);
            int commaPos = url.indexOf(':');
            if (commaPos != -1) {
                host = url.substring(0, commaPos);
                portAsString = url.substring(commaPos + 1);
            } else {
                host = url;
            }
        }
        if (host == null || "".equals(host)) {
            host = DEFAULT_HOST;
        }
        int port = DEFAULT_PORT;
        if (portAsString != null) {
            try {
                port = Integer.parseInt(portAsString);
            } catch (NumberFormatException nfe) {
                if (log.isWarnEnabled()) {
                    log.warn("Bad port format " + portAsString +
                            ", using default" + " port : " + port);
                }
            }
        }
        return init(host, port);
    }

    /**
     * Method to initialize the connection with specified host and port.
     *
     * @param host Adress of the Rserve instance
     * @param port Port on which the Rserve instance is listening.
     *
     * @return true if initialized false otherwise.
     */
    public boolean init(String host, int port) {
        if (log.isInfoEnabled()) {
            log.info("Trying to connect to the Rserve on '" + host + ":" + port +
                    "'");
        }
        try {
            conn = new RConnection(host, port);
        } catch (RserveException eee) {
            log.error("Unable to establish a connection to the R server. " +
                    "Maybe you forgot to start it. " +
                    "Try using the command \"R CMD Rserve\".", eee);
            return false;
        }
        return conn.isConnected();
    }

    /**
     * Method to send a R instruction to the engine and get back the result.
     *
     * @param expr the R expression to evaluate.
     *
     * @return the result of the R expression.
     *
     * @throws org.nuiton.j2r.RException if an error occur while evaluating the
     * R expression.
     * 
     * @see org.nuiton.j2r.REngine#eval(java.lang.String)
     */
    @Override
    public Object eval(String expr) throws RException {
        REXP result = null;
        try {
            //Encapsulate the R expression to get back the R error message
            //if thrown.
            if (log.isDebugEnabled()) {
                log.debug(String.format(RInstructions.RTRY, expr));
            }
            result = conn.eval(String.format(RInstructions.RTRY, expr));
            if (result.inherits(RInstructions.CLASS_ERROR)) {
                //If the R expression is an error, throw an expression with the
                //real R error message
                throw new RException(result.asString());
            }
        } catch (RserveException rse) {
            //If a communication error occur.
            throw new RException(
                    "An error occured while eval on net",
                    rse);
        } catch (REXPMismatchException rme) {
            //If the error message cannot be read.
            throw new RException("Cannot read error message from R :", rme);
        }
        return convertResult(result);
    }

    /**
     * Method to convert a R expression to Java.
     *
     * @param rexp the R expression to convert.
     * 
     * @return the resulting Java object.
     */
    private Object convertResult(REXP rexp) {
        if (rexp == null) {
            //if rexp is null, avoid the tests on its type.
            if (log.isDebugEnabled()) {
                log.debug("Null returned");
            }
            return null;
        }
        if (log.isDebugEnabled()) {
            log.debug("Converting : " + rexp.toString());
        }

        Object result = null;

        try {

            if (rexp.isInteger()) {
                //true if rexp is an integer or an integer array.
                //get rexp as an integer array
                int[] array = rexp.asIntegers();
                Integer[] bigArray = new Integer[array.length];
                for (int i = 0; i < array.length; i++) {
                    bigArray[i] = (Integer) array[i];
                }
                result = bigArray;
                //if the size of the array is 1, then return the integer alone.
                //else return the integer array.
                if (array.length == 1) {
                    result = (Integer) array[0];
                }
            } else if (rexp.isFactor()) {
                //if rexp is a factor, return it as a RFactor.
                result = rexp.asFactor();
            } else if (rexp.isNumeric()) {
                //true if rexp is a double or a double array.
                //get rexp as a double array
                double[] doublearray = rexp.asDoubles();
                Double[] bigDoubleArray = new Double[doublearray.length];
                for (int i = 0; i < doublearray.length; i++) {
                    bigDoubleArray[i] = (Double) doublearray[i];
                }
                result = bigDoubleArray;

                //if the size of the array is 1, then return the double alone.
                //else return the double array.
                if (doublearray.length == 1) {
                    result = (Double) doublearray[0];
                }
            } else if (rexp.isString()) {
                //true if rexp is a string or a string array.
                //get rexp as a string array
                result = rexp.asStrings();
                String[] stringArray = (String[]) result;
                //if the size of the array is 1, then return the string alone.
                //else return the string array.
                if (stringArray.length == 1) {
                    result = stringArray[0];
                }

            } else if (rexp.isLogical()) {
                //true if rexp is a boolean or a boolean array.
                //get rexp as a string array (there is no method for booleans)
                result = rexp.asStrings();
                String[] strings = ((String[]) result);
                //create a boolean array of the same length
                Boolean[] booleanArray = new Boolean[strings.length];
                //parse each string into boolean and out it into the array.
                for (int i = 0; i < ((String[]) result).length; i++) {
                    booleanArray[i] = Boolean.parseBoolean(strings[i]);
                }
                if (booleanArray.length == 1) {
                    result = booleanArray[0];
                } else {
                    result = booleanArray;
                }
            } else if (rexp.isNull()) {
                //if rexp contains a null R expression
                return null;
            } else if (rexp.inherits(RInstructions.CLASS_DATAFRAME)) {
                //if rexp is a data.frame
                RDataFrame temp = new RDataFrame((REngine) this);

                //create the data list.
                List> data =
                        new ArrayList>();
                //get rexp as a list (data.frame is a list of vectors)
                org.rosuda.REngine.RList dataList = rexp.asList();
                for (int i = 0; i < dataList.size(); i++) {
                    //for each vector, create a list and fill it with the
                    //content of the vector.
                    List templist = new ArrayList();
                    REXP tempREXP = dataList.at(i);
                    Object[] convertedREXP = (Object[]) convertResult(tempREXP);
                    templist = Arrays.asList(convertedREXP);
                    //add this list to the data list.
                    data.add(templist);

                }
                //Create a new dataframe with the names, row.names and data
                //gotten from rexp. It has no variable name so throws a
                //RException.
                temp = new RDataFrame((REngine) this, rexp.getAttribute(
                        RInstructions.ATTRIBUTE_NAMES).asStrings(),
                        rexp.getAttribute(RInstructions.ATTRIBUTE_ROWNAMES).asStrings(),
                        data, "");
                result = temp;
            } else if (rexp.isList()) {
                RList temp = new RList((REngine) this);
                List data = new ArrayList();
                org.rosuda.REngine.RList dataList = rexp.asList();
                for (int i = 0; i < dataList.size(); i++) {
                    //for each vector, create a list and fill it with the
                    //content of the vector.
                    REXP tempREXP = dataList.at(i);
                    Object convertedREXP = convertResult(tempREXP);
                    //add this list to the data list.
                    data.add(convertedREXP);

                }
                //Create a new dataframe with the names, row.names and data
                //gotten from rexp. It has no variable name so throws a
                //RException.
                try {
                    temp = new RList(rexp.getAttribute(
                            RInstructions.ATTRIBUTE_NAMES).asStrings(),
                            data, (REngine) this, "");
                } catch (RException re) {
                    //don't propagate the error as it is normal. Log it for debug.
                    if (log.isDebugEnabled()) {
                        log.debug(
                                "Converting REXP to RList. Creating list without variable name");
                    }
                }
                result = temp;
            } else {
                //if the type is not supported.
                log.error("Unknown return type on : " + rexp.toString());
            }
        } catch (REXPMismatchException eee) {
            log.error("An error occurred while getting the expression from R.",
                    eee);
        }
        return result;
    }

    /**
     * Terminate the connection with the server.
     *
     * @throws org.nuiton.j2r.RException
     *
     * @see org.nuiton.j2r.REngine#terminate()
     */
    @Override
    public void terminate() throws RException {
        //close the connection with the server.
        if (conn != null && conn.isConnected()) {
            conn.close();
        }
    }

    /**
     * Method to send a R instruction to the engine without getting back the
     * result. If not into autocommit mode, instructions are stored and sent to
     * R after a call of the commit method.
     *
     * @param expr The R expression to evaluate.
     *
     * @throws org.nuiton.j2r.RException
     * 
     * @see org.nuiton.j2r.REngine#voidEval(java.lang.String)
     * @see org.nuiton.j2r.net.RNetEngine#commit() 
     */
    @Override
    public void voidEval(String expr) throws RException {
        if (!autocommit) {
            //if not in autocommit mode, add the instruction to the list.
            rInstructions.add(expr);
        } else {
            try {
                if (log.isDebugEnabled()) {
                    log.debug(String.format(RInstructions.RTRY, expr));
                }
                //Encapsulate the R expression to get back the R error message
                //if thrown.
                REXP r = conn.eval(String.format(RInstructions.RTRY, expr));
                if ((null!=r) && (r.inherits(RInstructions.CLASS_ERROR))) {
                    //If the R expression is an error, throw an expression with the
                    //real R error message
                    throw new RException(r.asString());
                }
            } catch (RserveException rse) {
                //If a communication error occur.
                throw new RException(
                        "An error occured while eval on net",
                        rse);
            } catch (REXPMismatchException rme) {
                //If the error message cannot be read.
                throw new RException("Cannot read error message from R :", rme);
            }
        }
    }

    /**
     * Method to commit all the R expressions stored while in non-autocommit
     * mode
     *
     * @throws org.nuiton.j2r.RException if an error occur while evaluating one
     * expression.
     */
    @Override
    public void commit() throws RException {
        for (int i = 0; i < rInstructions.size(); i++) {
            //run all methods stored in the list.
            String expr = rInstructions.get(i);
            voidEval(expr);
        }
        //clear the list.
        rInstructions = new LinkedList();
    }
} // RNetEngine