atul
atul

Reputation: 61

methodMissing closure - not encapsulated?

I understand groovy basics - and closures...

I am trying to call groovy from java:

// Patient.java

public class Patient {  
    //... other data  
    private Map<String, String> attribStore = new HashMap<String,String>();  
    // getters/setters for attribStore omitted  

    public void addAttribute(String key, String val) {
        if (!attribStore.containsKey(key)) {
            attribStore.put(key, val);
        }
    }

// GroovyHelper.java

public class GroovyHelper {
    private String codeSnippet; // groovy script code

    public String evaluateSnippetToString(Binding binding) {
        addMethodMissingHandler();
        GroovyShell shell = createGroovyShell(binding);
        Object result = shell.evaluate(codeSnippet);
        return result.toString();
    }

    // installs a patient in the binding - accesses the patient  
    // attribStore from groovy
    // The missing method is used to create an "attribute" i.e.
    // a (key,val) pair in the patient map 
    private void addMethodMissingHandler() {
        codeSnippet = "def attribStore = p.getAttribStore();\n"
        + "Patient.metaClass.methodMissing = \n{"
        + " String methodName, args -> \n"
        + "methodName = methodName.replaceFirst(/^get/, '');\n"  
        + "def attrib = methodName[0].toLowerCase() + methodName.substring(1);\n"
        + "if (!attribStore.containsKey(attrib)) { attribStore[attrib] = '0'; }\n"
        + "return attribStore[attrib]; \n" + "}\n" + codeSnippet;
    }  
}

// junit test code

private Patient p;
private Binding binding;
private GroovyHelper gh;

    @Before 
    public void init() {
        p = new PatientBuilder().build();
        binding = new Binding();
        binding.setVariable("p", p);
        gh = new GroovyHelper();
    }

    @Test //passes
    public void testPopulatePatientAttribStore() throws Exception {
        p.addAttribute("xyz", "4");
        gh.setCodeSnippet("p.getXyz()");
        gh.evaluateSnippetToString(binding);
    }

    @Test
    public void testGroovy() throws Exception {
        Binding binding = new Binding();
        binding.setVariable("p", new Patient()); // new patient
        p.addAttribute("xyz", "9");

        GroovyShell gs1 = new GroovyShell(binding);

        assertEquals("9", gs1.evaluate("p.getXyz()")); // fails??? - expected: <[9]> but was: <[4]>
    }

My question is - is the closure holding on to the attribute store of an earlier binding?
What exactly is happening here?
Apologies all for the longish code - I worked on it
- cut down irrelevant code
to shrink it to a minimum - any pointer, "more reading to do" hints?

Upvotes: 0

Views: 401

Answers (2)

Brian Henry
Brian Henry

Reputation: 3171

It certainly appears that the MetaClass for Patient is surviving b/w GroovyShell runs - the MetaClassRegistry may be a singleton and thus live for the life of the classloader or JVM (can't quite think through how this works in the JVM at the moment). And, as you suggest, I believe your methodMissing closure closes around the attribStore variable it originally gets. (a quick test seems to indicate you should have access to the p in the binding from within that closure. I'm guessing you tried that and it didn't work?). One option might be to forcibly remove the MetaClass from Patient after each test in a teardown method.

Upvotes: 0

Ian Roberts
Ian Roberts

Reputation: 122424

I think part of the problem is that you're creating a methodMissing in the Patient metaclass that delegates to the attribStore of one specific patient. I would approach the problem in a different way - is it an option to implement methodMissing directly in the Patient class itself?

public class Patient {
  // other members as before

  public Object methodMissing(String name, Object[] args) {
    if(name != null && name.startsWith("get") && name.length() > 3) {
      String attrName = name.substring(3,1).toLowerCase() + name.substring(4);
      addAttribute(attrName, "0");
      return attribStore.get(attrName);
    } else {
      throw new MissingMethodException(name, this.getClass(), args);
    }
  }
}

Or if this is not an option, could you implement the GroovyHelper class in Groovy (compile it using groovyc and you can call into it from Java the same as any Java class)?

public class GroovyHelper {
    static {
      // add a methodMissing to the Patient metaclass
      Patient.metaClass.methodMissing = { String name, args ->
        if(name?.startsWith("get") && name.length() > 3) {
          String attrName = name.substring(3,1).toLowerCase() + name.substring(4)
          // delegate here is the particular Patient instance on which the
          // missing method call was made
          delegate.addAttribute(attrName, "0")
          return delegate.attribStore[attrName];
        } else {
          throw new MissingMethodException(name, this.getClass(), args);
        }
      }
    }

    private String codeSnippet // groovy script code

    public String evaluateSnippetToString(Binding binding) {
        GroovyShell shell = createGroovyShell(binding)
        Object result = shell.evaluate(codeSnippet)
        return result.toString()
    }
}

Upvotes: 1

Related Questions