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" все еще в силе.