GroovyCastException while running java code containing new line "\n" with groovy script engine(GroovyClassLoader)

3.9k views Asked by At

Currently I am working on a way to run java code in string form. So here is how I did it.

import java.util.HashMap;
import java.util.Map;

import groovy.lang.GroovyClassLoader;

public class GroovyStackOverflow {

    public static void main(String[] args) {
        GroovyClassLoader gcl = new GroovyClassLoader();
        String codeSnippet = "double calculatedAnswer = (Double)"
                + "contextMap.get(\"doubleValue\") * (Double)contextMap.get(\"doubleValue\");"
                + " calculatedAnswer = Math.sqrt(calculatedAnswer); "
                + "calculatedAnswer = calculatedAnswer * calculatedAnswer;"
                + "System.out.println(calculatedAnswer);"
                + " return calculatedAnswer;";
        StringBuilder sb = new StringBuilder();
        sb.append("public class ScriptImplementor implements ScriptEvaluator { public Object evaluate(Map contextMap) {");
        sb.append(codeSnippet);
        sb.append("} }");
        Class<?> clazz = gcl.parseClass(sb.toString());
        ScriptEvaluator scriptEvaluator = null;     
        double calculatedAnswer = 100.0;        
        try {
            Map contextMap = new HashMap();
            contextMap.put("doubleValue", (double)100.0);
            contextMap.put("threadId", "thread"+100);
            contextMap.put("hashCode", 100);
            scriptEvaluator = (ScriptEvaluator) clazz.newInstance();
            scriptEvaluator.evaluate(contextMap);;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

public interface ScriptEvaluator {
    public Object evaluate(Map contextMap);
}

Problem is that it fails in following case.

import java.util.HashMap;
import java.util.Map;

import groovy.lang.GroovyClassLoader;

public class GroovyStackOverflow {

    public static void main(String[] args) {
        GroovyClassLoader gcl = new GroovyClassLoader();
        String codeSnippet = "double calculatedAnswer = (Double)"
                + "\n "
                + "contextMap.get(\"doubleValue\") * (Double)contextMap.get(\"doubleValue\");"
                + " calculatedAnswer = Math.sqrt(calculatedAnswer); "
                + "calculatedAnswer = calculatedAnswer * calculatedAnswer;"
                + "System.out.println(calculatedAnswer);"
                + " return calculatedAnswer;";
        StringBuilder sb = new StringBuilder();
        sb.append("public class ScriptImplementor implements ScriptEvaluator { public Object evaluate(Map contextMap) {");
        //sb.append(codeSnippet.replaceAll("\n", " "));
        sb.append(codeSnippet);
        sb.append("} }");
        Class<?> clazz = gcl.parseClass(sb.toString());
        ScriptEvaluator scriptEvaluator = null;     
        double calculatedAnswer = 100.0;
        try {
            Map contextMap = new HashMap();
            contextMap.put("doubleValue", (double)100.0);
            contextMap.put("threadId", "thread"+100);
            contextMap.put("hashCode", 100);
            scriptEvaluator = (ScriptEvaluator) clazz.newInstance();
            scriptEvaluator.evaluate(contextMap);;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
public interface ScriptEvaluator {
    public Object evaluate(Map contextMap);
}

I don't understand why it fails and what this error message means-

Exception in thread "main" org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'class java.lang.Double' with class 'java.lang.Class' to class 'double'
    at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToNumber(DefaultTypeTransformation.java:163)
    at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.doubleUnbox(DefaultTypeTransformation.java:88)
    at ScriptImplementor.evaluate(script15126616543572010791987.groovy:1)
    at GroovyStackOverflow.main(GroovyStackOverflow.java:33)

After uncommenting this code //sb.append(codeSnippet.replaceAll("\n", " ")); it works. But please suggest a better way to handle it. Also why it does not give error while parsing class? And what other surprises I can expect like this?

1

There are 1 answers

1
Vampire On BEST ANSWER

You hit a difference between Java and Groovy.

In Java a statement is ended by a semicolon.

In Groovy a satement is ended by a semicolon, or by a linebreak if the statement already is a complete statement.

In your case this means the code

double calculatedAnswer = (Double)
contextMap.get("doubleValue") * (Double)contextMap.get("doubleValue")

is two statements.

The first of these statements is double calculatedAnswer = (Double).

In Groovy you can also omit .class to reference a class, so Double.class can be written as Double.

So what you do in that statement is, that you assign the Double class object to a double variable. The parentheses are just no-ops here.

This of course fails like the message says, as the Double class object cannot be case automatically to a double.

You can explicitly escape a linebreak to make it not end a statement like in

double calculatedAnswer = (Double)\
contextMap.get("doubleValue") * (Double)contextMap.get("doubleValue")

which would work like you expected.

But there can of course be other cases where Groovy and Java are different.

Always remember, the Groovy syntax is close to the Java syntax, yet not identical.

Afair each valid Java code is also valid Groovy code, but not necessarily with the exact same meaning as you can see in this example.