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