javadynamicjava-assist

In Javassist, how to set return type of method to java.lang.Double when creating dynamic method using CtMethod


I have to dynamically create a class and method at run time. I am using Javassist for this:

The method that I want to dynamically create is:

public Double formula1(FormulaAPI api) {  
      return api.evaluate(\"L\"); 
}

If I pass the entire above method as method body using CtMethod.make(..), it works fine.

But I want to make it more generic, i.e. i want to only have return api.evaluate(\"L\"); as my method body and want to use the JavaAssist API methods to set the name, return type etc. When trying to do this, I am getting error :

 ***Exception in thread "main" java.lang.VerifyError: 
    (class: Formulae, method: formula1 signature: (Lcom/formula/FormulaAPI;)D) Wrong return type in function***

My question is : when calling: CtMethod m = new CtMethod(...); how can I set the return type of CtMethod to java.lang.Double instead of CtPrimitiveType.doubleType ?

My entire source code is:

public class JavaAssistTest {
  private static final String className = "Formulae";
  private static final String methodName = "formula1";

  public static void main(String[] args) throws Exception{
      JavaAssistTest jj = new JavaAssistTest();
    try {
      Class clazz = jj.generateClass(className, methodName);

      Object obj = clazz.newInstance();
      Method method = clazz.getDeclaredMethod(methodName,FormulaAPI.class);
      FormulaAPI api = new FormulaAPI();
      method.invoke(obj,api);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    } catch (CannotCompileException e) {
      e.printStackTrace();
    }
  }
  public Class generateClass(String className, String methodName)
      throws CannotCompileException, NotFoundException{
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.makeClass(className);

    ClassLoader classLoader = FormulaAPI.class.getClassLoader();
    pool.appendClassPath(new LoaderClassPath(classLoader));
    pool.importPackage("com.formula");

    String methodBody = "return $1.evaluate(\"L\");";

    CtClass apiClass = pool.get("com.formula.FormulaAPI");

    CtMethod m = new CtMethod(CtPrimitiveType.doubleType,methodName,new CtClass[]{apiClass},cc);
    m.setBody(methodBody);
    cc.addMethod(m);

    return cc.toClass();
  }

}

The FormulaAPI class is as below:

public class FormulaAPI {
    public Double evaluate(String source){
        System.out.println("printing in FormulaAPI ");
        return 2.0;
    }
}

Solution

  • Clearly the CtPrimitiveType.doubleType is not the CtClass type to use since you need the wrapper class instead and that is why your are getting the exception.

    But the solution is very simple you simply need to get the Double type as a CtClass and use it instead. So modify your generate method to look like:

    public Class generateClass(String className, String methodName)
                throws CannotCompileException, NotFoundException {
       ClassPool pool = ClassPool.getDefault();
       CtClass cc = pool.makeClass(className);
    
       ClassLoader classLoader = FormulaAPI.class.getClassLoader();
       pool.appendClassPath(new LoaderClassPath(classLoader));
       pool.importPackage("com.formula");
    
       String methodBody = "return $1.evaluate(\"L\");";
    
       CtClass apiClass = pool.get("com.formula.FormulaAPI");
    
       CtClass doubleWrapperClass = pool.get("java.lang.Double");
    
       CtMethod m = new CtMethod(doubleWrapperClass,methodName,new CtClass[]{apiClass},cc);
       m.setBody(methodBody);
       cc.addMethod(m);
    
       return cc.toClass();
    }
    

    Running your code with these modifications, you should have no exceptions.