Reputation: 1
I'm creating a program (executable) that allows the user to play around with graphs and execute algorithms on them. Basically the user should be able to create a new algorithm, for example a modification of Dijkstra's, and try it out on the graph. However, I'm not sure what's the best way to program this.
At the moment I'm considering allowing the user to write java code and let that be saved in a separate directory. However, I don't know whether that's even possible, as you would have to compile java code live while executing the program. Is this method viable?
Another way would be to create an interpreter and allow the user to write pseudocode, which would then be saved and converted to something the program would understand. However, if the previous method can be done it might be easier than this one.
Is the first method possible? If so, is it better than the second? Or is there an easier way to do this?
Upvotes: 0
Views: 133
Reputation: 54719
There are several options for this, each associated with different efforts and possibilities. You could write an own interpreter, or use an existing iterpreter, or use a different stripting language and use one of the built-in script engines.
However, coming back to your original intention, to let the user write Java Code, there is also an option for this: When there is a JDK installed, you can use the Java Tools of the JDK. (Note that this will not work with a JRE only).
Particularly, you can use the JavaCompiler class to compile your Java classes on the fly.
I explicitly do NOT want to say that this is the most appropriate solution for your problem. I just want to say that this is an option, and that you can indeed compile Java Code on the fly.
When I wanted to just link to an example (or copy & paste one, with attribution), I noticed that the examples that I found online either plainly don't work, or used temporary files, or only focussed on certain specific aspects of the JavaCompiler
and its usage (imagine a list of Blogs and StackOverflow answers here).
I did not find a MCVE that showed how to compile the source code of one class (or even less: multiple classes!), and load the resulting class, completely in-memory. So I created such a utility class: The InMemoryCompiler
class can receive a list of class names and the corresponding source code, and returns a map from class names to Class<?>
objects. It can be that simple, and it should be that simple. Maybe someone finds this useful.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
public class JavaCompilerExample
{
public static void main(String[] args)
{
String classNameA = "ExampleClassA";
String sourceA =
"public class " + classNameA + " {" + "\n" +
" public static void main(String args[]) {" + "\n" +
" System.out.println(\"Hello, compiler!\");" + "\n" +
" ExampleClassB b = new ExampleClassB();" + "\n" +
" b.someMethod();" + "\n" +
" }" + "\n" +
"}" + "\n";
String classNameB = "ExampleClassB";
String sourceB =
"public class " + classNameB + " {" + "\n" +
" public void someMethod() {" + "\n" +
" System.out.println(\"Some method was called\");" + "\n" +
" }" + "\n" +
"}" + "\n";
InMemoryCompiler c = new InMemoryCompiler();
Map<String, Class<?>> classes = c.compile(
Arrays.asList(classNameA, classNameB),
Arrays.asList(sourceA, sourceB));
try
{
Class<?> exampleClass = classes.get(classNameA);
Method m = exampleClass.getDeclaredMethod("main",
new Class[] { String[].class });
m.invoke(null, (Object) null);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class InMemoryCompiler
{
private final Map<String, ByteArrayJavaFileObject> classFileObjects;
private final JavaCompiler javaCompiler;
private final JavaFileManager fileManager;
private final ClassLoader classLoader;
InMemoryCompiler()
{
classFileObjects =
new LinkedHashMap<String, ByteArrayJavaFileObject>();
javaCompiler = ToolProvider.getSystemJavaCompiler();
JavaFileManager standardFileManager = javaCompiler
.getStandardFileManager(null, Locale.ENGLISH, null);
fileManager = new CompiledFileManager(standardFileManager);
classLoader = new CompiledClassLoader();
}
public Map<String, Class<?>> compile(
List<String> classNames, List<String> sources)
{
List<JavaFileObject> javaFileObjects = new ArrayList<JavaFileObject>();
for (int i = 0; i < classNames.size(); i++)
{
String className = classNames.get(i);
String source = sources.get(i);
javaFileObjects.add(new StringJavaFileObject(className, source));
}
CompilationTask compilationTask = javaCompiler.getTask(
new PrintWriter(System.out), fileManager, null, null, null,
javaFileObjects);
compilationTask.call();
Map<String, Class<?>> classes = new LinkedHashMap<String, Class<?>>();
for (int i = 0; i < classNames.size(); i++)
{
String className = classNames.get(i);
try
{
Class<?> c = classLoader.loadClass(className);
classes.put(className, c);
}
catch (ClassNotFoundException e)
{
System.out.println(e);
}
}
return classes;
}
private class CompiledFileManager
extends ForwardingJavaFileManager<JavaFileManager>
{
CompiledFileManager(JavaFileManager fileManager)
{
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className, javax.tools.JavaFileObject.Kind kind,
FileObject sibling) throws IOException
{
ByteArrayJavaFileObject javaFileObject =
new ByteArrayJavaFileObject(className);
classFileObjects.put(className, javaFileObject);
return javaFileObject;
}
}
private class CompiledClassLoader extends ClassLoader
{
@Override
public Class<?> findClass(String name)
{
byte[] b = classFileObjects.get(name).getBytes();
return defineClass(name, b, 0, b.length);
}
}
private class ByteArrayJavaFileObject extends SimpleJavaFileObject
{
private final ByteArrayOutputStream stream;
public ByteArrayJavaFileObject(String name)
{
super(URI.create("bytes:///" + name), Kind.CLASS);
stream = new ByteArrayOutputStream();
}
@Override
public OutputStream openOutputStream() throws IOException
{
return stream;
}
public byte[] getBytes()
{
return stream.toByteArray();
}
}
private class StringJavaFileObject extends SimpleJavaFileObject
{
private final String code;
StringJavaFileObject(String name, String code)
{
super(URI.create("string:///" + name.replace('.', '/') +
Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
{
return code;
}
}
}
(Note: There is hardly any error handling in this class. It could be extended with a DiagnosticListener
and such, but this was not the main intention here)
Upvotes: 0
Reputation: 2773
Instead of creating an interpreter and reinventing the wheel, you could use an already existing interpreter, such as :
For each of these library, you can choose to expose some classes to the underlying language as variable.
Upvotes: 1
Reputation: 906
Java Virtual Machine has an embedded JavaScript engine that you can use for your purposes. You can have your application load and execute scripts at runtime. Also, you can easily inject your own objects implementing whatever functionality you need.
Upvotes: 0