ubic.basecode.util.r.RServeClient.java Source code

Java tutorial

Introduction

Here is the source code for ubic.basecode.util.r.RServeClient.java

Source

/*
 * The baseCode project
 * 
 * Copyright (c) 2006 University of British Columbia
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package ubic.basecode.util.r;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.io.FileHandler;
import org.apache.commons.lang3.StringUtils;
import org.rosuda.REngine.REXP;
import org.rosuda.REngine.REXPMismatchException;
import org.rosuda.REngine.REngineException;
import org.rosuda.REngine.RList;
import org.rosuda.REngine.Rserve.RConnection;
import org.rosuda.REngine.Rserve.RserveException;

import ubic.basecode.dataStructure.matrix.DenseDoubleMatrix;
import ubic.basecode.dataStructure.matrix.DoubleMatrix;
import ubic.basecode.util.ConfigUtils;

/**
 * @author pavlidis
 * 
 */
public class RServeClient extends AbstractRClient {

    /**
     * 
     */
    private static final int DEFAULT_PORT = 6311;

    private static final int MAX_CONNECT_TRIES = 10;

    private static final int MAX_EVAL_TRIES = 3;

    private final static String os = System.getProperty("os.name").toLowerCase();

    /**
     * @return
     * @throws ConfigurationException
     */
    protected static String findRserveCommand() throws ConfigurationException {
        URL userSpecificConfigFileLocation = ConfigUtils.locate("local.properties");

        PropertiesConfiguration userConfig = null;
        if (userSpecificConfigFileLocation != null) {
            userConfig = new PropertiesConfiguration();
            FileHandler handler = new FileHandler(userConfig);
            handler.setFileName("local.properties");
            handler.load();
        }
        String rserveExecutable = null;
        if (userConfig != null) {
            rserveExecutable = userConfig.getString("rserve.start.command");
        }
        if (StringUtils.isBlank(rserveExecutable)) {
            log.info("Rserve command not configured? Trying fallbacks");
            if (os.startsWith("windows")) { // lower cased
                rserveExecutable = System.getenv("R_HOME") + File.separator + "library" + File.separator + "Rserve"
                        + File.separator + "Rserve.exe";
            } else {
                rserveExecutable = "R CMD Rserve";
            }
        }
        return rserveExecutable;
    }

    private RConnection connection = null;

    /**
     * Gets connection on default host (localhost) and port (6311)
     * 
     * @throws IOException
     */
    protected RServeClient() throws IOException {
        if (!connect()) {
            throw new IOException("Could not connect to Rserve");
        }
    }

    /**
     * @param host
     * @throws IOException
     */
    protected RServeClient(String host) throws IOException {
        if (!connect(host, DEFAULT_PORT)) {
            throw new IOException("Could not connect to Rserve");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#assign(java.lang.String, double[])
     */
    @Override
    public void assign(String argName, double[] arg) {
        checkConnection();

        try {
            connection.assign(argName, arg);
        } catch (REngineException e) {
            throw new RuntimeException(e);
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#assign(java.lang.String, int[])
     */
    @Override
    public void assign(String arg0, int[] arg1) {
        if (StringUtils.isBlank(arg0)) {
            throw new IllegalArgumentException("Must supply valid variable name");
        }
        checkConnection();
        try {
            connection.assign(arg0, arg1);
        } catch (REngineException e) {
            throw new RuntimeException(e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.rosuda.JRclient.Rconnection#assign(java.lang.String, java.lang.String)
     */
    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#assign(java.lang.String, java.lang.String)
     */
    @Override
    public void assign(String sym, String ct) {
        if (StringUtils.isBlank(sym)) {
            throw new IllegalArgumentException("Must supply valid variable name");
        }
        try {
            this.checkConnection();
            connection.assign(sym, ct);
        } catch (RserveException e) {
            throw new RuntimeException("Assignment failed: " + sym + " value " + ct, e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#assign(java.lang.String, java.lang.String[])
     */
    @Override
    public void assign(String argName, String[] array) {
        if (array == null || array.length == 0) {
            throw new IllegalArgumentException("Array must not be null or empty");
        }
        if (StringUtils.isBlank(argName)) {
            throw new IllegalArgumentException("Must supply valid variable name");
        }
        try {
            log.debug("assign: " + argName + "<-" + array.length + " strings.");
            this.checkConnection();
            connection.assign(argName, array);
        } catch (REngineException e) {
            throw new RuntimeException(
                    "Failure with assignment: " + argName + "<-" + array.length + " strings." + e);
        }
    }

    /**
     * 
     *
     */
    public boolean connect() {
        return connect(true);
    }

    @Override
    public void disconnect() {
        if (connection != null && connection.isConnected())
            connection.close();
        connection = null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.r.RClient#eval(java.lang.String)
     */
    @Override
    public REXP eval(String command) {
        log.debug("eval: " + command);

        int lockValue = 0;
        try {

            /*
             * Failures due to communication? try repeatedly.
             */
            for (int i = 0; i < MAX_EVAL_TRIES + 1; i++) {
                RuntimeException ex = null;
                try {
                    checkConnection();
                    lockValue = connection.lock();
                    REXP r = connection.eval("try(" + command + ", silent=T)");
                    if (r == null)
                        return null;

                    if (r.inherits("try-error")) {
                        /*
                         * This is not an eval error that would warrant a retry.
                         */
                        throw new RuntimeException("Error from R: " + r.asString());
                    }
                    return r;

                } catch (RserveException e) {
                    ex = new RuntimeException("Error excecuting " + command + ": " + e.getMessage(), e);
                } catch (REXPMismatchException e) {
                    throw new RuntimeException(
                            "Error processing apparent error object returned by " + command + ": " + e.getMessage(),
                            e);
                }

                if (i == MAX_EVAL_TRIES) {
                    throw ex;
                }

                try {
                    log.debug("Eval failed, retrying");
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return null;
                }

            }

            throw new RuntimeException("Evaluation failed! No details available");
        } finally {
            if (lockValue != 0)
                connection.unlock(lockValue);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#finalize()
     */
    @Override
    public void finalize() {
        this.disconnect();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#getLastError()
     */
    @Override
    public String getLastError() {
        return connection.getLastError();
    }

    /**
     * 
     */
    @Override
    public boolean isConnected() {
        if (connection != null && connection.isConnected())
            return true;
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#retrieveMatrix(java.lang.String)
     */
    @Override
    public DoubleMatrix<String, String> retrieveMatrix(String variableName) {
        try {
            log.debug("Retrieving " + variableName);

            // REXP clr = this.eval( "class(" + variableName + ")" );
            // log.info( clr.asString() );

            // note that for some reason, asDoubleMatrix is returning a 1-d array. So I do this.
            REXP r = this.eval("data.frame(t(" + variableName + "))");
            if (r == null)
                throw new IllegalArgumentException(variableName + " not found in R context");

            RList dataframe = r.asList();
            int numrows = dataframe.size();
            double[][] results = new double[numrows][];
            int i = 0;
            for (Iterator<?> it = dataframe.iterator(); it.hasNext();) {
                REXP next = (REXP) it.next();
                double[] row = next.asDoubles();
                results[i] = row;
                i++;
            }

            DoubleMatrix<String, String> resultObject = new DenseDoubleMatrix<String, String>(results);

            retrieveRowAndColumnNames(variableName, resultObject);
            return resultObject;
        } catch (REXPMismatchException e) {
            throw new RuntimeException("Failed to get back matrix for variable " + variableName, e);
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.basecode.util.RClient#voidEval(java.lang.String)
     */
    @Override
    public void voidEval(String command) {
        if (command == null)
            throw new IllegalArgumentException("Null command");
        this.checkConnection();

        log.debug("voidEval: " + command);
        eval(command);

    }

    /**
     * 
     */
    private void checkConnection() {
        if (!this.isConnected()) {

            /*
             * This often won't work. Even if we reconnect, state will have been lost. However, under normal
             * circumstances (over a local network) connection loss should be very rare.
             */
            log.warn("Not connected, trying to reconnect");
            boolean ok = false;
            for (int i = 0; i < MAX_CONNECT_TRIES; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return;
                }
                ok = this.connect();
                if (ok)
                    break;
            }
            if (!ok) {
                throw new RuntimeException("Not connected");
            }
        }
    }

    /**
     * @param beQuiet
     */
    private boolean connect(boolean beQuiet) {
        if (connection != null && connection.isConnected()) {
            return true;
        }
        int tries = 3;
        Exception ex = null;
        for (int i = 0; i < tries; i++) {
            try {
                connection = new RConnection();
                return true;
            } catch (RserveException e) {
                ex = e;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    return false;
                }
            }
        }
        if (!beQuiet) {
            log.error("Could not connect to RServe: " + (ex == null ? "" : ex.getMessage()));
        }
        return false;
    }

    /**
     * @param host
     * @param port
     * @return
     */
    private boolean connect(String host, int port) {
        if (connection != null && connection.isConnected()) {
            return true;
        }
        try {
            connection = new RConnection(host, port);
        } catch (RserveException e) {
            log.error("Could not connect to RServe: " + e.getMessage());
            return false;
        }
        log.info("Connected via RServe.");
        return true;
    }

    /**
     * @param variableName
     * @param resultObject
     * @throws REXPMismatchException
     */
    private void retrieveRowAndColumnNames(String variableName, DoubleMatrix<String, String> resultObject) {
        List<String> rowNames = this.stringListEval("dimnames(" + variableName + ")[1][[1]]");

        if (rowNames.size() == resultObject.rows()) {
            resultObject.setRowNames(rowNames);
        }

        List<String> colNames = this.stringListEval("dimnames(" + variableName + ")[2][[1]]");

        if (colNames.size() == resultObject.columns()) {
            resultObject.setColumnNames(colNames);
        }
    }

}