Bug 13010 fixed: Wrong class was returned by jcompile (with ecj)
[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                 if (manager.className != null && !manager.className.isEmpty()) {
188                     return ScilabClassLoader.loadJavaClass(manager.className, true);
189                 } else {
190                     return -1;
191                 }
192             }
193         } else {
194             if (!isECJ) {
195                 error = getCompilerErrors(diagnostics);
196             }
197
198             throw new ScilabJavaException(error);
199         }
200     }
201
202     /**
203      * Returns the compilation errors from the diagnostics
204      * @param diagnostics the diagnostics returned by the compiler
205      * @return a string containing the errors
206      */
207     public static String getCompilerErrors(DiagnosticCollector<JavaFileObject> diagnostics) {
208         StringBuffer buffer = new StringBuffer();
209         int cpt = 1;
210         buffer.append("----------\n");
211         for (Diagnostic <? extends JavaFileObject > d : diagnostics.getDiagnostics()) {
212             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");
213
214             Reader reader = null;
215             try {
216                 reader = d.getSource().openReader(true);
217                 reader.skip(d.getStartPosition());
218                 char[] data = new char[(int) (d.getEndPosition() - d.getStartPosition() + 1)];
219                 reader.read(data);
220                 buffer.append("        ").append(data).append('\n');
221                 Arrays.fill(data, '^');
222                 buffer.append("        ").append(data).append('\n');
223             } catch (IOException e) {
224
225             } finally {
226                 if (reader != null) {
227                     try {
228                         reader.close();
229                     } catch (IOException e) { }
230                 }
231             }
232
233             buffer.append(d.getMessage(Locale.getDefault())).append('\n');
234         }
235
236         buffer.append("----------\n");
237         return buffer.toString();
238     }
239
240     /**
241      * Get the current classpath with the correct separator (':' under Unix and ';' under Windows)
242      * @return the classpath
243      */
244     public static String getClasspath() {
245         URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
246         URL[] urls = loader.getURLs();
247         StringBuffer buffer = new StringBuffer();
248
249         for (URL url : urls) {
250             buffer.append(url.getPath()).append(File.pathSeparatorChar);
251         }
252         buffer.append(".");
253
254         return buffer.toString();
255     }
256
257     /**
258      * Get the files in the classpath
259      * @return the files
260      */
261     public static List<File> getClasspathFiles() {
262         URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
263         URL[] urls = loader.getURLs();
264         List<File> files = new ArrayList<File>(urls.length);
265
266         for (URL url : urls) {
267             try {
268                 files.add(new File(url.toURI()));
269             } catch (Exception e) { }
270         }
271
272         return files;
273     }
274
275     /**
276      * Add a class in the classpath
277      * @param url the class url
278      */
279     public static void addURLToClassPath(URL url) {
280         URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
281         try {
282             final Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {URL.class});
283             method.setAccessible(true);
284             method.invoke(sysloader , new Object[] {url});
285         } catch (NoSuchMethodException e) {
286             System.err.println("Error: Cannot find the declared method: " + e.getLocalizedMessage());
287         } catch (IllegalAccessException e) {
288             System.err.println("Error: Illegal access: " + e.getLocalizedMessage());
289         } catch (InvocationTargetException e) {
290             System.err.println("Error: Could not invocate target: " + e.getLocalizedMessage());
291         }
292     }
293
294     /**
295      * Inner class to handle String as File
296      */
297     private static class SourceString extends SimpleJavaFileObject {
298
299         private final String code;
300
301         private SourceString(String className, String[] code) {
302             super(new File(BINPATH + "/" + className.replace('.', '/') + Kind.SOURCE.extension).toURI(), Kind.SOURCE);
303
304             StringBuffer buf = new StringBuffer();
305             for (String str : code) {
306                 buf.append(str);
307                 buf.append("\n");
308             }
309             this.code = buf.toString();
310         }
311
312         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
313             return code;
314         }
315     }
316
317     /**
318      * Inner class to handle String as File
319      */
320     private static class SourceFile extends SimpleJavaFileObject {
321
322         final File f;
323
324         private SourceFile(File f) {
325             super(f.toURI(), Kind.SOURCE);
326             this.f = f;
327         }
328
329         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
330             try {
331                 FileReader reader = new FileReader(f);
332                 char[] buffer = new char[1024];
333                 StringBuffer sb = new StringBuffer();
334                 int r;
335
336                 while ((r = reader.read(buffer, 0, 1024)) != -1) {
337                     sb.append(buffer, 0, r);
338                 }
339
340                 reader.close();
341
342                 return sb;
343             } catch (Exception e) {
344                 return null;
345             }
346         }
347     }
348
349     private static class ClassFileManager extends ForwardingJavaFileManager {
350
351         String className;
352
353         public ClassFileManager(StandardJavaFileManager standardManager) {
354             super(standardManager);
355         }
356
357         @Override
358         public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
359             if (sibling instanceof SourceString && className != null && className.indexOf('$') == -1) {
360                 this.className = className.replace('/', '.');
361             }
362
363             if (ScilabJavaObject.debug) {
364                 ScilabJavaObject.logger.log(Level.INFO, "Compilation of class \'" + className + "\'");
365             }
366
367             return super.getJavaFileForOutput(location, className, kind, sibling);
368         }
369     }
370 }