Reputation: 495
I am trying to intercept all method calls inside Groovy scripts running in a Java environment.
Specially I want to check the return type of all method calls, and if it is X
I want to replace it with Y
.
I know you can intercept using invokeMethod
on the MetaClass, but I can only do that to the script class that I compile. If the script in turn calls a method on class A
then it will create a new MetaClass[A]
that I can't intercept without earlier having manually fetched that MetaClass from the registry and overriding it with a meta method.
I have tried to use GroovySystem.getMetaClassRegistry()
to add listeners for when MetaClasses are created, to add meta methods there, but it seemingly never fires.
I would like this to be automatic, and not in advance having to add an annotation to the methods that I should transform, or knowing which classes' methods I want to transform. All methods that return X
should return Y
.
Can I globally intercept all method calls?
Can I intercept MetaClass creation instead?
Can I add automatic AST transformations?
Upvotes: 2
Views: 710
Reputation: 495
Turns out this is possible. You need to replace the MetaClassCreationHandle by calling GroovySystem.getMetaClassRegistry().setMetaClassCreationHandle
.
Since the base class MetaClassCreationHandle
is package private, it might be easier to extend from ExpandoMetaClassCreationHandle
instead. But take into consideration that it might be overkill for your most common need to make all your classes be based on the ExpandoMetaClass
. So what I did was something like this:
GroovySystem.getMetaClassRegistry().setMetaClassCreationHandle(new ExpandoMetaClassCreationHandle() {
@Override
protected MetaClass createNormalMetaClass(Class theClass, MetaClassRegistry registry) {
final List<Method> propertyMethods = new ArrayList<>();
for (Method method : theClass.getDeclaredMethods()) {
if (method.getReturnType() == TheMethodReturnTypeYouCareAbout.class) {
propertyMethods.add(method);
}
}
final MetaClass mc;
if (propertyMethods.isEmpty() == false) {
final ExpandoMetaClass expando = new ExpandoMetaClass(theClass, true, true);
for (Method mm : propertyMethods) {
final ClassInfo ci = ClassInfo.getClassInfo(mm.getDeclaringClass());
expando.addMetaMethod(new MetaMethod() {
@Override
public int getModifiers() {
return mm.getModifiers();
}
@Override
public String getName() {
return mm.getName();
}
@Override
public Class getReturnType() {
return mm.getReturnType();
}
@Override
public CachedClass getDeclaringClass() {
return ci.getCachedClass();
}
@Override
protected Class[] getPT() {
return mm.getParameterTypes();
}
@Override
public Object invoke(Object object, Object[] arguments) {
try {
final Object value = mm.invoke(object, arguments);
// Do whatever you need with the value.
return value;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
});
}
mc = expando;
} else if (GeneratedClosure.class.isAssignableFrom(theClass)) {
mc = new ClosureMetaClass(registry, theClass);
} else {
mc = new MetaClassImpl(registry, theClass);
}
return mc;
}
});
This means that we will only create Expando classes for those that we have a need of adding meta methods to.
Upvotes: 2