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

org.nuiton.j2r.jni.RJniEngine Maven / Gradle / Ivy

/*
 * #%L
 * Nuiton Java-2-R
 * %%
 * Copyright (C) 2006 - 2013 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.j2r.jni;

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;

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

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

    private static 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 (!Boolean.getBoolean("jri.load.classpath.libs")) {
                        if (log.isDebugEnabled()) {
                            log.debug(
                                "no property jri.load.classpath.libs defined, skip native lib loading");
                        }
                        return false;
                    }
                    if (!RNative.loadNativeRLibraryFromClasspath()) {
                        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. " +
                        "Maybe you forgot to configure your environment");
                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;
        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);
    }

    /**
     * Evaluate a R multi-line 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 evalScript(String expr) throws RException {
        return eval("{" + expr + "}\n");
    }

    /**
     * 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.
     */
    protected 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 = rexp.asInt();
                break;
            case REXP.XT_ARRAY_INT:
                int[] array = rexp.asIntArray();
                result = array;
                //Check if only one integer, return an integer.
                if (array.length == 1) {
                    result = array[0];
                }
                break;
            case REXP.XT_ARRAY_DOUBLE:
                //if double array, return the rexp as double array.
                double[] doubleArray = rexp.asDoubleArray();
                result = doubleArray;
                //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[]) 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)) {
                    result = convertToDataFrame(rexp);
                } else if (list != null) {
                    result = convertToRList(rexp);
                } 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;
    }

    protected RDataFrame convertToDataFrame(REXP rexp){

        org.rosuda.JRI.RList list = rexp.asList();

        RDataFrame temp;

        //create the data list.
        List> data = new ArrayList>();
        //get rexp as a list (data.frame is a list of vectors)
        for (int i = 0; i < list.keys().length; i++) {
            //for each vector, create a list and fill it with the
            //content of the vector.
            List templist = new ArrayList();
            REXP tempREXP = list.at(i);
            Object convertedREXP = convertResult(tempREXP);
            if (convertedREXP instanceof int[]){
                for(int value:(int[])convertedREXP){
                    templist.add(value);
                }
            }
            if (convertedREXP instanceof double[]){
                for(double value:(double[])convertedREXP){
                    templist.add(value);
                }
            }
            if (convertedREXP instanceof String[]){
                templist.addAll(Arrays.asList((String[])convertedREXP));
            }
            if (convertedREXP instanceof Boolean[]){
                templist.addAll(Arrays.asList((Boolean[])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(this, rexp.getAttribute(
                RInstructions.ATTRIBUTE_NAMES).asStringArray(),
                rexp.getAttribute(RInstructions.ATTRIBUTE_ROWNAMES).asStringArray(),
                data, "");
        return temp;
    }

    protected RList convertToRList(REXP rexp){
        RList temp = new RList(this);
        List data = new ArrayList();

        //get the names of the list objects
        REXP attrNames = rexp.getAttribute(RInstructions.ATTRIBUTE_NAMES);
        String[] names = {};
        if (attrNames != null) {
            names = attrNames.asStringArray();
        }

        org.rosuda.JRI.RList dataList = rexp.asList();

        //Use this because do not have a size() or length() method, cannot use
        // the keys size (elements might not have keys) and RList do not throw
        // out of bound exception if going to far.
        Boolean flag = true;
        int index = 0;

        while (flag) {
            //for each object of the list, convert it to java.
            REXP tempREXP = dataList.at(index);

            if (tempREXP == null) {
                flag = false;
            } else {
                Object convertedREXP = convertResult(tempREXP);
                //add this object to the data list.
                data.add(convertedREXP);
                index++;
            }
        }
        //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(names, data, 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");
            }
        }

        return temp;
    }

    /**
     * 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 (String expr:rInstructions) {
            //Send one by one, all the Rinstructions stored, beginning by
            //the first one stored
            engine.eval(expr);
        }
        //Clean the list of R instructions
        rInstructions = new LinkedList();
    }
} // RJniEngine