JavaCompiler called from JPMS module

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

JavaCompiler called from JPMS module

Alex Sviridov

Hi all,
 
I am working with H2 java database that allows to create triggers from java source code.
To compile the trigger the following code is used:
 
    JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
    String fullClassName = packageName + "." + className;
    StringWriter writer = new StringWriter();
    try (JavaFileManager fileManager = new
            ClassFileManager(JAVA_COMPILER
                .getStandardFileManager(null, null, null))) {
        ArrayList<JavaFileObject> compilationUnits = new ArrayList<>();
        compilationUnits.add(new StringJavaFileObject(fullClassName, source));
        // cannot concurrently compile
        final boolean ok;
        synchronized (JAVA_COMPILER) {
            ok = JAVA_COMPILER.getTask(writer, fileManager, null, null,
                    null, compilationUnits).call();
        }
        String output = writer.toString();
        handleSyntaxError(output, (ok? 0: 1));
        return fileManager.getClassLoader(null).loadClass(fullClassName);
    } catch (ClassNotFoundException | IOException e) {
        throw DbException.convert(e);
    }
 
Trigger `fullClassName` = `org.h2.dynamic.trigger.SOME_TRIGGER`.

And the trigger `source` =
    package org.h2.dynamic.trigger;
    import java.util.*;
    import java.math.*;
    import java.sql.*;
    public class SOME_TRIGGER {
        public static org.h2.api.Trigger create() {
    
        return new org.h2.api.Trigger() {
            
            @Override
            public void init(Connection conn, String schemaName, String triggerName,
                    String tableName, boolean before, int type) throws SQLException { }
            
            @Override
            public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { }
    
            @Override
            public void close() throws SQLException { }
    
            @Override
            public void remove() throws SQLException { }
    
        };
    }
    }
 
 
When H2 is on classpath I don't have any problems - trigger is compiled. However, when H2 is on
module path (H2 has in manifest Automatic-Module-Name: com.h2database) on some of child layers
(not boot layer) I get the following `output`:
 
    /org/h2/dynamic/trigger/SOME_TRIGGER.java:6: error: package org.h2.api does not exist
        public static org.h2.api.Trigger create() {
                                ^
    1 error
 
As I understand the problem is with classloader and JavaCompiler. Could anyone say how it can be fixed?
 
Best regards, Alex
Reply | Threaded
Open this post in threaded view
|

Re: JavaCompiler called from JPMS module

Alan Bateman
On 16/05/2020 17:53, Alex Sviridov wrote:

> :
>  
>  
> When H2 is on classpath I don't have any problems - trigger is compiled. However, when H2 is on
> module path (H2 has in manifest Automatic-Module-Name: com.h2database) on some of child layers
> (not boot layer) I get the following `output`:
>  
>      /org/h2/dynamic/trigger/SOME_TRIGGER.java:6: error: package org.h2.api does not exist
>          public static org.h2.api.Trigger create() {
>                                  ^
>      1 error
>  
> As I understand the problem is with classloader and JavaCompiler. Could anyone say how it can be fixed?
>
You can't compile against modules in module layers. Instead you'll need
to specify the module path where the compiler can find the JAR file with
module com.h2database. This snippet might help:

         Path src = ...       // path to the source code for SOME_TRIGGER
         Path dir = ...       // directory that contains com.h2database
         Path output = ... // output directory

         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
         StandardJavaFileManager fm =
compiler.getStandardFileManager(null, null, null);
         fm.setLocationFromPaths(StandardLocation.MODULE_PATH,
List.of(dir));
         fm.setLocationFromPaths(StandardLocation.CLASS_OUTPUT,
List.of(output));
         Iterable<? extends JavaFileObject> paths =
fm.getJavaFileObjects(src);
         JavaCompiler.CompilationTask task = compiler.getTask(null, fm,
null, null, null, paths);
         task.addModules(List.of("com.h2database"));
         task.call();

The call to addModules is needed because SOME_TRIGGER isn't being
compiled as a module so there is nobody to require com.h2database.

-Alan