Алексей Владыкин


Incompatible change in Groovy 1.7.7

30 августа 2011 Groovy

После переноса на другой компьютер одна моя Groovy-программа неожиданно перестала работать. Причем ошибка стала возникать в довольно безобидной строчке следующего вида:

text = text.replaceAll(/\$\{(.*?)\}/) {
    String[] m -> getValue(m[1]) }

Смысл этой операции — поиск и замена в тексте всех выражений вида ${something}. Определение функции getValue() роли не играет, т.к. Groovy кидает ошибку, не доходя до ее вызова:

java.lang.ClassCastException: [Ljava.util.ArrayList; cannot be cast to java.util.List
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass$1.invoke(ClosureMetaClass.java:259)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:273)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:886)
    at groovy.lang.Closure.call(Closure.java:282)
    at groovy.lang.Closure.call(Closure.java:295)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.getReplacement(DefaultGroovyMethods.java:4188)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.replaceAll(DefaultGroovyMethods.java:4237)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.replaceAll(DefaultGroovyMethods.java:4132)
    ...

Расследование показало, что фактором, влияющим на возникновение ошибки, является версия Groovy. В версии 1.7.5 код работает, а в версии 1.7.7 — нет. Даже если обе версии установлены на одном компьютере.

Любопытно, что вот такая вариация, где String[] заменен на List,

text = text.replaceAll(/\$\{(.*?)\}/) {
    List m -> getValue(m[1]) }

работает в Groovy 1.7.7, а в 1.7.5 приводит к ошибке

groovy.lang.MissingMethodException: No signature of method: B$_run_closure1.doCall() is applicable for argument types: (java.lang.String, java.lang.String) values: [${a}, a]
Possible solutions: doCall(java.util.List)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:266)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:886)
    at groovy.lang.Closure.call(Closure.java:276)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.replaceAll(DefaultGroovyMethods.java:4055)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.replaceAll(DefaultGroovyMethods.java:4011)
    ...

Таким образом, имеется несовместимость между версиями Groovy 1.7.5 и 1.7.7 (а может еще и 1.7.6, которую я не проверял). Не всякий код, написанный под одну версию, будет работать с другой. По этому случаю я завел баг GROOVY-4676.

К счастью, есть как минимум два способа объявить замыкание таким образом, что его поймут обе версии Groovy.

Первый способ: вместо массива или списка параметров явно объявляем два параметра, с типом или без.

text = text.replaceAll(/\$\{(.*?)\}/) {
    String m0, String m1 -> getValue(m1) }

Второй способ: вместо массива строк используем массив объектов Object[].

text = text.replaceAll(/\$\{(.*?)\}/) {
    Object[] m -> getValue(m[1]) }

В общем, решение проблемы оказалось простым, однако осадок остался. Похоже, принцип "Write Once, Debug Everywhere" все еще в силе.

К оглавлению блога