3

I know this has probably something to do with class loaders, however I couldn't find an example (it might be I'm google-ing for the wrong keywords.

I am trying to load a class (or a method) form a string. The string doesn't contain the name of a class, but the code for a class, e.g.

class MyClass implements IMath {
    public int add(int x, int y) {
         return x + y;
    }
}

and then do something like this:

String s = "class MyClass implements IMath { public int add(int x, int y) { return x + y; }}";
IMath loadedClass = someThing.loadAndInitialize(string);
int result = loadedClass.add(5,6);

Now obviously, the someThing.loadAndInitialize(string) - part is the one I don't know how to achieve. Is this even possible? Or would it be easier to run JavaScripts and somehow "give" the variables / objects (like x and y)?

Thank you for any hints.

Dänu
  • 5,791
  • 9
  • 43
  • 56
  • http://stackoverflow.com/questions/1168931/how-to-create-an-object-from-a-string-in-java-how-to-eval-a-string – nullpotent Jun 04 '12 at 14:28
  • Personally, if the source *had* to be Java, I'd either just use the Java compilation API, or if that wasn't sufficient, Janino, javassist, etc. – Dave Newton Jun 04 '12 at 14:34

5 Answers5

9

Use Java Compiler API. Here is a blog post that shows you how to do it.

You can use temporary files for this, as this requires input/output file, or you can create custom implementation of JavaFileObject that reads source from string. From the javadoc:

   /**
    * A file object used to represent source coming from a string.
    */
   public class JavaSourceFromString extends SimpleJavaFileObject {
       /**
        * The source code of this "file".
        */
       final String code;

       /**
        * Constructs a new JavaSourceFromString.
        * @param name the name of the compilation unit represented by this file object
        * @param code the source code for the compilation unit represented by this file object
        */
       JavaSourceFromString(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;
       }
   }

Once you have the output file (which is a compiled .class file), you can load it using URLClassLoader as follows:

    ClassLoader loader = new URLClassLoader(new URL[] {myClassFile.toURL());
    Class myClass = loader.loadClass("my.package.MyClass");

and then instantiate it, using:

    myClass.newInstance();

or using a Constructor.

npe
  • 15,395
  • 1
  • 56
  • 55
3

You can use Rhino and JavaScript in JDK 7. That might be a good way to do it.

invokedynamic is coming....

If you want to stick with Java, you need something to parse the source and turn it into byte code - something like cglib.

duffymo
  • 305,152
  • 44
  • 369
  • 561
1

An example of loading a class from a String without creating temporary class files. It is a shortened version of toluju's answer to a similar question.

import java.io.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import javax.tools.*;
import javax.tools.JavaFileObject.Kind;

@SuppressWarnings({ "unchecked", "boxing", "hiding", "rawtypes" })
public class MemoryClassLoader extends ClassLoader {
    public static void main(String[] args) throws Exception {
        String className = "test.MyClass";
        String javaSource = "package test; public class MyClass { public static void test() { System.out.println(\"Hello World\"); } }";
        // use a parent class loader that can resolve the classes referenced in the source
        ClassLoader parentClassLoader = MemoryClassLoader.class.getClassLoader();
        Class<?> clazz = new MemoryClassLoader(parentClassLoader).compileAndLoad(className, javaSource);
        clazz.getMethod("test").invoke(null);
    }

    public MemoryClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class<?> compileAndLoad(String className, String javaSource) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StringWriter errorWriter = new StringWriter();
        ByteArrayOutputStream compiledBytesOutputStream = new ByteArrayOutputStream();

        SimpleJavaFileObject sourceFile = new SimpleJavaFileObject(URI.create("file:///" + className.replace('.', '/') + ".java"), Kind.SOURCE) {
            @Override
            public CharSequence getCharContent(boolean ignoreEncErrors) {
                return javaSource;
            }
        };

        SimpleJavaFileObject classFile = new SimpleJavaFileObject(URI.create("file:///" + className.replace('.', '/') + ".class"), Kind.CLASS) {
            @Override
            public OutputStream openOutputStream() throws IOException {
                return compiledBytesOutputStream;
            }
        };

        ForwardingJavaFileManager fileManager = new ForwardingJavaFileManager(compiler.getStandardFileManager(null, null, null)) {
            @Override
            public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
                return classFile;
            }
        };

        // compile class
        if (!compiler.getTask(errorWriter, fileManager, null, null, null, Arrays.asList(sourceFile)).call()) {
            throw new Exception(errorWriter.toString());
        }

        // load class
        byte[] bytes = compiledBytesOutputStream.toByteArray();
        return super.defineClass(className, bytes, 0, bytes.length);
    }
}
Reto Höhener
  • 5,419
  • 4
  • 39
  • 79
0

You could compile it using JavaCompiler but I suggest you to use Groovy for this run-time class creation. It would be much easier.

Xeon
  • 5,949
  • 5
  • 31
  • 52
0

At first you need to compile your code, for example using compiler API: ( http://www.accordess.com/wpblog/an-overview-of-java-compilation-api-jsr-199/, http://docs.oracle.com/javase/6/docs/api/javax/tools/package-summary.html). And after it load compiled class with ClassLoader ( http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html )

dbf
  • 6,399
  • 2
  • 38
  • 65