Skip to main content

How to compile on the fly?

Posted by malenkov on December 17, 2008 at 12:00 AM PST

A PropertyEditor interface provides support for GUIs to enable editing a property value of a given type. The interface supports a variety of ways to display and update property values. One of these ways is to employ the string representation of a Java code fragment that can be obtained by getJavaInitializationString, the method all standard property editors implement. To test this feature, one could generate a source code with a method that returns a required string, compile the code, run the class, and verify the value. This is quite easy to do with the Java 6 Compiler API.

To start, let's define the class that will contain the source code to compile. To do so, override the getCharContent method of the SimpleJavaFileObject class:

class Source extends SimpleJavaFileObject {
    private final String content;

    Source(String name, Kind kind, String content) {
        super(URI.create("memo:///" + name.replace('.', '/') + kind.extension), kind);
        this.content = content;
    }

    @Override
    public CharSequence getCharContent(boolean ignore) {
        return this.content;
    }
}

Further, create a class to obtain the compiled binary code. Override the openOutputStream method of the SimpleJavaFileObject class and add the method to return a byte array written in the stream by the compiler:

class Output extends SimpleJavaFileObject {
    private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

    Output(String name, Kind kind) {
        super(URI.create("memo:///" + name.replace('.', '/') + kind.extension), kind);
    }

    byte[] toByteArray() {
        return this.baos.toByteArray();
    }

    @Override
    public ByteArrayOutputStream openOutputStream() {
        return this.baos;
    }
}

Now create a class the compiler will use to manage files. The Output object is created and stored for each compiled class. Compiler is using this object to write the binary code.

class MemoryFileManager extends ForwardingJavaFileManager {
    private final Map map = new HashMap();

    MemoryFileManager(JavaCompiler compiler) {
        super(compiler.getStandardFileManager(null, null, null));
    }

    @Override
    public Output getJavaFileForOutput
            (Location location, String name, Kind kind, FileObject source) {
        Output mc = new Output(name, kind);
        this.map.put(name, mc);
        return mc;
    }
}

Finally, create a custom class loader, that compiles all sources when initiated (in its constructor). Later the loader creates a Class object from the binary code when the class is called. Two following String variables are aimed to define the source code: the class full name (with the package) and the valid file content. If you need to compile several files at once, use the Map object, that contains the file content by the class full name. Below example has an obligatory set of compiling commands. Use the Compiler API for other options, for example, additional diagnostics or compilation output:

class MemoryClassLoader extends ClassLoader {
    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    private final MemoryFileManager manager = new MemoryFileManager(this.compiler);

    public MemoryClassLoader(String classname, String filecontent) {
        this(Collections.singletonMap(classname, filecontent));
    }

    public MemoryClassLoader(Map map) {
        List list = new ArrayList();
        for (Map.Entry entry : map.entrySet()) {
            list.add(new Source(entry.getKey(), Kind.SOURCE, entry.getValue()));
        }
        this.compiler.getTask(null, this.manager, null, null, null, list).call();
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        synchronized (this.manager) {
            Output mc = this.manager.map.remove(name);
            if (mc != null) {
                byte[] array = mc.toByteArray();
                return defineClass(name, array, 0, array.length);
            }
        }
        return super.findClass(name);
    }
}

Let's take a look at the example that calculates sin 30°. Use the Reflection API to invoke the method of the compiled class.

private static final String CLASS = "Test";
private static final String METHOD = "execute";
private static final String EXPRESSION = "Math.cos(Math.PI/6)";
private static final String CONTENT   
        = "public class " + CLASS + " {"
        + "    public static Object " + METHOD + "() {"
        + "        return " + EXPRESSION + ";"
        + "    }"
        + "}";

public static void main(String[] args) throws Exception {
    MemoryClassLoader mcl = new MemoryClassLoader(CLASS, CONTENT);
    System.out.println(mcl.loadClass(CLASS).getMethod(METHOD).invoke(null));
}

The source file of the example is available.

Related Topics >>

Comments

Using this example in a tomcat webservice

Hi,
I've tried using this code in a stand alone application for a Java file generated on-the-fly and it works beautifully.

I've now incorporated it into a web service and I get a
java.lang.ClassNotFoundException: DeliverMessage
at java.lang.ClassLoader.findClass(ClassLoader.java:359)
at uk.ac.manchester.cs.snee.sncb.MemoryClassLoader.findClass(MemoryClassLoader.java:87)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
...

when I try to invoke

MemoryClassLoader mcl = new MemoryClassLoader("DeliverMessage", deliverMessageJavaClassContent);
Class msgClass = mcl.loadClass("DeliverMessage");

Is there any reason why this might not work within a Web Service? Is there a suitable workaround I could try?

Thanks,
Ixent

I realized that a custom

I realized that a custom class loader could not be initialized if security manager is set and does not have necessary permissions:

Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createClassLoader")

at java.security.AccessControlContext.checkPermission(AccessControlContext.java:345)

at java.security.AccessController.checkPermission(AccessController.java:555)

at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)

at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:611)

at java.lang.ClassLoader.(ClassLoader.java:294)

at MemoryClassLoader.(MemoryClassLoader.java:69)

at MemoryClassLoader.(MemoryClassLoader.java:66)

at MemoryClassLoader.main(MemoryClassLoader.java:58)