EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.util]

COVERAGE SUMMARY FOR SOURCE FILE [SourceCompiler.java]

nameclass, %method, %block, %line, %
SourceCompiler.java100% (6/6)89%  (24/27)77%  (593/770)75%  (119.2/159)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SourceCompiler100% (1/1)80%  (12/15)71%  (411/581)70%  (87.2/125)
copyInThread (InputStream, OutputStream): void 0%   (0/1)0%   (0/8)0%   (0/2)
exec (String []): int 0%   (0/1)0%   (0/49)0%   (0/13)
javacProcess (File): void 0%   (0/1)0%   (0/38)0%   (0/2)
throwSyntaxError (String): void 100% (1/1)50%  (9/18)60%  (3/5)
getClass (String): Class 100% (1/1)67%  (29/43)60%  (6/10)
<static initializer> 100% (1/1)73%  (16/22)67%  (8/12)
getCompleteSourceCode (String, String, String): String 100% (1/1)80%  (47/59)85%  (11/13)
isGroovySource (String): boolean 100% (1/1)83%  (10/12)83%  (0.8/1)
javacCompile (String, String, String): byte [] 100% (1/1)86%  (94/109)85%  (19.5/23)
javacSun (File): void 100% (1/1)90%  (79/88)85%  (11.9/14)
getMethod (String): Method 100% (1/1)92%  (44/48)88%  (7.9/9)
javaxToolsJavac (String, String, String): Class 100% (1/1)93%  (57/61)82%  (9/11)
SourceCompiler (): void 100% (1/1)100% (12/12)100% (5/5)
setJavaSystemCompiler (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setSource (String, String): void 100% (1/1)100% (10/10)100% (3/3)
     
class SourceCompiler$1100% (1/1)100% (2/2)93%  (88/95)89%  (17/19)
findClass (String): Class 100% (1/1)92%  (81/88)89%  (16/18)
SourceCompiler$1 (SourceCompiler, ClassLoader): void 100% (1/1)100% (7/7)100% (1/1)
     
class SourceCompiler$ClassFileManager100% (1/1)100% (3/3)100% (19/19)100% (5/5)
SourceCompiler$ClassFileManager (StandardJavaFileManager): void 100% (1/1)100% (4/4)100% (2/2)
getClassLoader (JavaFileManager$Location): ClassLoader 100% (1/1)100% (5/5)100% (1/1)
getJavaFileForOutput (JavaFileManager$Location, String, JavaFileObject$Kind, ... 100% (1/1)100% (10/10)100% (2/2)
     
class SourceCompiler$ClassFileManager$1100% (1/1)100% (2/2)100% (19/19)100% (3/3)
SourceCompiler$ClassFileManager$1 (SourceCompiler$ClassFileManager): void 100% (1/1)100% (6/6)100% (1/1)
findClass (String): Class 100% (1/1)100% (13/13)100% (2/2)
     
class SourceCompiler$JavaClassObject100% (1/1)100% (3/3)100% (31/31)100% (5/5)
SourceCompiler$JavaClassObject (String, JavaFileObject$Kind): void 100% (1/1)100% (24/24)100% (3/3)
getBytes (): byte [] 100% (1/1)100% (4/4)100% (1/1)
openOutputStream (): OutputStream 100% (1/1)100% (3/3)100% (1/1)
     
class SourceCompiler$StringJavaFileObject100% (1/1)100% (2/2)100% (25/25)100% (4/4)
SourceCompiler$StringJavaFileObject (String, String): void 100% (1/1)100% (22/22)100% (3/3)
getCharContent (boolean): CharSequence 100% (1/1)100% (3/3)100% (1/1)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.util;
7 
8import java.io.ByteArrayOutputStream;
9import java.io.DataInputStream;
10import java.io.File;
11import java.io.FileInputStream;
12import java.io.IOException;
13import java.io.InputStream;
14import java.io.OutputStream;
15import java.io.PrintStream;
16import java.io.StringWriter;
17import java.io.Writer;
18import java.lang.reflect.Array;
19import java.lang.reflect.Method;
20import java.lang.reflect.Modifier;
21import java.net.URI;
22import java.security.SecureClassLoader;
23import java.util.ArrayList;
24import java.util.HashMap;
25 
26import org.h2.api.ErrorCode;
27import org.h2.engine.Constants;
28import org.h2.engine.SysProperties;
29import org.h2.message.DbException;
30import org.h2.store.fs.FileUtils;
31 
32import javax.tools.FileObject;
33import javax.tools.ForwardingJavaFileManager;
34import javax.tools.JavaCompiler;
35import javax.tools.JavaFileManager;
36import javax.tools.JavaFileObject;
37import javax.tools.JavaFileObject.Kind;
38import javax.tools.SimpleJavaFileObject;
39import javax.tools.StandardJavaFileManager;
40import javax.tools.ToolProvider;
41 
42/**
43 * This class allows to convert source code to a class. It uses one class loader
44 * per class.
45 */
46public class SourceCompiler {
47 
48    /**
49     * The "com.sun.tools.javac.Main" (if available).
50     */
51    static final JavaCompiler JAVA_COMPILER;
52 
53    private static final Class<?> JAVAC_SUN;
54 
55    private static final String COMPILE_DIR =
56            Utils.getProperty("java.io.tmpdir", ".");
57 
58    /**
59     * The class name to source code map.
60     */
61    final HashMap<String, String> sources = New.hashMap();
62 
63    /**
64     * The class name to byte code map.
65     */
66    final HashMap<String, Class<?>> compiled = New.hashMap();
67 
68    /**
69     * Whether to use the ToolProvider.getSystemJavaCompiler().
70     */
71    boolean useJavaSystemCompiler = SysProperties.JAVA_SYSTEM_COMPILER;
72 
73    static {
74        JavaCompiler c;
75        try {
76            c = ToolProvider.getSystemJavaCompiler();
77        } catch (Exception e) {
78            // ignore
79            c = null;
80        }
81        JAVA_COMPILER = c;
82        Class<?> clazz;
83        try {
84            clazz = Class.forName("com.sun.tools.javac.Main");
85        } catch (Exception e) {
86            clazz = null;
87        }
88        JAVAC_SUN = clazz;
89    }
90 
91    /**
92     * Set the source code for the specified class.
93     * This will reset all compiled classes.
94     *
95     * @param className the class name
96     * @param source the source code
97     */
98    public void setSource(String className, String source) {
99        sources.put(className, source);
100        compiled.clear();
101    }
102 
103    /**
104     * Enable or disable the usage of the Java system compiler.
105     *
106     * @param enabled true to enable
107     */
108    public void setJavaSystemCompiler(boolean enabled) {
109        this.useJavaSystemCompiler = enabled;
110    }
111 
112    /**
113     * Get the class object for the given name.
114     *
115     * @param packageAndClassName the class name
116     * @return the class
117     */
118    public Class<?> getClass(String packageAndClassName)
119            throws ClassNotFoundException {
120 
121        Class<?> compiledClass = compiled.get(packageAndClassName);
122        if (compiledClass != null) {
123            return compiledClass;
124        }
125        String source = sources.get(packageAndClassName);
126        if (isGroovySource(source)) {
127            Class<?> clazz = GroovyCompiler.parseClass(source, packageAndClassName);
128            compiled.put(packageAndClassName, clazz);
129            return clazz;
130        }
131 
132        ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
133 
134            @Override
135            public Class<?> findClass(String name) throws ClassNotFoundException {
136                Class<?> classInstance = compiled.get(name);
137                if (classInstance == null) {
138                    String source = sources.get(name);
139                    String packageName = null;
140                    int idx = name.lastIndexOf('.');
141                    String className;
142                    if (idx >= 0) {
143                        packageName = name.substring(0, idx);
144                        className = name.substring(idx + 1);
145                    } else {
146                        className = name;
147                    }
148                    String s = getCompleteSourceCode(packageName, className, source);
149                    if (JAVA_COMPILER != null && useJavaSystemCompiler) {
150                        classInstance = javaxToolsJavac(packageName, className, s);
151                    } else {
152                        byte[] data = javacCompile(packageName, className, s);
153                        if (data == null) {
154                            classInstance = findSystemClass(name);
155                        } else {
156                            classInstance = defineClass(name, data, 0, data.length);
157                        }
158                    }
159                    compiled.put(name, classInstance);
160                }
161                return classInstance;
162            }
163        };
164        return classLoader.loadClass(packageAndClassName);
165    }
166 
167    private static boolean isGroovySource(String source) {
168        return source.startsWith("//groovy") || source.startsWith("@groovy");
169    }
170 
171    /**
172     * Get the first public static method of the given class.
173     *
174     * @param className the class name
175     * @return the method name
176     */
177    public Method getMethod(String className) throws ClassNotFoundException {
178        Class<?> clazz = getClass(className);
179        Method[] methods = clazz.getDeclaredMethods();
180        for (Method m : methods) {
181            int modifiers = m.getModifiers();
182            if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) {
183                String name = m.getName();
184                if (!name.startsWith("_") && !m.getName().equals("main")) {
185                    return m;
186                }
187            }
188        }
189        return null;
190    }
191 
192    /**
193     * Compile the given class. This method tries to use the class
194     * "com.sun.tools.javac.Main" if available. If not, it tries to run "javac"
195     * in a separate process.
196     *
197     * @param packageName the package name
198     * @param className the class name
199     * @param source the source code
200     * @return the class file
201     */
202    byte[] javacCompile(String packageName, String className, String source) {
203        File dir = new File(COMPILE_DIR);
204        if (packageName != null) {
205            dir = new File(dir, packageName.replace('.', '/'));
206            FileUtils.createDirectories(dir.getAbsolutePath());
207        }
208        File javaFile = new File(dir, className + ".java");
209        File classFile = new File(dir, className + ".class");
210        try {
211            OutputStream f = FileUtils.newOutputStream(javaFile.getAbsolutePath(), false);
212            Writer out = IOUtils.getBufferedWriter(f);
213            classFile.delete();
214            out.write(source);
215            out.close();
216            if (JAVAC_SUN != null) {
217                javacSun(javaFile);
218            } else {
219                javacProcess(javaFile);
220            }
221            byte[] data = new byte[(int) classFile.length()];
222            DataInputStream in = new DataInputStream(new FileInputStream(classFile));
223            in.readFully(data);
224            in.close();
225            return data;
226        } catch (Exception e) {
227            throw DbException.convert(e);
228        } finally {
229            javaFile.delete();
230            classFile.delete();
231        }
232    }
233 
234    /**
235     * Get the complete source code (including package name, imports, and so
236     * on).
237     *
238     * @param packageName the package name
239     * @param className the class name
240     * @param source the (possibly shortened) source code
241     * @return the full source code
242     */
243    static String getCompleteSourceCode(String packageName, String className,
244            String source) {
245        if (source.startsWith("package ")) {
246            return source;
247        }
248        StringBuilder buff = new StringBuilder();
249        if (packageName != null) {
250            buff.append("package ").append(packageName).append(";\n");
251        }
252        int endImport = source.indexOf("@CODE");
253        String importCode =
254            "import java.util.*;\n" +
255            "import java.math.*;\n" +
256            "import java.sql.*;\n";
257        if (endImport >= 0) {
258            importCode = source.substring(0, endImport);
259            source = source.substring("@CODE".length() + endImport);
260        }
261        buff.append(importCode);
262        buff.append("public class ").append(className).append(
263                " {\n" +
264                "    public static ").append(source).append("\n" +
265                "}\n");
266        return buff.toString();
267    }
268 
269    /**
270     * Compile using the standard java compiler.
271     *
272     * @param packageName the package name
273     * @param className the class name
274     * @param source the source code
275     * @return the class
276     */
277    Class<?> javaxToolsJavac(String packageName, String className, String source) {
278        String fullClassName = packageName + "." + className;
279        StringWriter writer = new StringWriter();
280        JavaFileManager fileManager = new
281                ClassFileManager(JAVA_COMPILER
282                    .getStandardFileManager(null, null, null));
283        ArrayList<JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>();
284        compilationUnits.add(new StringJavaFileObject(fullClassName, source));
285        JAVA_COMPILER.getTask(writer, fileManager, null, null,
286                null, compilationUnits).call();
287        String err = writer.toString();
288        throwSyntaxError(err);
289        try {
290            return fileManager.getClassLoader(null).loadClass(fullClassName);
291        } catch (ClassNotFoundException e) {
292            throw DbException.convert(e);
293        }
294    }
295 
296    private static void javacProcess(File javaFile) {
297        exec("javac",
298                "-sourcepath", COMPILE_DIR,
299                "-d", COMPILE_DIR,
300                "-encoding", "UTF-8",
301                javaFile.getAbsolutePath());
302    }
303 
304    private static int exec(String... args) {
305        ByteArrayOutputStream buff = new ByteArrayOutputStream();
306        try {
307            ProcessBuilder builder = new ProcessBuilder();
308            // The javac executable allows some of it's flags
309            // to be smuggled in via environment variables.
310            // But if it sees those flags, it will write out a message
311            // to stderr, which messes up our parsing of the output.
312            builder.environment().remove("JAVA_TOOL_OPTIONS");
313            builder.command(args);
314 
315            Process p = builder.start();
316            copyInThread(p.getInputStream(), buff);
317            copyInThread(p.getErrorStream(), buff);
318            p.waitFor();
319            String err = new String(buff.toByteArray(), Constants.UTF8);
320            throwSyntaxError(err);
321            return p.exitValue();
322        } catch (Exception e) {
323            throw DbException.convert(e);
324        }
325    }
326 
327    private static void copyInThread(final InputStream in, final OutputStream out) {
328        new Task() {
329            @Override
330            public void call() throws IOException {
331                IOUtils.copy(in, out);
332            }
333        }.execute();
334    }
335 
336    private static void javacSun(File javaFile) {
337        PrintStream old = System.err;
338        ByteArrayOutputStream buff = new ByteArrayOutputStream();
339        PrintStream temp = new PrintStream(buff);
340        try {
341            System.setErr(temp);
342            Method compile;
343            compile = JAVAC_SUN.getMethod("compile", String[].class);
344            Object javac = JAVAC_SUN.newInstance();
345            compile.invoke(javac, (Object) new String[] {
346                    "-sourcepath", COMPILE_DIR,
347                    "-d", COMPILE_DIR,
348                    "-encoding", "UTF-8",
349                    javaFile.getAbsolutePath() });
350            String err = new String(buff.toByteArray(), Constants.UTF8);
351            throwSyntaxError(err);
352        } catch (Exception e) {
353            throw DbException.convert(e);
354        } finally {
355            System.setErr(old);
356        }
357    }
358 
359    private static void throwSyntaxError(String err) {
360        if (err.startsWith("Note:")) {
361            // unchecked or unsafe operations - just a warning
362        } else if (err.length() > 0) {
363            err = StringUtils.replaceAll(err, COMPILE_DIR, "");
364            throw DbException.get(ErrorCode.SYNTAX_ERROR_1, err);
365        }
366    }
367 
368 
369    /**
370     * Access the Groovy compiler using reflection, so that we do not gain a
371     * compile-time dependency unnecessarily.
372     */
373    private static final class GroovyCompiler {
374 
375        private static final Object LOADER;
376        private static final Throwable INIT_FAIL_EXCEPTION;
377 
378        static {
379            Object loader = null;
380            Throwable initFailException = null;
381            try {
382                // Create an instance of ImportCustomizer
383                Class<?> importCustomizerClass = Class.forName(
384                        "org.codehaus.groovy.control.customizers.ImportCustomizer");
385                Object importCustomizer = Utils.newInstance(
386                        "org.codehaus.groovy.control.customizers.ImportCustomizer");
387                // Call the method ImportCustomizer.addImports(String[])
388                String[] importsArray = new String[] {
389                        "java.sql.Connection",
390                        "java.sql.Types",
391                        "java.sql.ResultSet",
392                        "groovy.sql.Sql",
393                        "org.h2.tools.SimpleResultSet"
394                };
395                Utils.callMethod(importCustomizer, "addImports", new Object[] { importsArray });
396 
397                // Call the method
398                // CompilerConfiguration.addCompilationCustomizers(
399                //         ImportCustomizer...)
400                Object importCustomizerArray = Array.newInstance(importCustomizerClass, 1);
401                Array.set(importCustomizerArray, 0, importCustomizer);
402                Object configuration = Utils.newInstance(
403                        "org.codehaus.groovy.control.CompilerConfiguration");
404                Utils.callMethod(configuration,
405                        "addCompilationCustomizers", new Object[] { importCustomizerArray });
406 
407                ClassLoader parent = GroovyCompiler.class.getClassLoader();
408                loader = Utils.newInstance(
409                        "groovy.lang.GroovyClassLoader", parent, configuration);
410            } catch (Exception ex) {
411                initFailException = ex;
412            }
413            LOADER = loader;
414            INIT_FAIL_EXCEPTION = initFailException;
415        }
416 
417        public static Class<?> parseClass(String source,
418                String packageAndClassName) {
419            if (LOADER == null) {
420                throw new RuntimeException(
421                        "Compile fail: no Groovy jar in the classpath", INIT_FAIL_EXCEPTION);
422            }
423            try {
424                Object codeSource = Utils.newInstance("groovy.lang.GroovyCodeSource",
425                        source, packageAndClassName + ".groovy", "UTF-8");
426                Utils.callMethod(codeSource, "setCachable", false);
427                Class<?> clazz = (Class<?>) Utils.callMethod(
428                        LOADER, "parseClass", codeSource);
429                return clazz;
430            } catch (Exception e) {
431                throw new RuntimeException(e);
432            }
433        }
434    }
435 
436    /**
437     * An in-memory java source file object.
438     */
439    static class StringJavaFileObject extends SimpleJavaFileObject {
440 
441        private final String sourceCode;
442 
443        public StringJavaFileObject(String className, String sourceCode) {
444            super(URI.create("string:///" + className.replace('.', '/')
445                + Kind.SOURCE.extension), Kind.SOURCE);
446            this.sourceCode = sourceCode;
447        }
448 
449        @Override
450        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
451            return sourceCode;
452        }
453 
454    }
455 
456    /**
457     * An in-memory java class object.
458     */
459    static class JavaClassObject extends SimpleJavaFileObject {
460 
461        private final ByteArrayOutputStream out = new ByteArrayOutputStream();
462 
463        public JavaClassObject(String name, Kind kind) {
464            super(URI.create("string:///" + name.replace('.', '/')
465                + kind.extension), kind);
466        }
467 
468        public byte[] getBytes() {
469            return out.toByteArray();
470        }
471 
472        @Override
473        public OutputStream openOutputStream() throws IOException {
474            return out;
475        }
476    }
477 
478    /**
479     * An in-memory class file manager.
480     */
481    static class ClassFileManager extends
482            ForwardingJavaFileManager<StandardJavaFileManager> {
483 
484        /**
485         * The class (only one class is kept).
486         */
487        JavaClassObject classObject;
488 
489        public ClassFileManager(StandardJavaFileManager standardManager) {
490            super(standardManager);
491        }
492 
493        @Override
494        public ClassLoader getClassLoader(Location location) {
495            return new SecureClassLoader() {
496                @Override
497                protected Class<?> findClass(String name)
498                        throws ClassNotFoundException {
499                    byte[] bytes = classObject.getBytes();
500                    return super.defineClass(name, bytes, 0,
501                            bytes.length);
502                }
503            };
504        }
505 
506        @Override
507        public JavaFileObject getJavaFileForOutput(Location location,
508                String className, Kind kind, FileObject sibling) throws IOException {
509            classObject = new JavaClassObject(className, kind);
510            return classObject;
511        }
512    }
513 
514}

[all classes][org.h2.util]
EMMA 2.0.5312 (C) Vladimir Roubtsov