1

I have used JavaCompiler to compile a class that I generate on the fly. The compile task succeeds. I then try to load the compiled class using Class.forName("MyClass"); and it fails with a ClassNotFoundException. Is there a known issue with doing this?

package com.amir.method;

import java.io.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import javax.tools.*;

public class MethodGenerator {

    private final static MethodGenerator INSTANCE = new MethodGenerator();

    private MethodGenerator() {
    }

    public static MethodGenerator get() {
        return INSTANCE;
    }


    public static void main(String[] args) {

        final Class<?> clz = MethodGenerator.get().compile("Foo", "doIt"); //<- args ignored for now

    }

    public Class compile(final String className,
                         final String methodName) {
        try {
            return doCompile(className, methodName);
        } catch(final Exception e) {
            throw new RuntimeException(this.toString(), e);
        }

    }

    private Class doCompile(final String className,
                            final String methodName) throws IOException, ClassNotFoundException {

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

        StringWriter writer = new StringWriter();
        PrintWriter out = new PrintWriter(writer);
        out.println("public class HelloWorld {");
        out.println("  public static void main(String args[]) {");
        out.println("    System.out.println(\"This is in another java file\");");
        out.println("  }");
        out.println("}");
        out.close();

        final JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());
        final Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
        final String classPath = System.getProperty("java.class.path") + File.pathSeparator;
        final String bootClassPath = System.getProperty("sun.boot.class.path") + File.pathSeparator;
        final List<String> options = new ArrayList<String>();
        options.add("-classpath");
        final StringBuilder builder = new StringBuilder();
        builder.append(classPath);
        builder.append(bootClassPath);
        final URLClassLoader urlClassLoader = (URLClassLoader) ClassLoaderResolver.getClassLoader();
        for (final URL url : urlClassLoader.getURLs()) {
            builder.append(url.getFile()).append(File.pathSeparator);
        }
        final int lastIndexOfColon = builder.lastIndexOf(File.pathSeparator);
        builder.replace(lastIndexOfColon, lastIndexOfColon + 1, "");
        options.add(builder.toString());

        final JavaCompiler.CompilationTask task = compiler.getTask(null, null, diagnostics, options, null, compilationUnits);

        boolean success = task.call();

        if(success) {
            final Class<?> cls = Class.forName("HelloWorld", true, urlClassLoader);
            return cls;
        }

        return null;

    }

    class JavaSourceFromString extends SimpleJavaFileObject {
        final String code;

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

}
Amir Afghani
  • 37,814
  • 16
  • 84
  • 124
  • Did you check you manifest if the export/importing that class ? How do you build your project. Try to re generate your manifest file. – StackFlowed Nov 06 '14 at 17:56
  • Manifest? I'm compiling a class on the fly. I'm not sure I understand what a manifest file would do ? – Amir Afghani Nov 06 '14 at 17:58
  • Compiling the class doesn't automatically add it to the classloader. You'll need to do that yourself. – Paul Nov 06 '14 at 18:02
  • Hi @Paul Can you post an answer with an approach given the code I listed above please? – Amir Afghani Nov 06 '14 at 18:03
  • 1
    What is a `ClassLoaderResolver`? I don't see anything like that in the JRE, and if I Google for it I see this class in various places including one from Apache. – ajb Nov 06 '14 at 18:03
  • @ajb It's a custom class loader I wrote, but I think I can replace it with Thread.currentThread().getContextClassLoader() -- Do you think that is related to the problem? – Amir Afghani Nov 06 '14 at 18:07
  • @AmirAfghani I have no idea. I was trying out your code to see if I could duplicate your problem or look into it further, but I couldn't get it to compile without that. I'll see if I can get something to happen with `getContextClassLoader`. – ajb Nov 06 '14 at 18:09
  • Try replacing that line with the current threads context class loader @ajb – Amir Afghani Nov 06 '14 at 18:10
  • As many of the comments already suggest, I also suspect the loading of the class is the problem here; I have written a "compiler" based on this API a while ago and it still works, so maybe you can [use some code from it](https://github.com/fge/json-schema-processor-examples/tree/master/src/main/java/com/github/fge/compiler) (feel free). In particular, look at the `CompilerOutput` class when I get to load the class using a custom `ClassLoader`. – fge Nov 06 '14 at 18:10
  • @AmirAfghani OK, I tried it with `getContextClassLoader()`, and it seems to succeed. `doCompile` returns a `Class` whose name is `HelloWorld`. So perhaps the problem is in your custom loader. – ajb Nov 06 '14 at 18:13
  • @ajb can you post what you tried please? I just tried using getContextClassLoader and it still fails. – Amir Afghani Nov 06 '14 at 18:14
  • Your code is similar to the code in [this answer](http://stackoverflow.com/a/12173346/185034) which presumably works. Have you tried the code in the answer verbatim? – Paul Nov 06 '14 at 18:17
  • @AmirAfghani Basically just what you have above, except replacing one line with `final URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();`. I did this from a Command Prompt window in Windows. My `%CLASSPATH%` starts with `.;` and then lists some other Apache and Jackson libraries in it. I don't know what else to tell you. – ajb Nov 06 '14 at 18:18

1 Answers1

0

Try to load the class after compiling it by using, Thread.currentThread().getContextClassLoader().loadClass("com.ur.class");

Found this...check if this can help..

// Create a new custom class loader, pointing to the directory that contains the compiled
// classes, this should point to the top of the package structure!
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
// Load the class from the classloader by name....
Class<?> loadedClass = classLoader.loadClass("com.ur.class");
// Create a new instance...
Object obj = loadedClass.newInstance();
// Santity check
if (obj instanceof com.ur.class) {
    // code here ...
}

For more information refer this: How do you dynamically compile and load external java classes?

Community
  • 1
  • 1
Rupesh
  • 2,627
  • 1
  • 28
  • 42
  • What exactly would the string argument be? In the comments section, it was suggested that I should use "com.amir.method.HelloWorld", but that doesn't seem to work... – Amir Afghani Nov 06 '14 at 18:09
  • @AmirAfghani What package is the class in? – Vince Nov 06 '14 at 18:15
  • @VinceEmigh - as you can see from the generated code, its not in any specific package. There is no package statement in my source. – Amir Afghani Nov 06 '14 at 18:17
  • @AmirAfghani Then you wouldn't use "com.amir.method" before "HelloWorld" when loading the class. Youd just use "HelloWorld" – Vince Nov 06 '14 at 18:21