If you are using Java 6, you could try the Java Compiler API. At its core is the JavaCompiler class. You should be able to construct the source code for your Comparator object in memory.
Warning: I have not actually tried the code below as the JavaCompiler object is not available on my platform, for some odd reason...
Warning: Compiling arbitrary Java code can be hazardous to your health.
Consider yourself warned...
String comparableClassName = ...; // the class name of the objects you wish to compare
String comparatorClassName = ...; // something random to avoid class name conflicts
String source = "public class " + comparatorClassName + " implements Comparable<" + comparableClassName + "> {" +
" public int compare(" + comparableClassName + " a, " + comparableClassName + " b) {" +
" return " + expression + ";" +
" }" +
"}";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/*
* Please refer to the JavaCompiler JavaDoc page for examples of the following objects (most of which can remain null)
*/
Writer out = null;
JavaFileManager fileManager = null;
DiagnosticListener<? super JavaFileObject> diagnosticListener = null;
Iterable<String> options = null;
Iterable<String> classes = null;
Iterable<? extends JavaFileObject> compilationUnits = new ArrayList<? extends JavaFileObject>();
compilationUnits.add(
new SimpleJavaFileObject() {
// See the JavaDoc page for more details on loading the source String
}
);
compiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits).call();
Comparator comparator = (Comparator) Class.forName(comparableClassName).newInstance();
After this, you just have to store the appropriate Java expression in your database field, referencing a and b.
If you are using Java 6, you could try the Java Compiler API. At its core is the JavaCompiler class. You should be able to construct the source code for your Comparator object in memory.
Warning: I have not actually tried the code below as the JavaCompiler object is not available on my platform, for some odd reason...
Warning: Compiling arbitrary Java code can be hazardous to your health.
Consider yourself warned...
String comparableClassName = ...; // the class name of the objects you wish to compare
String comparatorClassName = ...; // something random to avoid class name conflicts
String source = "public class " + comparatorClassName + " implements Comparable<" + comparableClassName + "> {" +
" public int compare(" + comparableClassName + " a, " + comparableClassName + " b) {" +
" return " + expression + ";" +
" }" +
"}";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/*
* Please refer to the JavaCompiler JavaDoc page for examples of the following objects (most of which can remain null)
*/
Writer out = null;
JavaFileManager fileManager = null;
DiagnosticListener<? super JavaFileObject> diagnosticListener = null;
Iterable<String> options = null;
Iterable<String> classes = null;
Iterable<? extends JavaFileObject> compilationUnits = new ArrayList<? extends JavaFileObject>();
compilationUnits.add(
new SimpleJavaFileObject() {
// See the JavaDoc page for more details on loading the source String
}
);
compiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits).call();
Comparator comparator = (Comparator) Class.forName(comparableClassName).newInstance();
After this, you just have to store the appropriate Java expression in your database field, referencing a and b.
The question of how to programmatically compile Java Code that is given as a String is asked quite frequently and in various forms, sometimes referring to code that is stored in a database or entered by the user. When I searched for information about this, I stumbled upon many of these questions, and was disappointed to see that the general recommendation was to use external tools (BeanShell, Groovy...). The answer by Adam Paynter to this question was the most helpful in order to at least figure out the relevant keywords. But even by consulting further external resources (like an example from Java2s), I struggled with implementing a pure in-memory compilation of one or more Java classes (that actually worked) using only the JavaCompiler API.
So here is an example showing the whole process of compiling one or multiple classes in-memory, at runtime, when their source code is given as a string. It is built around a small utility class, RuntimeCompiler, that simply receives a sequence class names and the corresponding source codes, and then allows compiling these classes and obtaining the Class objects.
It is a MCVE that can be compiled and executed directly - with a JDK, not with a JRE, because the latter does not contain the tools like the JavaCompiler.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* An example showing how to use the RuntimeCompiler utility class
*/
public class RuntimeCompilerExample
{
public static void main(String[] args) throws Exception
{
simpleExample();
twoClassExample();
useLoadedClassExample();
}
/**
* Simple example: Shows how to add and compile a class, and then
* invoke a static method on the loaded class.
*/
private static void simpleExample()
{
String classNameA = "ExampleClass";
String codeA =
"public class ExampleClass {" + "\n" +
" public static void exampleMethod(String name) {" + "\n" +
" System.out.println(\"Hello, \"+name);" + "\n" +
" }" + "\n" +
"}" + "\n";
RuntimeCompiler r = new RuntimeCompiler();
r.addClass(classNameA, codeA);
r.compile();
MethodInvocationUtils.invokeStaticMethod(
r.getCompiledClass(classNameA),
"exampleMethod", "exampleParameter");
}
/**
* An example showing how to add two classes (where one refers to the
* other), compile them, and invoke a static method on one of them
*/
private static void twoClassExample()
{
String classNameA = "ExampleClassA";
String codeA =
"public class ExampleClassA {" + "\n" +
" public static void exampleMethodA(String name) {" + "\n" +
" System.out.println(\"Hello, \"+name);" + "\n" +
" }" + "\n" +
"}" + "\n";
String classNameB = "ExampleClassB";
String codeB =
"public class ExampleClassB {" + "\n" +
" public static void exampleMethodB(String name) {" + "\n" +
" System.out.println(\"Passing to other class\");" + "\n" +
" ExampleClassA.exampleMethodA(name);" + "\n" +
" }" + "\n" +
"}" + "\n";
RuntimeCompiler r = new RuntimeCompiler();
r.addClass(classNameA, codeA);
r.addClass(classNameB, codeB);
r.compile();
MethodInvocationUtils.invokeStaticMethod(
r.getCompiledClass(classNameB),
"exampleMethodB", "exampleParameter");
}
/**
* An example that compiles and loads a class, and then uses an
* instance of this class
*/
private static void useLoadedClassExample() throws Exception
{
String classNameA = "ExampleComparator";
String codeA =
"import java.util.Comparator;" + "\n" +
"public class ExampleComparator " + "\n" +
" implements Comparator<Integer> {" + "\n" +
" @Override" + "\n" +
" public int compare(Integer i0, Integer i1) {" + "\n" +
" System.out.println(i0+\" and \"+i1);" + "\n" +
" return Integer.compare(i0, i1);" + "\n" +
" }" + "\n" +
"}" + "\n";
RuntimeCompiler r = new RuntimeCompiler();
r.addClass(classNameA, codeA);
r.compile();
Class<?> c = r.getCompiledClass("ExampleComparator");
Comparator<Integer> comparator = (Comparator<Integer>) c.newInstance();
System.out.println("Sorting...");
List<Integer> list = new ArrayList<Integer>(Arrays.asList(3,1,2));
Collections.sort(list, comparator);
System.out.println("Result: "+list);
}
}
/**
* Utility class for compiling classes whose source code is given as
* strings, in-memory, at runtime, using the JavaCompiler tools.
*/
class RuntimeCompiler
{
/**
* The Java Compiler
*/
private final JavaCompiler javaCompiler;
/**
* The mapping from fully qualified class names to the class data
*/
private final Map<String, byte[]> classData;
/**
* A class loader that will look up classes in the {@link #classData}
*/
private final MapClassLoader mapClassLoader;
/**
* The JavaFileManager that will handle the compiled classes, and
* eventually put them into the {@link #classData}
*/
private final ClassDataFileManager classDataFileManager;
/**
* The compilation units for the next compilation task
*/
private final List<JavaFileObject> compilationUnits;
/**
* Creates a new RuntimeCompiler
*
* @throws NullPointerException If no JavaCompiler could be obtained.
* This is the case when the application was not started with a JDK,
* but only with a JRE. (More specifically: When the JDK tools are
* not in the classpath).
*/
public RuntimeCompiler()
{
this.javaCompiler = ToolProvider.getSystemJavaCompiler();
if (javaCompiler == null)
{
throw new NullPointerException(
"No JavaCompiler found. Make sure to run this with "
+ "a JDK, and not only with a JRE");
}
this.classData = new LinkedHashMap<String, byte[]>();
this.mapClassLoader = new MapClassLoader();
this.classDataFileManager =
new ClassDataFileManager(
javaCompiler.getStandardFileManager(null, null, null));
this.compilationUnits = new ArrayList<JavaFileObject>();
}
/**
* Add a class with the given name and source code to be compiled
* with the next call to {@link #compile()}
*
* @param className The class name
* @param code The code of the class
*/
public void addClass(String className, String code)
{
String javaFileName = className + ".java";
JavaFileObject javaFileObject =
new MemoryJavaSourceFileObject(javaFileName, code);
compilationUnits.add(javaFileObject);
}
/**
* Compile all classes that have been added by calling
* {@link #addClass(String, String)}
*
* @return Whether the compilation succeeded
*/
boolean compile()
{
DiagnosticCollector<JavaFileObject> diagnosticsCollector =
new DiagnosticCollector<JavaFileObject>();
CompilationTask task =
javaCompiler.getTask(null, classDataFileManager,
diagnosticsCollector, null, null,
compilationUnits);
boolean success = task.call();
compilationUnits.clear();
for (Diagnostic<?> diagnostic : diagnosticsCollector.getDiagnostics())
{
System.out.println(
diagnostic.getKind() + " : " +
diagnostic.getMessage(null));
System.out.println(
"Line " + diagnostic.getLineNumber() +
" of " + diagnostic.getSource());
System.out.println();
}
return success;
}
/**
* Obtain a class that was previously compiled by adding it with
* {@link #addClass(String, String)} and calling {@link #compile()}.
*
* @param className The class name
* @return The class. Returns <code>null</code> if the compilation failed.
*/
public Class<?> getCompiledClass(String className)
{
return mapClassLoader.findClass(className);
}
/**
* In-memory representation of a source JavaFileObject
*/
private static final class MemoryJavaSourceFileObject extends
SimpleJavaFileObject
{
/**
* The source code of the class
*/
private final String code;
/**
* Creates a new in-memory representation of a Java file
*
* @param fileName The file name
* @param code The source code of the file
*/
private MemoryJavaSourceFileObject(String fileName, String code)
{
super(URI.create("string:///" + fileName), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException
{
return code;
}
}
/**
* A class loader that will look up classes in the {@link #classData}
*/
private class MapClassLoader extends ClassLoader
{
@Override
public Class<?> findClass(String name)
{
byte[] b = classData.get(name);
return defineClass(name, b, 0, b.length);
}
}
/**
* In-memory representation of a class JavaFileObject
* @author User
*
*/
private class MemoryJavaClassFileObject extends SimpleJavaFileObject
{
/**
* The name of the class represented by the file object
*/
private final String className;
/**
* Create a new java file object that represents the specified class
*
* @param className THe name of the class
*/
private MemoryJavaClassFileObject(String className)
{
super(URI.create("string:///" + className + ".class"),
Kind.CLASS);
this.className = className;
}
@Override
public OutputStream openOutputStream() throws IOException
{
return new ClassDataOutputStream(className);
}
}
/**
* A JavaFileManager that manages the compiled classes by passing
* them to the {@link #classData} map via a ClassDataOutputStream
*/
private class ClassDataFileManager extends
ForwardingJavaFileManager<StandardJavaFileManager>
{
/**
* Create a new file manager that delegates to the given file manager
*
* @param standardJavaFileManager The delegate file manager
*/
private ClassDataFileManager(
StandardJavaFileManager standardJavaFileManager)
{
super(standardJavaFileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(final Location location,
final String className, Kind kind, FileObject sibling)
throws IOException
{
return new MemoryJavaClassFileObject(className);
}
}
/**
* An output stream that is used by the ClassDataFileManager
* to store the compiled classes in the {@link #classData} map
*/
private class ClassDataOutputStream extends OutputStream
{
/**
* The name of the class that the received class data represents
*/
private final String className;
/**
* The output stream that will receive the class data
*/
private final ByteArrayOutputStream baos;
/**
* Creates a new output stream that will store the class
* data for the class with the given name
*
* @param className The class name
*/
private ClassDataOutputStream(String className)
{
this.className = className;
this.baos = new ByteArrayOutputStream();
}
@Override
public void write(int b) throws IOException
{
baos.write(b);
}
@Override
public void close() throws IOException
{
classData.put(className, baos.toByteArray());
super.close();
}
}
}
/**
* Utility methods not directly related to the RuntimeCompiler
*/
class MethodInvocationUtils
{
/**
* Utility method to invoke the first static method in the given
* class that can accept the given parameters.
*
* @param c The class
* @param methodName The method name
* @param args The arguments for the method call
* @return The return value of the method call
* @throws RuntimeException If either the class or a matching method
* could not be found
*/
public static Object invokeStaticMethod(
Class<?> c, String methodName, Object... args)
{
Method m = findFirstMatchingStaticMethod(c, methodName, args);
if (m == null)
{
throw new RuntimeException("No matching method found");
}
try
{
return m.invoke(null, args);
}
catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
catch (IllegalArgumentException e)
{
throw new RuntimeException(e);
}
catch (InvocationTargetException e)
{
throw new RuntimeException(e);
}
catch (SecurityException e)
{
throw new RuntimeException(e);
}
}
/**
* Utility method to find the first static method in the given
* class that has the given name and can accept the given
* arguments. Returns <code>null</code> if no such method
* can be found.
*
* @param c The class
* @param methodName The name of the method
* @param args The arguments
* @return The first matching static method.
*/
private static Method findFirstMatchingStaticMethod(
Class<?> c, String methodName, Object ... args)
{
Method methods[] = c.getDeclaredMethods();
for (Method m : methods)
{
if (m.getName().equals(methodName) &&
Modifier.isStatic(m.getModifiers()))
{
Class<?>[] parameterTypes = m.getParameterTypes();
if (areAssignable(parameterTypes, args))
{
return m;
}
}
}
return null;
}
/**
* Returns whether the given arguments are assignable to the
* respective types
*
* @param types The types
* @param args The arguments
* @return Whether the arguments are assignable
*/
private static boolean areAssignable(Class<?> types[], Object ...args)
{
if (types.length != args.length)
{
return false;
}
for (int i=0; i<types.length; i++)
{
Object arg = args[i];
Class<?> type = types[i];
if (arg != null && !type.isAssignableFrom(arg.getClass()))
{
return false;
}
}
return true;
}
}
EDIT In response to the comment:
In order to compile classes that are contained in external JAR files, it should be sufficient to add the JAR to the classpath of the calling application. The JavaCompiler will then pick up this classpath to find classes that it needs for the compilation.
There seems to be some magic involved. At least, I have not figured out the exact mechanisms behind that, just tested it with an example
And a side note: Of course, one could consider literally arbitrary extensions to this class. My goal was to create a simple, standalone, easily copy-and-pastable example that shows the whole process, and may even be "useful" for certain application patterns.
For more sophisticated functionalities, one could consider either extending this class accordingly, or have a look, for example, at the Java-Runtime-Compiler from the OpenHFT project (I stumbled upon this a few weeks after I had written this answer). It basically uses the same techniques internally, but in a somewhat more sophisticated way, and also offers dedicated mechanisms for handling class loaders for external dependencies.
Convert strings to executable java code - Oracle Forums
convert String to runtime code in java - Stack Overflow
java - How do you run a string as code? - Stack Overflow
Convert a string in code in Java - Stack Overflow
You can wrap this string in a class and compile it with Java Compiler API. You would have to be running JDK instead of JRE.
See this similar question
This simple answer is, this can't really be done in Java. You could come up with some hacky ways of doing it by attempting to invoke the javac compiler, but I'm not sure how you would really load it in or make use of it at that point.
In some scripting languages, like Javascript and Lua, you can use 'eval' type functions that attempt to load your string and execute it.
If your goal is to take any text input and run arbitrary commands, then the places to start looking on the JVM are the Java 6 compiler API (http://java.sun.com/javase/6/docs/api/javax/tools/package-summary.html) or JSR 223 (http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting).
If your goal is storage and retrieval of information from text file, look at protocol buffers (http://code.google.com/p/protobuf/) or the Java serialization API (http://www.javaworld.com/javaworld/jw-07-2000/jw-0714-flatten.html).
You first need to parse your mini-language. So (as mentioned) Antlr, or Javacc to create a parser for your language.
After that, you need to use the meta-programming features of your 'interpreter'/target language. For Java, you want to start with reflection, and if you absolutely need it, later move on to byte code generation.
As has already been suggested you can compile, save and run code on the fly using the Compiler API.
Another neat alternative would be to use beanshell. Beanshell is no longer actively developed, but I can vouch for it's reliability, I've used it successfully in multiple production projects.
Use BeanShell. There's a page on how to use it from Java.
Since Java 6, you can compile and run a Java compilation unit defined as a String or a File using standard APIs in the SDK (a compilation unit is basically everything that goes inside a .java file - package, imports, classes/interfaces/enumerations), take a look at this example. You can't run an arbitrary Java snippet like the one in your question, though.
If at all possible, it'd be a better idea to embed a different scripting language that allows you to run snippets of code from a Java program - for example, JavaScript, Groovy, MVEL, BeanShell, etc.
If you turn it into a full-blown source file, you can feed it to the java compiler programmatically, but last time I checked that was only available if you had the java SDK installed on your machine; it was not available on machines with the client distribution of Java. Of course, this may have changed since then. Look at package com.sun.tools.javac and you will find the java compiler API there.