Add Compiler
This commit is contained in:
parent
8ac701ea4a
commit
1b2f248574
@ -0,0 +1,175 @@
|
||||
package com.moparisthebest.classgen;
|
||||
|
||||
|
||||
import javax.tools.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This let's you take compile and then run Java source code at runtime, this class only allows you to compile 1 class
|
||||
* at a time.
|
||||
* <p>
|
||||
* This is not thread safe, though it could be extended and made thread safe, or locked around.
|
||||
*
|
||||
* @author moparisthebest
|
||||
* @see MultiCompiler for compiling multiple classes at once
|
||||
* <p>
|
||||
* The original idea was taken from:
|
||||
* http://mindprod.com/jgloss/javacompiler.html#SAMPLECODE
|
||||
*/
|
||||
public class Compiler {
|
||||
|
||||
private final JavaCompiler compiler;
|
||||
private final MemoryJavaFileManager mjfm;
|
||||
//private final ClassLoader classLoader;
|
||||
private final List<JavaFileObject> singleton = Arrays.asList(new JavaFileObject[1]);
|
||||
|
||||
public Compiler() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
protected Compiler(final boolean multi) {
|
||||
compiler = ToolProvider.getSystemJavaCompiler();
|
||||
if (compiler == null)
|
||||
throw new RuntimeException("tools.jar needs to be on classpath to compile code at runtime");
|
||||
final JavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
|
||||
mjfm = multi ? new MultiMemoryJavaFileManager(fileManager) : new SingleMemoryJavaFileManager(fileManager);
|
||||
//classLoader = new MemoryClassLoader(mjfm);
|
||||
}
|
||||
|
||||
protected ClassLoader compile(final Iterable<? extends JavaFileObject> source) {
|
||||
//final MemoryJavaFileManager mjfm = new MemoryJavaFileManager(compiler.getStandardFileManager(null, null, null));
|
||||
final JavaCompiler.CompilationTask task = compiler.getTask(null, mjfm, null, null, null, source);
|
||||
return task.call() ?
|
||||
new MemoryClassLoader(mjfm)
|
||||
//classLoader
|
||||
: null;
|
||||
}
|
||||
|
||||
protected ClassLoader compile(final JavaFileObject... source) {
|
||||
return compile(Arrays.asList(source));
|
||||
}
|
||||
|
||||
public ClassLoader compile(final JavaFileObject source) {
|
||||
singleton.set(0, source);
|
||||
return compile(singleton);
|
||||
}
|
||||
|
||||
protected <T> T compile(final String className, final Iterable<? extends JavaFileObject> source) {
|
||||
// compile item
|
||||
final ClassLoader cl = compile(source);
|
||||
if (cl == null)
|
||||
throw new RuntimeException("Error compiling class, aborting...");
|
||||
return instantiate(cl, className);
|
||||
}
|
||||
|
||||
protected <T> T compile(final String className, final JavaFileObject... source) {
|
||||
return compile(className, Arrays.asList(source));
|
||||
}
|
||||
|
||||
public <T> T compile(final String className, final JavaFileObject source) {
|
||||
singleton.set(0, source);
|
||||
return compile(className, singleton);
|
||||
}
|
||||
|
||||
public <T> T compile(final String className, final CharSequence code) {
|
||||
return compile(className, new StringJavaFileObject(className, code));
|
||||
}
|
||||
|
||||
public <T> T instantiate(final ClassLoader cl, final String className) {
|
||||
try {
|
||||
// Load class and create an instance.
|
||||
final Class<?> calcClass = cl.loadClass(className);
|
||||
@SuppressWarnings("unchecked") final T ret = (T) calcClass.newInstance();
|
||||
return ret;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error finding or instantiating class, exiting...", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MemoryJavaFileObject extends ForwardingJavaFileObject<JavaFileObject> {
|
||||
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
MemoryJavaFileObject(JavaFileObject fileObject) {
|
||||
super(fileObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return baos;
|
||||
}
|
||||
}
|
||||
|
||||
interface MemoryJavaFileManager extends JavaFileManager {
|
||||
MemoryJavaFileObject getMemoryJavaFileObject(final String name);
|
||||
}
|
||||
|
||||
class SingleMemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> implements MemoryJavaFileManager {
|
||||
|
||||
private MemoryJavaFileObject classes = null;
|
||||
|
||||
SingleMemoryJavaFileManager(JavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryJavaFileObject getMemoryJavaFileObject(final String name) {
|
||||
final MemoryJavaFileObject ret = classes;
|
||||
classes = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
|
||||
final MemoryJavaFileObject jfo = new MemoryJavaFileObject(super.getJavaFileForOutput(location, className, kind, sibling));
|
||||
//System.out.printf("MemoryJavaFileManager.getJavaFileForOutput(%s, %s, %s, %s): %s\n", location, className, kind, sibling, jfo);
|
||||
classes = jfo;
|
||||
return jfo;
|
||||
}
|
||||
}
|
||||
|
||||
class MultiMemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> implements MemoryJavaFileManager {
|
||||
|
||||
private Map<String, MemoryJavaFileObject> classes = new HashMap<String, MemoryJavaFileObject>();
|
||||
|
||||
MultiMemoryJavaFileManager(JavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryJavaFileObject getMemoryJavaFileObject(final String name) {
|
||||
return classes.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
|
||||
final MemoryJavaFileObject jfo = new MemoryJavaFileObject(super.getJavaFileForOutput(location, className, kind, sibling));
|
||||
//System.out.printf("MemoryJavaFileManager.getJavaFileForOutput(%s, %s, %s, %s): %s\n", location, className, kind, sibling, jfo);
|
||||
classes.put(className, jfo);
|
||||
return jfo;
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryClassLoader extends ClassLoader {
|
||||
final MemoryJavaFileManager jfm;
|
||||
|
||||
MemoryClassLoader(MemoryJavaFileManager jfm) {
|
||||
this.jfm = jfm;
|
||||
}
|
||||
|
||||
public Class findClass(String name) throws ClassNotFoundException {
|
||||
final MemoryJavaFileObject jfo = jfm.getMemoryJavaFileObject(name);
|
||||
if (jfo == null)
|
||||
throw new ClassNotFoundException("Class '" + name + "' cannot be found in the MemoryJavaFileManager");
|
||||
final byte[] b = jfo.baos.toByteArray();
|
||||
return defineClass(name, b, 0, b.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.moparisthebest.classgen;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* This lets you compile multiple source files at once
|
||||
*/
|
||||
public class MultiCompiler extends Compiler {
|
||||
|
||||
public static Compiler instance = new MultiCompiler();
|
||||
|
||||
public MultiCompiler() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader compile(final Iterable<? extends JavaFileObject> source) {
|
||||
return super.compile(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader compile(final JavaFileObject... source) {
|
||||
return super.compile(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T compile(final String className, final JavaFileObject... source) {
|
||||
return super.compile(className, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T compile(final String className, final Iterable<? extends JavaFileObject> source) {
|
||||
return super.compile(className, source);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.moparisthebest.classgen;
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* For sending java source as strings to a Compiler
|
||||
* @see Compiler
|
||||
*/
|
||||
public class StringJavaFileObject extends SimpleJavaFileObject {
|
||||
private final CharSequence code;
|
||||
|
||||
public StringJavaFileObject(final String name, final CharSequence code) {
|
||||
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return code;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.moparisthebest.classgentest;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/15/17.
|
||||
*/
|
||||
public interface Calculator {
|
||||
public double calc(double a, double b);
|
||||
|
||||
public java.util.Date whenGenerated();
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package com.moparisthebest.classgentest;
|
||||
|
||||
import com.moparisthebest.classgen.Compiler;
|
||||
import com.moparisthebest.classgen.MultiCompiler;
|
||||
import com.moparisthebest.classgen.StringJavaFileObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* This is a sample program that shows how to generate java source code, compile, and run it, all at runtime and
|
||||
* without ever touching the filesystem. It can be used for much more complicated (and usefull) things, perhaps
|
||||
* like creating Pojo instances from ResultSets. :)
|
||||
* <p>
|
||||
* You need tools.jar on your classpath, and this class in the same package as well:
|
||||
* public interface Calculator {
|
||||
* public double calc(double a, double b);
|
||||
* public java.util.Date whenGenerated();
|
||||
* }
|
||||
* <p>
|
||||
* The original idea was taken from:
|
||||
* http://mindprod.com/jgloss/javacompiler.html#SAMPLECODE
|
||||
*
|
||||
* @author moparisthebest
|
||||
*/
|
||||
public class CalculatorCompiler {
|
||||
|
||||
@Test
|
||||
public void testSingle() throws InterruptedException {
|
||||
final Compiler compiler = new Compiler();
|
||||
final Calculator mult1 = genCalc(compiler, "Multiply", "a * b");
|
||||
final Calculator hyp = genCalc(compiler, "Hypotenuse", "Math.sqrt( a*a + b*b )");
|
||||
final Calculator mult2 = genCalc(compiler, "Multiply", "a * b * 2");
|
||||
final Calculator mult3 = genCalc(compiler, "Multiply", "a * b * 3");
|
||||
final Calculator mult4 = genCalc(compiler, "Multiply", "a * b * 4");
|
||||
assertEquals(12.0, mult1.calc(3.0, 4.0), 0.01);
|
||||
assertEquals(5.0, hyp.calc(3.0, 4.0), 0.01);
|
||||
assertEquals(24.0, mult2.calc(3.0, 4.0), 0.01);
|
||||
assertEquals(48.0, mult3.calc(4.0, 4.0), 0.01);
|
||||
assertEquals(48.0, mult4.calc(3.0, 4.0), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMulti() throws InterruptedException {
|
||||
final MultiCompiler compiler = new MultiCompiler();
|
||||
final Calculator mult1 = genCalc(compiler, "Multiply", "a * Var.var", "Var", "4.0");
|
||||
final Calculator hyp = genCalc(compiler, "Hypotenuse", "Math.sqrt( a*a + Var.var*Var.var )", "Var", "4.0");
|
||||
final Calculator mult2 = genCalc(compiler, "Multiply2", "a * Var.var * 2", "Var", "4.0");
|
||||
final Calculator mult3 = genCalc(compiler, "Multiply3", "a * Var.var * 3", "Var", "4.0");
|
||||
final Calculator mult4 = genCalc(compiler, "Multiply", "a * Var.var * 4", "Var", "4.0");
|
||||
assertEquals(mult1.calc(3.0, 4.0), 12.0, 0.01);
|
||||
assertEquals(hyp.calc(3.0, 4.0), 5.0, 0.01);
|
||||
assertEquals(mult2.calc(3.0, 4.0), 24.0, 0.01);
|
||||
assertEquals(mult3.calc(4.0, 4.0), 48.0, 0.01);
|
||||
assertEquals(48.0, mult4.calc(3.0, 4.0), 0.01);
|
||||
}
|
||||
|
||||
private static String writeCalculator(String className, String expression) {
|
||||
String packageName = null;
|
||||
final int lastIndex = className.lastIndexOf(".");
|
||||
if (lastIndex != -1) {
|
||||
packageName = className.substring(0, lastIndex);
|
||||
className = className.substring(lastIndex + 1);
|
||||
}
|
||||
|
||||
return (packageName == null ? "" : "package " + packageName + ";\n") +
|
||||
"import java.util.Date;\n" +
|
||||
"public final class " + className + " implements " + Calculator.class.getName() + " {\n" +
|
||||
" public double calc(double a, double b) {\n" +
|
||||
" return " + expression + ";\n" +
|
||||
" }\n" +
|
||||
" public Date whenGenerated() {\n" +
|
||||
" return new Date(" + System.currentTimeMillis() + "L);\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
}
|
||||
|
||||
private static StringJavaFileObject writeCalculatorOb(String className, String expression) {
|
||||
return new StringJavaFileObject(className, writeCalculator(className, expression));
|
||||
}
|
||||
|
||||
private static StringJavaFileObject writeVar(String className, String expression) {
|
||||
String packageName = null;
|
||||
final int lastIndex = className.lastIndexOf(".");
|
||||
if (lastIndex != -1) {
|
||||
packageName = className.substring(0, lastIndex);
|
||||
className = className.substring(lastIndex + 1);
|
||||
}
|
||||
|
||||
return new StringJavaFileObject(className, (packageName == null ? "" : "package " + packageName + ";\n") +
|
||||
"public final class " + className + " {\n" +
|
||||
" public static final double var = " + expression + ";\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
public static Calculator genCalc(final Compiler compiler, final String className, final String expression) {
|
||||
// compose text of Java program on the fly.
|
||||
final String calc = writeCalculator(className, expression);
|
||||
/*
|
||||
System.out.println("calc:");
|
||||
System.out.println(calc);
|
||||
*/
|
||||
return compiler.compile(className, calc);
|
||||
}
|
||||
|
||||
public static Calculator genCalc(final MultiCompiler compiler, final String className, final String expression, final String varClass, final String varExpression) {
|
||||
// compose text of Java program on the fly.
|
||||
final StringJavaFileObject calc = writeCalculatorOb(className, expression);
|
||||
final StringJavaFileObject var = writeVar(varClass, varExpression);
|
||||
/*
|
||||
System.out.println("calc:");
|
||||
System.out.println(calc.getCharContent(true));
|
||||
System.out.println("var:");
|
||||
System.out.println(var.getCharContent(true));
|
||||
*/
|
||||
return compiler.compile(className, calc, var);
|
||||
}
|
||||
|
||||
public static double runCalc(final String className, final String expression, final double a, final double b) {
|
||||
final Calculator calc = genCalc(new Compiler(), className, expression);
|
||||
double ret = calc.calc(a, b);
|
||||
System.out.printf("%s.calc( %f, %f ) is : %f\n", className, a, b, ret);
|
||||
System.out.printf("%s generated on: %s\n", className, calc.whenGenerated());
|
||||
return ret;
|
||||
}
|
||||
}
|
4
pom.xml
4
pom.xml
@ -241,8 +241,8 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.5</source>
|
||||
<target>1.5</target>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<debug>true</debug>
|
||||
<!--compilerArgument>-Xlint:unchecked</compilerArgument-->
|
||||
</configuration>
|
||||
|
Loading…
Reference in New Issue
Block a user