/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.util.r; import static com.google.common.base.Preconditions.checkNotNull; import java.awt.Image; import java.awt.Toolkit; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.rosuda.REngine.REXP; import org.rosuda.REngine.REXPMismatchException; import org.rosuda.REngine.REngineException; import org.rosuda.REngine.Rserve.RConnection; import org.rosuda.REngine.Rserve.RFileInputStream; import org.rosuda.REngine.Rserve.RserveException; import fr.ens.biologie.genomique.eoulsan.util.FileUtils; /** * This class define an enhanced connection to RServe. * @author Laurent Jourdren * @author Marion Gaussen * @since 1.2 */ public class RSConnection { private final String serverName; private RConnection rconnection; private static final int BUFFER_SIZE = 32 * 1024; // // Getters // /** * Get the R connection. * @return Returns the RConnection * @throws REngineException */ public RConnection getRConnection() throws REngineException { if (this.rconnection == null) { connect(); } return this.rconnection; } /** * Get the name of the Rserve server. * @return the name of the Rserve server */ public String getServerName() { return this.serverName; } // // Other methods // /** * Write a file to the RServer. * @param outputFilename the filename * @param value The content of the file * @throws REngineException if an error occurs while writing the file */ public void writeStringAsFile(final String outputFilename, final String value) throws REngineException { if (outputFilename == null) { return; } try { final Writer writer = FileUtils.createBufferedWriter(getFileOutputStream(outputFilename)); if (value != null) { writer.write(value); writer.close(); } } catch (IOException e) { throw new REngineException(getRConnection(), "Error: " + e.getMessage()); } } /** * Create an inputStream on a file on RServer. * @param filename Name of the file on RServer to load * @return an inputStream * @throws REngineException if an exception occurs while reading file */ public InputStream getFileInputStream(final String filename) throws REngineException { final RConnection c = getRConnection(); try { return c.openFile(filename); } catch (IOException e) { throw new REngineException(c, "Error: " + e.getMessage()); } } /** * Create an outputStream on a file on RServer. * @param filename Name of the file on RServer to write * @return an outputStream * @throws REngineException if an exception occurs while reading file */ public OutputStream getFileOutputStream(final String filename) throws REngineException { final RConnection c = getRConnection(); try { return c.createFile(filename); } catch (IOException e) { throw new REngineException(c, "Error: " + e.getMessage()); } } /** * Put a file from the RServer. * @param inputFile input file of the file to put * @param rServeFilename filename of the file to put * @throws REngineException if an error occurs while downloading the file */ public void putFile(final File inputFile, final String rServeFilename) throws REngineException { checkNotNull(inputFile, "inputFile argument cannot be null"); checkNotNull(rServeFilename, "rServeFilename argument cannot be null"); try { putFile(new FileInputStream(inputFile), rServeFilename); } catch (FileNotFoundException e) { throw new REngineException(getRConnection(), "file not found: " + e.getMessage()); } } /** * Put a file from the RServer. * @param is input stream of the file to put * @param rServeFilename filename of the file to put * @throws REngineException if an error occurs while downloading the file */ public void putFile(final InputStream is, final String rServeFilename) throws REngineException { checkNotNull(is, "inputFile argument cannot be null"); checkNotNull(rServeFilename, "rServeFilename argument cannot be null"); try { OutputStream os = getFileOutputStream(rServeFilename); byte[] buf = new byte[BUFFER_SIZE]; int i = 0; while ((i = is.read(buf)) != -1) { os.write(buf, 0, i); } is.close(); os.close(); } catch (REngineException e) { throw new REngineException(getRConnection(), "Unable to put file: " + e.getMessage()); } catch (IOException e) { throw new REngineException(getRConnection(), "Unable to create report: " + e.getMessage()); } } /** * Get a file from the RServer. * @param rServeFilename filename of the file to retrieve * @param outputFile output file of the file to retrieve * @throws REngineException if an error occurs while downloading the file */ public void getFile(final String rServeFilename, final File outputFile) throws REngineException { try { InputStream is = getFileInputStream(rServeFilename); OutputStream os = new FileOutputStream(outputFile); byte[] buf = new byte[BUFFER_SIZE]; int i = 0; while ((i = is.read(buf)) != -1) { os.write(buf, 0, i); } is.close(); os.close(); } catch (REngineException e) { throw new REngineException(getRConnection(), "Unable to get file"); } catch (FileNotFoundException e) { throw new REngineException(getRConnection(), "file not found"); } catch (IOException e) { throw new REngineException(getRConnection(), "Unable to create report."); } } /** * Get a list of files from the RServer. * @param rServeFilenames list of filenames of the files to retrieve * @param zipFile zip output file for the files to retrieve * @throws REngineException if an error occurs while downloading the file */ public void getFilesIntoZip(final List<String> rServeFilenames, final File zipFile) throws REngineException { try { ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); final byte[] buf = new byte[BUFFER_SIZE]; for (String f : rServeFilenames) { final InputStream is = getFileInputStream(f); // Add Zip entry to output stream. out.putNextEntry(new ZipEntry(f)); int i = 0; while ((i = is.read(buf)) != -1) { out.write(buf, 0, i); } // Complete the entry out.closeEntry(); is.close(); } // Complete the Zip file out.close(); } catch (REngineException e) { throw new REngineException(getRConnection(), "Unable to get file"); } catch (FileNotFoundException e) { throw new REngineException(getRConnection(), "File not found"); } catch (IOException e) { throw new REngineException(getRConnection(), "Unable to get file"); } } /** * Remove a file on the RServer. * @param filename File to remove */ public void removeFile(final String filename) throws REngineException { // Test if the file exists final RConnection c = getRConnection(); try { REXP exists = c.eval("file.exists(\"" + filename + "\")"); if (exists.asInteger() == 1) { c.voidEval("file.remove(\"" + filename + "\")"); } } catch (RserveException | REXPMismatchException e) { throw new REngineException(c, "RServe exception: " + e); } } /** * Remove all the files of the working directory. * @throws REngineException if an error occurs while removing the file * @throws REXPMismatchException if an error occurs while removing the file */ public void removeAllFiles() throws REngineException, REXPMismatchException { for (String file : listFiles()) { removeFile(file); } } /** * Override the commandArg() R function. * @param arguments the arguments * @throws REngineException if an error occurs while executing R code */ public void setCommandArgs(final List<String> arguments) throws REngineException { if (arguments == null) { throw new NullPointerException("arguments argument cannot be null"); } final StringBuilder sb = new StringBuilder(); sb.append("f <- function(trailingOnly = FALSE) { c("); boolean first = true; for (String arg : arguments) { if (first) { first = false; } else { sb.append(","); } sb.append('\''); sb.append(arg); sb.append('\''); } sb.append(") }"); final RConnection c = getRConnection(); try { // Execute the source c.voidEval(sb.toString()); } catch (RserveException e) { throw new REngineException(c, "RServe exception: " + e); } } /** * Execute a R code. * @param source code to execute * @throws REngineException if an error while executing the code */ public void executeRCode(final String source) throws REngineException { if (source == null) { return; } final RConnection c = getRConnection(); try { // Execute the source c.voidEval("source(\"" + source + "\")"); } catch (RserveException e) { throw new REngineException(c, "RServe exception: " + e); } } /** * Execute a R Sweave code. * @param source code to execute * @throws REngineException if an error while executing the code */ public void executeRnwCode(final String source) throws REngineException { executeRnwCode(source, null); } /** * Execute a R Sweave code. * @param source code to execute * @param latexOutput output latex filename * @throws REngineException if an error while executing the code */ public void executeRnwCode(final String source, final String latexOutput) throws REngineException { if (source == null) { return; } final RConnection c = getRConnection(); final StringBuilder sb = new StringBuilder(); sb.append("Sweave(\""); sb.append(source); sb.append('\"'); if (latexOutput != null) { sb.append(", output=\""); sb.append(latexOutput); sb.append('\"'); } sb.append(')'); try { c.voidEval(sb.toString()); } catch (RserveException e) { throw new REngineException(c, "Rserve exception: " + e); } } /** * Load an image. * @param filename filename of the image on the server * @return an Image object * @throws REngineException if an error while loading the image */ public Image loadImage(final String filename) throws REngineException { if (filename == null) { return null; } final RConnection c = getRConnection(); if (c == null) { throw new REngineException(null, "Connection is null"); } try { RFileInputStream is = c.openFile(filename); ArrayList<byte[]> buffers = new ArrayList<>(); int bufSize = 65536; byte[] buf = new byte[bufSize]; int imgLength = 0; int n = 0; while (true) { n = is.read(buf); if (n == bufSize) { buffers.add(buf); buf = new byte[bufSize]; } if (n > 0) { imgLength += n; } if (n < bufSize) { break; } } if (imgLength < 10) { // this shouldn't be the case actually, // because we did some error checking, but // for those paranoid ... throw new REngineException(c, "Cannot load image, check R output, probably R didn't produce anything."); } // now let's join all the chunks into one, big array ... byte[] imgCode = new byte[imgLength]; int imgPos = 0; for (byte[] b : buffers) { System.arraycopy(b, 0, imgCode, imgPos, bufSize); imgPos += bufSize; } if (n > 0) { System.arraycopy(buf, 0, imgCode, imgPos, n); } // ... and close the file ... and remove it - we have what we need :) is.close(); c.removeFile("test.jpg"); // now this is pretty boring AWT stuff, nothing to do with R ... return Toolkit.getDefaultToolkit().createImage(imgCode); } catch (IOException e) { throw new REngineException(c, "Error while load image"); } catch (RserveException e) { throw new REngineException(c, "Error while removing image from server"); } } /** * Get a file as a byte array. * @param filename filename of the file on the server * @return a byte array * @throws REngineException if an error while loading the file */ public byte[] getFileAsArray(final String filename) throws REngineException { final RConnection c = getRConnection(); if (c == null) { throw new REngineException(null, "Connection is null"); } try { RFileInputStream is = c.openFile(filename); ArrayList<byte[]> buffers = new ArrayList<>(); int bufSize = 65536; byte[] buf = new byte[bufSize]; int imgLength = 0; int n = 0; while (true) { n = is.read(buf); if (n == bufSize) { buffers.add(buf); buf = new byte[bufSize]; } if (n > 0) { imgLength += n; } if (n < bufSize) { break; } } if (imgLength < 10) { throw new REngineException(c, "Cannot load files, check R output, probably R didn't produce anything."); } byte[] imgCode = new byte[imgLength]; int imgPos = 0; for (byte[] b : buffers) { System.arraycopy(b, 0, imgCode, imgPos, bufSize); imgPos += bufSize; } if (n > 0) { System.arraycopy(buf, 0, imgCode, imgPos, n); } is.close(); return imgCode; } catch (IOException e) { throw new REngineException(c, "Error while loading files"); } } /** * Open a file to read on Rserve server. * @param filename the filename * @return an input stream * @throws REngineException if an error occurs while creating the input stream */ public RFileInputStream openFile(final String filename) throws REngineException { final RConnection c = getRConnection(); RFileInputStream file; try { file = c.openFile(filename); } catch (IOException e) { throw new REngineException(c, "Error while opening file"); } return file; } /** * Get all the file on the Rserve server. * @param outPath the output path * @throws REngineException if an error occurs while retrieving the files * @throws REXPMismatchException if an error occurs while retrieving the files */ public void getAllFiles(final File outPath) throws REngineException, REXPMismatchException { if (outPath == null) { throw new NullPointerException("outPath argument cannot be null"); } for (String file : listFiles()) { getFile(file, new File(outPath, file)); } } /** * List files on Rserve server. * @return files a String list of files names * @throws REngineException * @throws REXPMismatchException */ public List<String> listFiles() throws REngineException, REXPMismatchException { final RConnection c = getRConnection(); String[] files = c.eval("list.files()").asStrings(); if (files == null) { return Collections.emptyList(); } return Arrays.asList(files); } /** * Connect to the Rserve server. * @throws REngineException if an error occurs while connecting to the server */ private void connect() throws REngineException { try { this.rconnection = new RConnection(this.serverName); } catch (RserveException e) { throw new REngineException(this.rconnection, "Unable to connect to the server: " + e.getMessage()); } } /** * Destroy the connection to the Rserve server. */ public void disConnect() { if (this.rconnection != null) { this.rconnection.close(); this.rconnection = null; } } // // Constructor // /** * Default constructor. Connect to the localhost. */ public RSConnection() { this(null); } /** * Public constructor. * @param serverName RServe server to use */ public RSConnection(final String serverName) { this.serverName = serverName == null ? "127.0.0.1" : serverName.trim(); } }