Recently, I have been developing a Java application that analyzes Java source code in my business, but there were cases where I wanted to eval a part of the source code to be analyzed. So I eval the Java source code and lightly investigated how to get the results.
Please refer to tanzaku / eval \ -java \ -code for the source code used for the experiment. The master contains the JDK10 test code, and the eval_jdk8 branch contains the JDK8 test code.
Since you can get an instance of Class by the method according to the site below, you can evaluate it by executing the method with reflection. Java Compiler Class Memo \ (Hishidama's JavaCompiler Memo )
If you do not want to make detailed customizations, it may be easier to use a library like the one below. OpenHFT/Java-Runtime-Compiler: Java Runtime Compiler
You can evaluate it just by calling ScriptEngine # eval
as shown below. (You need to add groovy-all
to your classpath)
package experiments.eval.evaluator;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class GroovyEvaluator {
private static ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");
public String eval(final String sourceCode) throws ScriptException {
return scriptEngine.eval(sourceCode).toString();
}
}
However, be aware that the Java source code may not be a valid Groovy script as is. For example, an expression like new String [] {"A "}
is invalid in groovy and should be something like [" A "] as String []
.
It can be evaled using the JShell API added in Java 9. JShell (Java SE 9 & JDK 9 )
package experiments.eval.evaluator;
import java.util.Arrays;
import java.util.stream.Stream;
import jdk.jshell.JShell;
import jdk.jshell.SnippetEvent;
public class JShellEvaluator {
private Stream<String> splitStatements(final String sourceCode) {
return Arrays.stream(sourceCode.split(";")).map(stmt -> stmt + ";");
}
public String eval(final String sourceCode) {
try (final JShell jshell = JShell.create()) {
//It seems that multiple statements cannot be evaluated at once, so evaluate them separately.
return splitStatements(sourceCode)
.flatMap(stmt -> jshell.eval(stmt).stream())
.reduce((a,b)->b)
.map(SnippetEvent::value)
.orElse(null);
}
}
}
The processing time when evaluated by each method was measured. We are measuring the time it takes to eval a small process 100 times. The 100 operations are slightly different so that they are not cached. Please refer to the source code for details.
Method | JDK ver | processing time[sec] |
---|---|---|
Java Compiler API | Oracle JDK 8 | 14.732 |
Java Compiler API | Oracle JDK 10 | 42.743 |
groovy | Oracle JDK 8 | 3.578 |
groovy | Oracle JDK 10 | 4.914 |
JShell API | Oracle JDK 10 | 147.294 |
The source code is concise, and it seems that it is not inferior in speed, so it seems good to evaluate it with groovy. However, in any case, it is definitely a very heavy process, so it seems to be difficult to use it a lot. Of course, if the version of the compiler changes, the performance will change, so be careful when upgrading.
Recommended Posts