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 | } |