Incompatible change in Groovy 1.7.7
После переноса на другой компьютер одна моя 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" все еще в силе.