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.

2
  • stackoverflow.com/questions/1168931/… Commented Jun 4, 2012 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. Commented Jun 4, 2012 at 14:34

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, I didn't know about the compiler api :-)
I'd rather use defineClass() instead of writing the class file to disk temporarily.
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.

Comments

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);
    }
}

Comments

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 )

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.