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)