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

org.nuiton.j2r.jni.RJniEngine 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
 * . ##%*/
package org.nuiton.j2r.jni;

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.nuiton.j2r.types.RList;
import org.rosuda.JRI.REXP;
import org.rosuda.JRI.Rengine;

/**
 * RJniEngine.java
 * 
 * Created: 22 aout 2006
 * 
 * @author Arnaud Thimel 
 */
public class RJniEngine extends REngineAbstract implements REngine {

    private Log log = LogFactory.getLog(RJniEngine.class);
    /**
     * Rengine is made to be static
     */
    private static Rengine engine;
    /**
     * 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 R engine.
     *
     * @return true if initialized, false otherwise.
     * 
     * @see org.nuiton.j2r.REngine#init()
     */
    @Override
    public boolean init() {
        if (engine == null) {
            try {
                String[] args = {"--no-save"};

                //Set the property so that rJava does not make a System.exit(1)
                //System.setProperty("jri.ignore.ule", "yes");

                //jriLoaded is false is rJava did not find jri library
                if (!Rengine.jriLoaded) {
                    if (log.isErrorEnabled()) {
                        log.error(
                            "Cannot find jri library, make sure it is correctly installed");
                    }
                    return false;
                }

                engine = new Rengine(args, false, null);
                
                if (!engine.waitForR()) {
                    if (log.isErrorEnabled()) {
                        log.error("Cannot load the R engine");
                    }
                    return false;
                }
            } catch (Exception eee) {
                log.error("An error occured during R/JNI initialization.",
                    eee);
                return false;
            }
        }
        return true;
    }

    /**
     * Evaluate a R expression in R and get back the result.
     *
     * @param expr the R expression to evaluate.
     *
     * @return the result of the R expression.
     * 
     * @see org.nuiton.j2r.REngine#eval(java.lang.String)
     */
    @Override
    public Object eval(String expr) throws RException {
        REXP result = null;
        if (log.isDebugEnabled()) {
            log.debug(String.format(RInstructions.RTRY, expr));
        }
        //encapsulate the R expression in a try method/object to get the R error
        //message if thrown
        result = engine.eval(String.format(RInstructions.RTRY, expr));
        if (result.getAttribute(RInstructions.ATTRIBUTE_CLASS) != null) {
            //if the "class" attribute of the R expression is "try-error"
            //throw a new exception with the error message from R.
            String klass =
                result.getAttribute(RInstructions.ATTRIBUTE_CLASS).asString();
            if (klass.equals(RInstructions.CLASS_ERROR)) {
                throw new RException(result.asString());
            }
        }
        return convertResult(result);
    }

    /**
     * Convert the result from an R expression to a java object.
     *
     * @param rexp the R expression to convert.
     *
     * @return the java object corresponding to the R expression.
     */
    private Object convertResult(REXP rexp) {
        if (rexp == null) {
            log.debug("Null returned");
            return null;
        }
        if (log.isDebugEnabled()) {
            log.debug("Converting : " + rexp.toString());
        }
        int type = rexp.getType();
        Object result = null;
        switch (type) {
            case REXP.XT_STR:
                //If string return the r expression as string
                result = rexp.asString();
                break;
            case REXP.XT_INT:
                //if integer, return the rexp as integer
                result = (Integer) rexp.asInt();
                break;
            case REXP.XT_ARRAY_INT:
                int[] array = rexp.asIntArray();
                Integer[] bigArray = new Integer[array.length];
                for (int i = 0; i < array.length; i++) {
                    bigArray[i] = (Integer) array[i];
                }
                result = bigArray;
                //Check if only one integer, return an integer.
                if (array.length == 1) {
                    result = (Integer) array[0];
                }
                break;
            case REXP.XT_ARRAY_DOUBLE:
                //if double array, return the rexp as double array.
                double[] doublearray = rexp.asDoubleArray();
                Double[] bigdoublearray = new Double[doublearray.length];
                for (int i = 0; i < doublearray.length; i++) {
                    bigdoublearray[i] = (Double) doublearray[i];
                }
                result = bigdoublearray;
                //Check if only one double, return a double.
                if (doublearray.length == 1) {
                    result = doublearray[0];
                }
                break;
            case REXP.XT_BOOL:
                //if boolean, return rexp as boolean
                result = rexp.asBool().isTRUE();
                break;
            case REXP.XT_DOUBLE:
                //if double, return rexp as double
                //Get a double array
                result = rexp.asDoubleArray();
                //return only the first element.
                result = (Double) ((double[]) result)[0];
                break;
            case REXP.XT_NULL:
                //if null return null
                result = null;
                break;
            case REXP.XT_ARRAY_BOOL_INT:
                //if boolean array, get the rexp as integer array (full of 0 and 1)
                result = rexp.asIntArray();
                int[] integers = ((int[]) result);
                Boolean[] booleanArray = new Boolean[integers.length];
                //transform the 0 and 1 in true and false in a boolean array
                for (int i = 0; i < integers.length; i++) {
                    if (integers[i] == 1) {
                        booleanArray[i] = Boolean.TRUE;
                    } else {
                        booleanArray[i] = Boolean.FALSE;
                    }
                }
                //check if there is only a boolean, return a boolean
                if (booleanArray.length == 1) {
                    result = booleanArray[0];
                } else {
                    result = booleanArray;
                }
                //return the boolean array
                break;
            case REXP.XT_ARRAY_STR:
                //if is a string array, return as a string array.
                result = rexp.asStringArray();
                break;
            case REXP.XT_VECTOR:
                //dataframes, lists and vectors are recognized as vectors.
                //get the class of the vector (to successfully detect data.frames)
                String klass = "";
                REXP klassAttribute = rexp.getAttribute(
                    RInstructions.ATTRIBUTE_CLASS);
                if (klassAttribute != null) {
                    klass = klassAttribute.asString();
                }
                //get REXP asList to successfully detect lists.
                org.rosuda.JRI.RList list = rexp.asList();
                if (klass.equals(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.JRI.RList dataList = rexp.asList();
                    for (int i = 0; i < dataList.keys().length; 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).asStringArray(),
                        rexp.getAttribute(RInstructions.ATTRIBUTE_ROWNAMES).asStringArray(),
                        data, "");
                    result = temp;
                } else if (list != null) {
                    RList temp = new RList((REngine) this);
                    List data = new ArrayList();
                    org.rosuda.JRI.RList dataList = rexp.asList();
                    for (int i = 0; i < dataList.keys().length; i++) {
                        //for each object of the list, convert it to java.
                        REXP tempREXP = dataList.at(i);
                        Object convertedREXP = convertResult(
                            tempREXP);
                        //add this object to the data list.
                        data.add(convertedREXP);

                    }
                    //Create a new list with the names and data
                    //gotten from rexp. It has no variable name so throws a
                    //RException.
                    try {
                        temp = new RList(
                            rexp.getAttribute(RInstructions.ATTRIBUTE_NAMES).asStringArray(),
                            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 {
                    rexp.asVector();
                }

                break;
            default:
                //if don't know the type, throw an exception.
                log.error("Unknown return type [" + type + "] " + "on : " +
                    rexp.toString());
                break;
        }
        return result;
    }

    /**
     * Terminate the R connection.
     * 
     * @see org.nuiton.j2r.REngine#terminate()
     */
    @Override
    public void terminate() {
        if (engine.isAlive()) {
            engine.end();
        }
    }

    /**
     * Evaluate a R expression in R without getting back the result. If in
     * non-autocommit mode, expression is stored and evaluated only when the
     * commit() method is called.
     *
     * @param expr the R expression to evaluate.
     * 
     * @see org.nuiton.j2r.REngine#voidEval(java.lang.String)
     * @see org.nuiton.j2r.jni.RJniEngine#commit() 
     */
    @Override
    public void voidEval(String expr) throws RException {
        // voidEval is not really supported by JRI, we just discard the result
        // conversion

        //if in autocommit mode, add the instruction to the list.
        if (!autocommit) {
            rInstructions.add(expr);
        } else {
            //encapsulate the R expression in a try method/object to get the R error
            //message if thrown
            if (log.isDebugEnabled()) {
                log.debug(String.format(RInstructions.RTRY, expr));
            }
            REXP r = engine.eval(String.format(RInstructions.RTRY, expr));
            if ((null != r) && (null != r.getAttribute(RInstructions.ATTRIBUTE_CLASS))) {
                //if the "class" attribute of the R expression is "try-error"
                //throw a new exception with the error message from R.
                String classe = r.getAttribute(RInstructions.ATTRIBUTE_CLASS).asString();
                if (classe.equals(RInstructions.CLASS_ERROR)) {
                    throw new RException(r.asString());
                }
            }
        }
    }

    /**
     * Method to commit all the R instructions stored when not in autocommit
     * mode.
     *
     * @throws org.nuiton.j2r.RException if an error occur while evaluating the
     * instructions in R.
     *
     * @see org.nuiton.j2r.REngine#commit() 
     */
    @Override
    public void commit() throws RException {
        for (int i = 0; i < rInstructions.size(); i++) {
            //Send one by one, all the Rinstructions stored, beginning by
            //the first one stored
            String expr = rInstructions.get(i);
            engine.eval(expr);
        }
        //Clean the list of R instructions
        rInstructions = new LinkedList();
    }
} // RJniEngine