3

I write a simple java code in a String and want to execute dynamically when the program runs, below is my code:

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class CompliedClass {
    public static void main(String[] args) {
        String source = ""
                +"class Solution{"
                +"public int add(){"
                +"return 1+1;"
                +"}"
                +"}";

        //File root = new File("");
        File sourceFile = new File("./src/Solution.java");
        try {
            Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(sourceFile.getPath());
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            System.out.println("JDK required (running inside of JRE)");
        }else{
            System.out.println("you got it!");
        }

        int compilationResult = compiler.run(null, null, null,sourceFile.getPath());
        if(compilationResult == 0){
            System.out.println("Compilation is successful");
        }else{
            System.out.println("Compilation Failed");
        }

        try{
            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { sourceFile.toURI().toURL() });
            Class<?> cls = Class.forName("Solution" , true, classLoader);
            Object instance = cls.newInstance();
            Method method = cls.getDeclaredMethod("add", null);
            System.out.println(method.invoke(instance, null));
        }catch(Exception e){
            System.out.println("something wrong");
        }

    }
}

The Problem with the code above is when I execute first time, I can not get the result, it seems the below code has an exception:

Object instance = cls.newInstance();

Then I execute the second time, it functions good, so the conclusion is when I run for the first time, the Solution class can not be found, which cause the exception below

java.lang.ClassNotFoundException: Solution

could someone help me fix this issue please?

crlyw
  • 109
  • 8
  • Just wonder why not loading a jar file dynamically? – Top.Deck Feb 22 '17 at 21:04
  • ClassLoaders load .class files, not .java files. You cannot pass `sourceFile` to a URLClassLoader. In fact, I’m pretty sure you can’t even pass a .class file; you need to pass the URL of a directory or .jar file which contains one or more .class files. The URLs you pass to URLClassLoader are functionally identical to a classpath. – VGR Feb 22 '17 at 21:18
  • 1
    A side note: You don't even have to write the stuff to a *file*. This can be done purely in-memory. See http://stackoverflow.com/questions/935175/convert-string-to-code/30038318#30038318 for an example. – Marco13 Feb 22 '17 at 21:24

3 Answers3

1

1) Your class should have the public modifier otherwise CompliedClass could not access to it if it not declared in the same package. Here it could work as both are in the default package but it is not advised.

2) Your error is here :

URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { sourceFile.toURI().toURL() });

Here sourceFile refers to the Solution java class file.
It should rather refer to the url of the folder containing the Solution java class file as according to the javadoc of URLClassLoader java.net.URLClassLoader.newInstance(URL[] urls), the url parameter refers to the URLs to search for classes and resources.

You can try this code with the two explained modifications:

public class CompliedClass {
    public static void main(String[] args) {

        String source = "public class Solution{" + "public int add(){" + "return 1+1;" + "}" + "}";

        File folder = new File("./src");
        File sourceFile = new File(folder, "Solution.java");

        try {
            Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(sourceFile.getPath());
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            System.out.println("JDK required (running inside of JRE)");
        } else {
            System.out.println("you got it!");
        }

        int compilationResult = compiler.run(null, null, null, sourceFile.getPath());
        if (compilationResult == 0) {
            System.out.println("Compilation is successful");
        } else {
            System.out.println("Compilation Failed");
        }

        try {
            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] {folder.toURI().toURL() });
            Class<?> cls = Class.forName("Solution", true, classLoader);
            Object instance = cls.newInstance();
            Method method = cls.getDeclaredMethod("add", null);
            System.out.println(method.invoke(instance, null));
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("something wrong");
        }

    }
}

Output :

.\src\Solution.java

you got it!

Compilation is successful

2

davidxxx
  • 125,838
  • 23
  • 214
  • 215
1

Your problem might be the class file for the solution .class being created in another location than the location of the .class file of the current class. Make sure the location where the .class file is created is included in the classpath of the current program and you would be good to go.

Aman J
  • 452
  • 4
  • 15
1

There are several problems with the code you have provided.

  1. The class you define as a string should be accessible by your main class. Currently it is not. The simplest way to fix it is to make it public
  2. Your class loader is trying to load class from the source code itself (the URL to "Solution.java" is provided as class path URL). URLClassLoader actually expects either URL to a jar or a folder with classes.

Please find a corrected version below:

public class CompliedClass {

  public static void main(String[] args) throws Exception {
    String classDef = ""
        + "public class Solution{"
        + "  public int add(){return 1+1;}"
        + "}";

    Path sourceFile = Paths.get("Solution.java");
    Files.write(sourceFile, classDef.getBytes());
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    if (compiler == null) {
      System.out.println("JDK required (running inside of JRE)");
      System.exit(1);
    } else {
      System.out.println("you got it!");
    }

    int compilationResult = compiler.run(null, null, null, sourceFile.toString());
    if (compilationResult == 0) {
      System.out.println("Compilation is successful");
    } else {
      System.out.println("Compilation Failed");
      System.exit(1);
    }

    URL classPath = sourceFile.toAbsolutePath().getParent().toUri().toURL();
    URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{classPath});
    Class<?> cls = Class.forName("Solution", true, classLoader);
    Object instance = cls.newInstance();
    Method method = cls.getDeclaredMethod("add", null);
    System.out.println(method.invoke(instance, null));
  }
}