Stmated
Stmated

Reputation: 495

Intercept all method calls for all metaclasses in groovy

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

Answers (1)

Stmated
Stmated

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

Related Questions