Merge remote-tracking branch 'origin/master' into YaSp
[scilab.git] / scilab / modules / external_objects_java / src / java / org / scilab / modules / external_objects_java / ScilabJavaCompiler.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - 2011 - Calixte DENIZET <calixte@contrib.scilab.org>
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  *
11  */
12
13 package org.scilab.modules.external_objects_java;
14
15 import java.io.BufferedWriter;
16 import java.io.CharArrayWriter;
17 import java.io.File;
18 import java.io.FileReader;
19 import java.io.IOException;
20 import java.io.Reader;
21 import java.io.StringWriter;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.net.MalformedURLException;
25 import java.net.URI;
26 import java.net.URLClassLoader;
27 import java.net.URL;
28 import java.util.Arrays;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.ServiceLoader;
33 import java.util.logging.Level;
34
35 import javax.swing.SwingUtilities;
36 import javax.tools.Diagnostic;
37 import javax.tools.DiagnosticCollector;
38 import javax.tools.FileObject;
39 import javax.tools.ForwardingJavaFileManager;
40 import javax.tools.JavaCompiler;
41 import javax.tools.StandardLocation;
42 import javax.tools.JavaFileObject;
43 import javax.tools.SimpleJavaFileObject;
44 import javax.tools.StandardJavaFileManager;
45 import javax.tools.ToolProvider;
46 import javax.tools.JavaCompiler.CompilationTask;
47 import javax.tools.JavaFileObject.Kind;
48
49 import org.scilab.modules.commons.ScilabCommonsUtils;
50
51 /**
52  * Class to provide a java compiler to JIMS.
53  * Try to find the compiler provide with JDK and if it is not found, use the Eclipse Compiler for Java
54  * @author Calixte DENIZET
55  */
56 @SuppressWarnings("serial")
57 public class ScilabJavaCompiler {
58
59     private static final String JAVACOMPILER = "javax.tools.JavaCompiler";
60     private static final String BINPATH = System.getProperty("java.io.tmpdir") + File.separator + "JIMS" + File.separator + "bin";
61
62     private static JavaCompiler compiler;
63     private static boolean ecjLoaded = false;
64
65     private static boolean isECJ;
66
67
68     static {
69         new File(System.getProperty("java.io.tmpdir") + File.separator + "JIMS").mkdir();
70         new File(BINPATH).mkdir();
71         try {
72             URL binURL = new File(BINPATH).toURI().toURL();
73             addURLToClassPath(binURL);
74         } catch (MalformedURLException e) {
75             System.err.println(e);
76         }
77     }
78
79     /**
80      * Just find a compiler
81      */
82     private static void findCompiler() throws ScilabJavaException {
83         if (compiler == null) {
84             try {
85                 compiler = ToolProvider.getSystemJavaCompiler();
86             } catch (Exception e) { }
87
88             if (compiler == null) {
89                 ServiceLoader<JavaCompiler> jcompilers = ServiceLoader.load(JavaCompiler.class);
90                 for (JavaCompiler jc : jcompilers) {
91                     if (jc != null) {
92                         compiler = jc;
93                         break;
94                     }
95                 }
96             }
97
98             if (compiler == null) {
99                 if (ecjLoaded) {
100                     throw new ScilabJavaException("No java compiler in the classpath\nCheck for tools.jar (comes from JDK) or ecj-3.6.x.jar (Eclipse Compiler for Java)");
101                 }
102
103                 // Compiler should be in thirdparty so we load it
104                 ScilabCommonsUtils.loadOnUse("external_objects_java");
105                 ecjLoaded = true;
106                 findCompiler();
107             }
108
109             isECJ = compiler.getClass().getSimpleName().indexOf("Eclipse") != -1;
110         }
111     }
112
113     /**
114      * Compile code got as string
115      * @param className the class name
116      * @param code the lines giving the code to compile
117      * @return an integer corresponding to the compiled and loaded class.
118      */
119     public static int compileCode(String className, String[] code) throws ScilabJavaException {
120         if (SwingUtilities.isEventDispatchThread()) {
121             findCompiler();
122         } else {
123             try {
124                 SwingUtilities.invokeAndWait(new Runnable() {
125                     @Override
126                     public void run() {
127                         try {
128                             findCompiler();
129                         } catch (ScilabJavaException e) {
130                             // TODO Auto-generated catch block
131                             e.printStackTrace();
132                         }
133                     }
134                 });
135             } catch (InvocationTargetException e) {
136                 // TODO Auto-generated catch block
137                 e.printStackTrace();
138             } catch (InterruptedException e) {
139                 // TODO Auto-generated catch block
140                 e.printStackTrace();
141             }
142         }
143
144         DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
145         StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);
146         String cp = null;
147
148         if (isECJ) {
149             // it seems that with the embedded ecj, the only way to set the cp is to use java.class.path...
150             cp = getClasspath();
151             System.setProperty("java.class.path", cp + File.pathSeparatorChar + System.getProperty("java.class.path"));
152         } else {
153             try {
154                 stdFileManager.setLocation(StandardLocation.CLASS_PATH, getClasspathFiles());
155             } catch (Exception e) { }
156         }
157
158         ClassFileManager manager = new ClassFileManager(stdFileManager);
159         List<SimpleJavaFileObject> compilationUnits = new ArrayList<SimpleJavaFileObject>();
160         CharArrayWriter caw = new CharArrayWriter();
161         BufferedWriter out = new BufferedWriter(caw);
162         boolean isFile = true;
163         SourceString sourceString = null;
164         for (String s : code) {
165             File f = new File(s);
166             if (!f.exists() || !f.canRead()) {
167                 isFile = false;
168                 break;
169             }
170         }
171
172         if (isFile) {
173             for (String s : code) {
174                 File f = new File(s);
175                 compilationUnits.add(new SourceFile(f));
176             }
177         } else {
178             sourceString = new SourceString(className, code);
179             compilationUnits.add(sourceString);
180         }
181
182         String[] compileOptions = new String[] {"-d", BINPATH};
183         Iterable<String> options = Arrays.asList(compileOptions);
184
185         CompilationTask task = compiler.getTask(out, manager, diagnostics, options, null, compilationUnits);
186         boolean success = task.call();
187
188         if (cp != null) {
189             final String s = System.getProperty("java.class.path").replace(cp + File.pathSeparatorChar, "");
190             System.setProperty("java.class.path", s);
191         }
192
193         String error = "";
194
195         try {
196             out.flush();
197             error = caw.toString();
198         } catch (IOException e) {
199
200         } finally {
201             try {
202                 out.close();
203             } catch (IOException e) { }
204         }
205
206         if (success) {
207             if (isFile) {
208                 return -1;
209             } else {
210                 if (manager.className != null && !manager.className.isEmpty()) {
211                     return ScilabClassLoader.loadJavaClass(BINPATH, manager.className);
212                 } else {
213                     return -1;
214                 }
215             }
216         } else {
217             if (!isECJ) {
218                 error = getCompilerErrors(diagnostics);
219             }
220
221             throw new ScilabJavaException(error);
222         }
223     }
224
225     /**
226      * Returns the compilation errors from the diagnostics
227      * @param diagnostics the diagnostics returned by the compiler
228      * @return a string containing the errors
229      */
230     public static String getCompilerErrors(DiagnosticCollector<JavaFileObject> diagnostics) {
231         StringBuffer buffer = new StringBuffer();
232         int cpt = 1;
233         buffer.append("----------\n");
234         for (Diagnostic <? extends JavaFileObject > d : diagnostics.getDiagnostics()) {
235             buffer.append(Integer.toString(cpt++)).append(". ").append(d.getKind());
236             if (d.getSource() != null) {
237                 buffer.append(" in ").append(d.getSource().toUri().getPath()).append(" (at line ").append(Long.toString(d.getLineNumber())).append(")\n");
238
239                 Reader reader = null;
240                 try {
241                     reader = d.getSource().openReader(true);
242                     reader.skip(d.getStartPosition());
243                     char[] data = new char[(int) (d.getEndPosition() - d.getStartPosition() + 1)];
244                     reader.read(data);
245                     buffer.append("        ").append(data).append('\n');
246                     Arrays.fill(data, '^');
247                     buffer.append("        ").append(data).append('\n');
248                 } catch (IOException e) {
249
250                 } finally {
251                     if (reader != null) {
252                         try {
253                             reader.close();
254                         } catch (IOException e) { }
255                     }
256                 }
257             } else {
258                 // this is not a file related error
259                 buffer.append('\n');
260             }
261             buffer.append(d.getMessage(Locale.getDefault())).append('\n');
262         }
263
264         buffer.append("----------\n");
265         return buffer.toString();
266     }
267
268     /**
269      * Get the current classpath with the correct separator (':' under Unix and ';' under Windows)
270      * @return the classpath
271      */
272     public static String getClasspath() {
273         URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
274         URL[] urls = loader.getURLs();
275         StringBuffer buffer = new StringBuffer();
276
277         for (URL url : urls) {
278             buffer.append(url.getPath()).append(File.pathSeparatorChar);
279         }
280         buffer.append(".");
281
282         return buffer.toString();
283     }
284
285     /**
286      * Get the files in the classpath
287      * @return the files
288      */
289     public static List<File> getClasspathFiles() {
290         URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
291         URL[] urls = loader.getURLs();
292         List<File> files = new ArrayList<File>(urls.length);
293
294         for (URL url : urls) {
295             try {
296                 files.add(new File(url.toURI()));
297             } catch (Exception e) { }
298         }
299
300         return files;
301     }
302
303     /**
304      * Add a class in the classpath
305      * @param url the class url
306      */
307     public static void addURLToClassPath(URL url) {
308         URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
309         try {
310             final Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {URL.class});
311             method.setAccessible(true);
312             method.invoke(sysloader , new Object[] {url});
313         } catch (NoSuchMethodException e) {
314             System.err.println("Error: Cannot find the declared method: " + e.getLocalizedMessage());
315         } catch (IllegalAccessException e) {
316             System.err.println("Error: Illegal access: " + e.getLocalizedMessage());
317         } catch (InvocationTargetException e) {
318             System.err.println("Error: Could not invocate target: " + e.getLocalizedMessage());
319         }
320     }
321
322     /**
323      * Inner class to handle String as File
324      */
325     private static class SourceString extends SimpleJavaFileObject {
326
327         private final String code;
328
329         private SourceString(String className, String[] code) {
330             super(new File(BINPATH + "/" + className.replace('.', '/') + Kind.SOURCE.extension).toURI(), Kind.SOURCE);
331
332             StringBuffer buf = new StringBuffer();
333             for (String str : code) {
334                 buf.append(str);
335                 buf.append("\n");
336             }
337             this.code = buf.toString();
338         }
339
340         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
341             return code;
342         }
343     }
344
345     /**
346      * Inner class to handle String as File
347      */
348     private static class SourceFile extends SimpleJavaFileObject {
349
350         final File f;
351
352         private SourceFile(File f) {
353             super(f.toURI(), Kind.SOURCE);
354             this.f = f;
355         }
356
357         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
358             try {
359                 FileReader reader = new FileReader(f);
360                 char[] buffer = new char[1024];
361                 StringBuffer sb = new StringBuffer();
362                 int r;
363
364                 while ((r = reader.read(buffer, 0, 1024)) != -1) {
365                     sb.append(buffer, 0, r);
366                 }
367
368                 reader.close();
369
370                 return sb;
371             } catch (Exception e) {
372                 return null;
373             }
374         }
375     }
376
377     private static class ClassFileManager extends ForwardingJavaFileManager {
378
379         String className;
380
381         public ClassFileManager(StandardJavaFileManager standardManager) {
382             super(standardManager);
383         }
384
385         @Override
386         public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
387             if (sibling instanceof SourceString && className != null && className.indexOf('$') == -1) {
388                 this.className = className.replace('/', '.');
389             }
390
391             if (ScilabJavaObject.debug) {
392                 ScilabJavaObject.logger.log(Level.INFO, "Compilation of class \'" + className + "\'");
393             }
394
395             return super.getJavaFileForOutput(location, className, kind, sibling);
396         }
397     }
398 }