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