Val Blant
Val Blant

Reputation: 1762

Is hot code replace supposed to work for Groovy in Eclipse?

I was wondering if anyone was able to get Groovy hot replace working in Eclipse reliably. I can't find any useful info about this, so I am not sure if it's b/c it's just working for everyone else? Or is nobody using Eclipse to do Groovy development?

I have tried using the latest Eclipse (4.5 Mars) with latest Groovy-Eclipse plugin (Groovy Eclipse 2.9.2 from http://dist.springsource.org/snapshot/GRECLIPSE/e4.5/), and I still can't get reliable hot replace.

Some simple hot replace scenarios work fine. However, just a little bit of complexity leads to strange Groovy exceptions. I get different errors in different situations, but I was able to reproduce one in a simple junit, so I'll demonstrate that one with some simplified domain objects.

HotSwapTests.groovy:

class HotSwapTests {
    @Test
    public void testHotReplace() {
        DefaultTxView transactionGroup = new DefaultTxView();

        List<Default> defaults = [];

        Default d1 = new Default(ProducerAccountTransactionType.REPAID_AMOUNT, ParticipantAccountType.DEFAULT);
        Default d2 = new Default(ProducerAccountTransactionType.REPAID_AMOUNT, ParticipantAccountType.DEFAULT);

        d1.setCancelledDefault(d2);

        defaults << d1;

        transactionGroup.setDefaultTransactions(defaults);


        while (true) {
            Default result = transactionGroup.getRepaymentTransaction();
            println result
        } 
    }
}

DefaultTxView.groovy:

public class DefaultTxView {

    def List<Default> defaultTransactions;

    public Default getRepaymentTransaction() { return getTransactionOfType(REPAID_AMOUNT); }

    public Default getTransactionOfType(ProducerAccountTransactionType type) {
        return defaultTransactions.find { it.getType() == type };
    }

Default.java:

The contents of this domain object are not really important - it's a simple POJO.

Now, to test hotswap I place a breakpoint at the marked line:

while (true) {
    Default result = transactionGroup.getRepaymentTransaction(); <<< break
    println result
} 

And then I go to DefaultTxView.groovy and modify the code inside the closure passed in to the find method:

public Default getTransactionOfType(ProducerAccountTransactionType type) {
    return defaultTransactions.find { it.getType() == type && it.getCancelledDefault() == null};
}

I don't get any warning or error messages when I save the file, but if I attempt to step over the modified line now, I get the following exception:

java.lang.ArrayIndexOutOfBoundsException: 2
    at ca.gc.agr.app.web.jsf.producer.DefaultTxView$_getTransactionOfType_closure1.doCall(DefaultTxView.groovy:15)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:278)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:39)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.BooleanReturningMethodInvoker.invoke(BooleanReturningMethodInvoker.java:48)
    at org.codehaus.groovy.runtime.callsite.BooleanClosureWrapper.call(BooleanClosureWrapper.java:50)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.find(DefaultGroovyMethods.java:3060)
    at org.codehaus.groovy.runtime.dgm$175.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at ca.gc.agr.app.web.jsf.producer.DefaultTxView.getTransactionOfType(DefaultTxView.groovy:15)
    at ca.gc.agr.app.web.jsf.producer.DefaultTxView$getTransactionOfType$1.callCurrent(Unknown Source)
    at ca.gc.agr.app.web.jsf.producer.DefaultTxView.getRepaymentTransaction(DefaultTxView.groovy:11)
    at ca.gc.agr.app.web.jsf.producer.DefaultTxView$getRepaymentTransaction$0.call(Unknown Source)
    at ca.gc.agr.app.web.jsf.temp.HotSwapTests.testHotReplace(HotSwapTests.groovy:29)

I get very similar results when running my webapp in TomCat, with the same exception after modifying that line. Restarting the junit, or TomCat makes the new line work fine, so it's definitely a hot replace issue.

So what am I doing wrong? Any advice would be appreciated.

Upvotes: 1

Views: 487

Answers (1)

Will
Will

Reputation: 14539

I've used hot deploy in a web dev environment with groovy successfully in the past using the eclipse plugin.

IIRC, I used groovyReset.jar, DCEVM and jdk1.7.

groovyReset.jar should be in the classpath and set as java agent. I've used the one found inside the groovy-eclipse plugin folder (like eclipse/plugins/org.codehaus.groovy_2.3.7.xx-201411061335-e44-RELEASE/extras/groovyReset.jar)

-javaagent:/groovyReset.jar

New closures and methods were instantly visible without redeploy. Of course including a simple LOC in a method worked too. Sometimes i needed to restart the VM, but still a breath of fresh air.


In your case, i think at least groovyReset.jar must be present. It is responsible for resetting the metaclass. If you decompile a groovy class you can check method calls being called by reflection using an array of java.lang.Method. Upon hot code swap this array gets out of order, needing a reset.

Upvotes: 1

Related Questions