1

I have a String that I need to convert to java class, compile and create an instance at run time: Suppose my String is:

String s = " public class Test {
  public Double add(Double x, Double y){
    return (x+y);
  }
}"

How can I convert it to a class Test.class, instantiate it and call the method add(Double x, Double y) at run time?

I read about Byte Buddy , but the examples I see has a class already defined. In a situation like the above, could anyone give an example how can I use ByteBuddy or any other libraries through which I can achieve this?

Any inputs or suggestions on how to convert this String to compilable and instantiable java class would be helpful.

Kiran Mistry
  • 2,614
  • 3
  • 12
  • 28
javaseeker
  • 73
  • 1
  • 9
  • You're going to have to invoke the Java compiler somehow. Which means bundling it with the application. At that point, you can just invoke it through the shell or however else you like. – Antimony Mar 13 '20 at 04:31
  • Can you explain why you need to do this? – Alfredo Awesome Monazite Mar 13 '20 at 04:40
  • 1
    There are lots of examples, like [this](https://stackoverflow.com/a/39255177/2711488), [that](https://stackoverflow.com/a/41003305/2711488), [here](https://stackoverflow.com/a/48888184/2711488), and [there](https://stackoverflow.com/a/56466155/2711488), even addressing potential follow-up questions. – Holger Mar 13 '20 at 08:58

2 Answers2

3

Can we use something like this :

package com.demo;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;

public class StringToClass
{
    public static void main(String[] args)
    {
        String s = "import com.demo.FormulaAPI; public class FormulaExecutor" +
                " { public Double formula1(FormulaAPI apiReference)" +
                " { System.out.println(apiReference.evaluate(\"10.10\"));  return apiReference.evaluate(\"10.10\"); } }";
        try
        {
            dynamicClass(s, "FormulaExecutor");
        } catch (IOException | NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e)
        {
            e.printStackTrace();
        }

    }

    static void dynamicClass(String sourceCode, String className) throws IOException, NoSuchMethodException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException
    {
        File parent = new File(System.getProperty("user.dir"));
        File sourceFile = new File(parent, className + ".java");
        sourceFile.deleteOnExit();

        FileWriter writer = new FileWriter(sourceFile);
        writer.write(sourceCode);
        writer.close();

        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);
        File parentDir = sourceFile.getParentFile();
        standardJavaFileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(parentDir));
        Iterable<? extends JavaFileObject> compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile));
        javaCompiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits).call();
        standardJavaFileManager.close();

        URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[] {parentDir.toURI().toURL()});
        Class<?> dynamicClass = urlClassLoader.loadClass(className);


        Method formula1 = dynamicClass.getDeclaredMethod("formula1", FormulaAPI.class);
        formula1.invoke(dynamicClass.newInstance(), new Object[] {new FormulaAPI()});
    }


}

package com.demo;

public class FormulaAPI
{
    public Double evaluate(String str)
    {
        return Double.valueOf(str);
    }
}

For Now, Method name is hardcoded

        Method addMethod = dynamicClass.getDeclaredMethod("add", Double.class, Double.class);

We can even generate it at runTime using reflection

We can import the class in the source code.

  • Thank you for suggesting the solution. I tried the code, but getting the error java.lang.ClassNotFoundException: on the line : urlClassLoader.loadClass – javaseeker Mar 13 '20 at 07:59
  • I ran it my local machine. It worked for me. I'm using macbook and intellij for development. Can you please share the stack trace. @javaseeker – Ajay Singh Pundir Mar 13 '20 at 08:23
  • The example that you mentioned is working perfect. What I see is a warning - stack trace below, Any idea why? Cannot paste the entire stack trace as there is word limit in comments, i will paste here in parts – javaseeker Mar 13 '20 at 22:24
  • <..the path..>FormulaExecutor.java:1: warning: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment – javaseeker Mar 13 '20 at 22:26
  • My source String is: `public class FormulaExecutor { public Double formula1(FormulaAPI apiReference) { return apiReference.evaluate("L"); } }` – javaseeker Mar 13 '20 at 22:27
  • I am referring to another class -FormulaAPI -in my source code – javaseeker Mar 13 '20 at 22:28
  • Caused by: java.lang.ClassNotFoundException: com.sun.tools.javac.processing.JavacProcessingEnvironment at java.lang.ClassLoader.findClass(ClassLoader.java:530) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at lombok.launch.ShadowClassLoader.loadClass(ShadowClassLoader.java:373) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) – javaseeker Mar 13 '20 at 22:29
  • this is getting printed in console as a warning – javaseeker Mar 13 '20 at 22:29
  • I have updated the code by adding the import in the source code string . As we need, to import `FormulaAPI` class. Can you please have a look in this code? Let me know, if any issue. @javaseeker – Ajay Singh Pundir Mar 14 '20 at 03:49
1

Byte Buddy works on the byte code level and does not process source code. You can use Javassist for this purpose which offers limited processing of source code as it ships its own compiler.

Alternatively, use the Java compiler API as suggested.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192