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