I have the following method in Java:
public void myMethod(
@ClosureParams(
value = SimpleType.class,
options = {
"java.util.Map"
}
) Closure<String> closure
) {
...
}
which has @ClosureParams
to specify closure's parameter types for static type checker and type inference in IDEA.
In the Groovy script, I call this method as follows:
myMethod { Map<String, Object> doc ->
...
}
and it works fine. But when I try to specify generic types for java.util.Map
of closure in my java method:
public void myMethod(
@ClosureParams(
value = SimpleType.class,
options = {
"java.util.Map<java.lang.String,java.lang.Object>" // <-- added here
}
) Closure<String> closure
) {
...
}
groovy's static type checker fails with error:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\myproject\script.groovy: 1: Expected parameter of type java.util.Map<java.lang.String,java.lang.Object> but got java.util.Map <String, Object>
@ line 1, column 8.
myMethod { Map<String, Object> doc ->
although IDEA infers the type of doc
without any Map
or Map<...>
using @ClosureParams
hint.
When I look into the source of groovy.transform.stc.SimpleType
class, I see that this class does not give the ability to specify generic types as it uses plain Class.forName
:
public class SimpleType extends SingleSignatureClosureHint {
@Override
public ClassNode[] getParameterTypes(final MethodNode node, final String[] options, final SourceUnit sourceUnit, final CompilationUnit unit, final ASTNode usage) {
ClassNode[] result = new ClassNode[options.length];
for (int i = 0; i < result.length; i++) {
result[i] = findClassNode(sourceUnit, unit, options[i]);
}
return result;
}
}
// findClassNode method:
protected ClassNode findClassNode(final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final String className) {
if (className.endsWith("[]")) {
return findClassNode(sourceUnit, compilationUnit, className.substring(0, className.length() - 2)).makeArray();
}
ClassNode cn = compilationUnit.getClassNode(className);
if (cn == null) {
try {
cn = ClassHelper.make(Class.forName(className, false, sourceUnit.getClassLoader()));
} catch (ClassNotFoundException e) {
cn = ClassHelper.make(className);
}
}
return cn;
}
My question: how to specify closure parameter type with generics in groovy? Preferably with support in IDEA.
You can use groovy.transform.stc.FromString
signature hint to get the generic types working. Consider the following example:
JavaClass.java
import groovy.lang.Closure;
import groovy.transform.stc.ClosureParams;
import groovy.transform.stc.FromString;
import java.util.HashMap;
public class JavaClass {
public static void processRendered(@ClosureParams(
value = FromString.class,
options = {"java.util.Map<java.lang.String,java.lang.Object>"}) Closure closure) {
closure.call(new HashMap<String, Object>());
}
}
script.groovy
import groovy.transform.CompileStatic
import static JavaClass.processRendered
@CompileStatic
def test() {
processRendered { Map<String, Object> map ->
map.put("test", 1)
}
processRendered {
it.put("test", 2)
}
}
test()
It compiles and gives you signature hint, also for the implicit it
variable.
The following example uses Groovy 2.5.7.