Reputation: 61
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
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
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