Add Compiler

This commit is contained in:
Travis Burtrum 2017-05-16 10:42:14 -04:00
parent 8ac701ea4a
commit 1b2f248574
6 changed files with 370 additions and 2 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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>