| 1 | package de.uka.ipd.sdq.sensorframework.visualisation.rvisualisation.utils; |
| 2 | |
| 3 | import java.util.Vector; |
| 4 | |
| 5 | import org.apache.log4j.Logger; |
| 6 | import org.eclipse.core.runtime.IStatus; |
| 7 | import org.eclipse.jface.dialogs.MessageDialog; |
| 8 | import org.eclipse.ui.IWorkbenchWindow; |
| 9 | import org.eclipse.ui.PlatformUI; |
| 10 | import org.rosuda.JRI.REXP; |
| 11 | import org.rosuda.JRI.Rengine; |
| 12 | |
| 13 | import de.uka.ipd.sdq.sensorframework.visualisation.rvisualisation.RVisualisationPlugin; |
| 14 | |
| 15 | /**Encapsulate the access to the R engine. |
| 16 | * Is responsible for the initialization of the R engine and the execution |
| 17 | * of R commands. |
| 18 | * @author groenda |
| 19 | */ |
| 20 | public class RConnection { |
| 21 | /** The logger used by this class. */ |
| 22 | private static Logger logger = |
| 23 | Logger.getLogger(RConnection.class.getName()); |
| 24 | |
| 25 | /** The text console of the connected R engine. */ |
| 26 | private static RTextConsole rConsole = new RTextConsole(); |
| 27 | |
| 28 | |
| 29 | /** The R engine that is used by this class. */ |
| 30 | private static Rengine rengine = null; |
| 31 | /** Global connection to the R engine. */ |
| 32 | private static RConnection rConnection = null; |
| 33 | |
| 34 | static { |
| 35 | rConnection = new RConnection(); |
| 36 | } |
| 37 | |
| 38 | /**Initializes the connection to the R engine. |
| 39 | */ |
| 40 | public RConnection() { |
| 41 | initalizeConnection(); |
| 42 | } |
| 43 | |
| 44 | /**Initializes the connection to a R engine. |
| 45 | */ |
| 46 | protected void initalizeConnection() { |
| 47 | if (rConnection != null) { |
| 48 | return; |
| 49 | } |
| 50 | |
| 51 | checkPathValidity(); |
| 52 | |
| 53 | // initialize connection |
| 54 | |
| 55 | |
| 56 | |
| 57 | // Disable complete shut dpwn, if an exception within R is detected. |
| 58 | // Properties prop=new Properties(); |
| 59 | // prop.put("jri.ignore.ule", "yes"); |
| 60 | // System.setProperties(prop); |
| 61 | System.setProperty("jri.ignore.ule", "yes"); |
| 62 | |
| 63 | // just making sure we have the right version of everything |
| 64 | try{ |
| 65 | if (!Rengine.versionCheck()) { |
| 66 | long javaVersion = Rengine.getVersion(); |
| 67 | long rniVersion = Rengine.rniGetVersion(); |
| 68 | RVisualisationPlugin |
| 69 | .log( |
| 70 | IStatus.ERROR, |
| 71 | "Creating R engine ** Version mismatch - Java files (version "+javaVersion+") do not " |
| 72 | + "match library version (version "+rniVersion+")."); |
| 73 | new MessageDialog( |
| 74 | PlatformUI.getWorkbench().getActiveWorkbenchWindow() |
| 75 | .getShell(), |
| 76 | "Error loading R", |
| 77 | null, |
| 78 | "Could not load R. The version of the java files (version "+javaVersion+") and the " |
| 79 | + "library versions (version "+rniVersion+") do not match.", |
| 80 | MessageDialog.ERROR, new String[] { "OK" }, 0).open(); |
| 81 | //return; // keep on trying to get a more detailed error message |
| 82 | } |
| 83 | } catch (UnsatisfiedLinkError ule) { |
| 84 | RVisualisationPlugin.log( |
| 85 | IStatus.ERROR, |
| 86 | "Could not load the dynamic link libaries that are " |
| 87 | + "necessary to connect the sensorframework " |
| 88 | + " to R 2.12. The JRI provided with this package is " |
| 89 | + "designed for R 2.12.0, check the detailed " |
| 90 | + "error message if a version conflict may have occured." |
| 91 | + "Ensure jri.dll is within the java.library.path " |
| 92 | + "variable and R's bin directory is on the system " |
| 93 | + "path. Details: java.library.path=" |
| 94 | + System.getProperty("java.library.path") |
| 95 | + ";errorMessage=" + ule.getMessage()); |
| 96 | new MessageDialog( |
| 97 | PlatformUI.getWorkbench().getActiveWorkbenchWindow() |
| 98 | .getShell(), |
| 99 | "Error loading R", |
| 100 | null, |
| 101 | "Could not load R 2.12. You need to install the correct " |
| 102 | + "Version of R on your machine. Put R's binary " |
| 103 | + "folder into your system path, so the dynamic link " |
| 104 | + "libraries can be found. " |
| 105 | + "Check the error log for a detailed message.", |
| 106 | MessageDialog.ERROR, new String[] { "OK" }, 0).open(); |
| 107 | return; |
| 108 | } |
| 109 | |
| 110 | // 1) we pass the arguments from the command line |
| 111 | // 2) we won't use the main loop at first, we'll start it later |
| 112 | // (that's the "false" as second argument) |
| 113 | // 3) the callbacks are implemented by the TextConsole class |
| 114 | // above |
| 115 | rengine = new Rengine(new String[] {"--vanilla", "--slave"}, false, rConsole); |
| 116 | |
| 117 | // the engine creates R is a new thread, so we should wait until |
| 118 | // it's ready |
| 119 | |
| 120 | if (!rengine.waitForR()) { |
| 121 | RVisualisationPlugin |
| 122 | .log( |
| 123 | IStatus.ERROR, |
| 124 | "Creating R engine ** Waiting for the R engine to come up " |
| 125 | + "failed. Please check the R-output on the console for more details, as they are written to System.out and System.err"); |
| 126 | new MessageDialog( |
| 127 | PlatformUI.getWorkbench() |
| 128 | .getActiveWorkbenchWindow().getShell(), |
| 129 | "Error loading R", |
| 130 | null, |
| 131 | "Could not load R. The R engine didn't come up in time.\n" |
| 132 | +"Please check the output on the console for more details, as they are written to System.out and System.err", |
| 133 | MessageDialog.ERROR, new String[] { "OK" }, 0).open(); |
| 134 | rengine = null; |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | RVisualisationPlugin.log(IStatus.INFO, "Connection to R established " |
| 139 | + "successfully."); |
| 140 | prepareEnvironment(); |
| 141 | checkPackageAvailability(); |
| 142 | } |
| 143 | |
| 144 | /**Checks the availability of the package with the given name in R. |
| 145 | * If it is not available an error message is logged and displayed. |
| 146 | * @param packageName The name of the R package. |
| 147 | */ |
| 148 | private void checkPackageAvailability(final String packageName) { |
| 149 | String previousMessage = getLastConsoleMessage(); |
| 150 | rengine.eval("library(" + packageName + ")"); |
| 151 | String result = getLastConsoleMessage(); |
| 152 | if (!previousMessage.equals(result)) { |
| 153 | RVisualisationPlugin |
| 154 | .log( |
| 155 | IStatus.ERROR, |
| 156 | "Library \"" + packageName + "\" is not available. Please " |
| 157 | + "install the \"" + packageName + "\" package in your R " |
| 158 | + "installation.\n Error details: " + result + "\n" |
| 159 | + "Possible Solution: \n" |
| 160 | + "If you are using Windows Vista check if the package is " |
| 161 | + "in the installation path of R and not in the user path." |
| 162 | + "This can be achieved by executing the R command " |
| 163 | + "\"library\"."); |
| 164 | //Get the ActiveWorkbenchWindow to send the error message to or handle it of there is none. |
| 165 | IWorkbenchWindow wbw = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| 166 | if (wbw != null){ |
| 167 | new MessageDialog( |
| 168 | wbw.getShell(), |
| 169 | "Library \"" + packageName + "\" is not available in R", |
| 170 | null, |
| 171 | "The library \"" + packageName + "\" is not available. " |
| 172 | + "Please install the \"" + packageName + "\" package in " |
| 173 | + "your R installation or" |
| 174 | + " the R reports will not work properly. Check the PDE " |
| 175 | + "error log for more information.", |
| 176 | MessageDialog.ERROR, new String[] { "OK" }, 0).open(); |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | /**Checks the availability of all necessary packages. |
| 182 | * If some are not available an error message is logged. |
| 183 | */ |
| 184 | private void checkPackageAvailability() { |
| 185 | checkPackageAvailability("plotrix"); |
| 186 | } |
| 187 | |
| 188 | /**Checks if the java.library.path is valid for system.loadLibrary(). |
| 189 | */ |
| 190 | private void checkPathValidity() { |
| 191 | String libraryPath = System.getProperty("java.library.path"); |
| 192 | String[] libraryPaths = libraryPath.split(";"); |
| 193 | Vector<String> conflictingPaths = new Vector<String>(); |
| 194 | for (String path : libraryPaths) { |
| 195 | if (path.contains(" ") && !path.startsWith("\"") |
| 196 | && !path.endsWith("\"")) { |
| 197 | conflictingPaths.add(path); |
| 198 | } |
| 199 | } |
| 200 | if (!conflictingPaths.isEmpty()) { |
| 201 | String formattedPath = ""; |
| 202 | for (String conflictPath : conflictingPaths) { |
| 203 | formattedPath += "'" + conflictPath + "', "; |
| 204 | } |
| 205 | // cut last ", " |
| 206 | formattedPath = |
| 207 | formattedPath.substring(0, formattedPath.length() - 2); |
| 208 | RVisualisationPlugin.log( |
| 209 | IStatus.WARNING, |
| 210 | "The environment variable java.library.path contains " |
| 211 | + " unescaped spaces. This may lead to errors loading " |
| 212 | + "the necessary dynamic link libraries of R.\n\n" |
| 213 | + "Conflicting parts of the java.library.path are: " |
| 214 | + formattedPath + "\n\n" |
| 215 | + "A possible solution is to set the library path to" |
| 216 | + " point to the path containing jri.dll via the -D" |
| 217 | + " command line switch of the java VM of by setting" |
| 218 | + " it via the eclipse.ini file. However, this does" |
| 219 | + " not work in all cases."); |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /**This method is used to prepare the R environment. Additionally, |
| 224 | * Information about the R environment is gathered and logged with |
| 225 | * level debug. |
| 226 | */ |
| 227 | private void prepareEnvironment() { |
| 228 | rengine.eval("Sys.setlocale(\"LC_ALL\", \"German_Germany.1252\")"); |
| 229 | rengine.eval("Sys.setlocale(\"LC_NUMERIC\", \"C\")"); |
| 230 | rengine.eval("rUser <- chartr(\"\\\\\", \"/\", " |
| 231 | + "Sys.getenv(\"R_USER\"))"); |
| 232 | rengine.eval("rLibs <- chartr(\"\\\\\", \"/\", " |
| 233 | + "Sys.getenv(\"R_LIBS_USER\"))"); |
| 234 | rengine.eval("homedrive <- chartr(\"\\\\\", \"/\", " |
| 235 | + "Sys.getenv(\"HOMEDRIVE\"))"); |
| 236 | rengine.eval("homepath <- chartr(\"\\\\\", \"/\", " |
| 237 | + "Sys.getenv(\"HOMEPATH\"))"); |
| 238 | rengine.eval("usrLibPath <- substring(strsplit(rLibs, " |
| 239 | + "rUser)[[1]][2],2)"); |
| 240 | rengine.eval("Sys.setenv(\"R_USER\"=paste(homedrive, homepath, " |
| 241 | + "sep=\"\"))"); |
| 242 | rengine.eval("Sys.setenv(\"R_LIBS_USER\"=paste(Sys.getenv(\"R_USER\"), " |
| 243 | + "usrLibPath, sep=\"\")[[1]])"); |
| 244 | |
| 245 | REXP envContent = rengine.eval("Sys.getenv()"); |
| 246 | REXP envNames = rengine.eval("names(s <- Sys.getenv())"); |
| 247 | String[] sEnvContent = envContent.asStringArray(); |
| 248 | String[] sEnvNames = envNames.asStringArray(); |
| 249 | String locale = ""; |
| 250 | for (int number = 0; number < sEnvContent.length; number++) { |
| 251 | locale += sEnvNames[number] + " = " + sEnvContent[number] + "\n"; |
| 252 | } |
| 253 | REXP locales = rengine.eval("Sys.getlocale()"); |
| 254 | String[] sLocales = locales.asStringArray(); |
| 255 | locale += "\nLocalization information:\n"; |
| 256 | for (int number = 0; number < sLocales.length; number++) { |
| 257 | locale += sLocales[number] + "\n"; |
| 258 | } |
| 259 | |
| 260 | logger.debug("Environmental Information:\n" |
| 261 | + locale); |
| 262 | // MessageBox needed, as PDE log only allows few characters. |
| 263 | // new MessageDialog( |
| 264 | // PlatformUI.getWorkbench() |
| 265 | // .getActiveWorkbenchWindow().getShell(), |
| 266 | // "Environmental Information", |
| 267 | // null, |
| 268 | // locale, |
| 269 | // MessageDialog.INFORMATION, new String[] { "OK" }, 0).open(); |
| 270 | } |
| 271 | |
| 272 | /**Executes the command(s) in R. |
| 273 | * @param rCommands One or more valid commands in R, separated by |
| 274 | * <code>\n</code>. |
| 275 | * @return result Result returned from R. Empty if no result was returned. |
| 276 | */ |
| 277 | public Vector<REXP> execute(final String rCommands) { |
| 278 | if (!isEngineAvailable()) { |
| 279 | throw new UnsupportedOperationException( |
| 280 | "Tried to execute command in R without having a R engine" |
| 281 | + " available."); |
| 282 | } |
| 283 | |
| 284 | String[] commands = rCommands.split("\n"); |
| 285 | String result = ""; |
| 286 | REXP resultExp; |
| 287 | Vector<REXP> resultExpArray = new Vector<REXP>(); |
| 288 | |
| 289 | for (String command : commands) { |
| 290 | resultExp = rengine.eval(command); |
| 291 | if (resultExp != null) { |
| 292 | result += resultExp.toString() + "\n"; |
| 293 | resultExpArray.add(resultExp); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | return resultExpArray; |
| 298 | } |
| 299 | |
| 300 | /**Stores an array in a R variable. |
| 301 | * @param name Name of the R variable in which the array is stored. |
| 302 | * @param array Array to store in an R variable. |
| 303 | */ |
| 304 | public void assign(final String name, final double[] array) { |
| 305 | rengine.assign(name, array); |
| 306 | } |
| 307 | |
| 308 | /**checks if an R engine could be found and the connection is established. |
| 309 | * @return <code>true</code> if the connection is established. |
| 310 | */ |
| 311 | public static boolean isEngineAvailable() { |
| 312 | return !(rengine == null); |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * @return returns the current RConnection. |
| 317 | */ |
| 318 | public static RConnection getRConnection() { |
| 319 | return rConnection; |
| 320 | } |
| 321 | |
| 322 | /** |
| 323 | * @return the last message on the console of the connected R engine. |
| 324 | */ |
| 325 | public String getLastConsoleMessage() { |
| 326 | return rConsole.getLastMessage(); |
| 327 | |
| 328 | } |
| 329 | |
| 330 | } |