09 February 2008

Tags: cglib delegate design java pattern

This week I had some third party class I needed to override, but as in many cases, I had to use the delegate design pattern in order to be able to expand the class without modifying the 3rd party library.

However, I came to a problem when the base class used the visitor pattern. The following snippet explains it all:

public class MyBaseClass implements AnInterface {

...

public void accept(Visitor visitor) {
   visitor.visit(this);
}

...

The problem is that if you delegate the doSomething() method, the object which will be passed to the visitor will be the delegate, not your implementation :

public class DelegatingClass implements AnInterface,AnotherInterface {
...
public void visit(Visitor visitor) {
   delegate.visit(visitor); // ok, assume you can't do visitor.visit(this), that would be too easy. Imagine the delegate does more stuff or imagine another method like doSomething() { something.call(this); }
}
}

This leads to situations where the visited object is not the one you’d like to, and it breaks your algorithms where you assume the visited object is your implementation (which adds several features). Futhermore, there are situations when you can’t even delegate. For examples, when extending a class with final methods, or when you try to expand a class which is package protected (there are tons of examples like this in Apache Lucene). I’ve come to a solution using the cglib library. The idea is to create a proxy and dynamically extend the base class with your features. I’ve written a simple utility class which does it all :

package com.lingway.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * This utility class generates cglib proxies which delegates missing methods to instanciated
 * objects.
 * User: Cedric
 * Date: 9 févr. 2008
 * Time: 08:35:20
 */
public class AdvancedEnhancer {
    /**
     * Creates a new instance of a delegating object.
     * @param aSuperClass super class of the generated object.
     * @param someInterfaces interfaces generated object will implement.
     * @param delegates delegates which implements the "missing" methods.
     * @return Object</> delegating object
     */
    public static Object enhanceWithInterfaces(
            Class aSuperClass,
            Class[] someInterfaces,
            Object[] delegates) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(aSuperClass);
        enhancer.setInterfaces(someInterfaces);
        enhancer.setCallback(new InterfaceMethodInterceptor(delegates));
        return enhancer.create();
    }

    /**
     * A convenient creator which assumes a single interface to be implemented
     * by a list of delegates. This allows removing explicit class casting.
     * @param aSuperClass super class of the generated object.
     * @param aTargetInterface target interface
     * @param delegates delegates which implements the "missing" methods.
     * @return T</> delegating object implementing the T interface
     */
    @SuppressWarnings("unchecked")
    public static

And here’s an example of how it works. Pretty easy !

package com.lingway.cglib;

/**
 * A proof of concept of the "cglib delegate pattern"
 * User: Cedric
 * Date: 9 févr. 2008
 * Time: 14:06:56
 */
public class ProofOfConcept {
    public interface ISelf {
        ISelf self(); // basically, returns "this", which is just for the POC :)
    }

    public interface IEcho {
        void echo();
    }

    public interface IMixin extends ISelf,IEcho {}

    public static class Self implements ISelf {
        public ISelf self() {
            return this;
        }
    }

    public static class Echo implements IEcho {
        public void echo() {
            System.out.println("echo !");
        }
    }

    public static void main(String[] args) {
        IEcho echo = new Echo(); // these are the "additional features"
        IMixin mixin = AdvancedEnhancer.enhanceWithTargetInterface(Self.class, IMixin.class, echo);
        System.out.println("mixin.self (shows this is the proxy) = " + mixin.self());
        mixin.echo();
    }
}

In the previous example, I ``enhance'' the Self class with the features of the Echo class. This is really easy, and perfectly solves our problem. Note that technically, the delegate and the delegator are switched.

Enjoy !