DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Java Mixins With Code Generation

09.05.2006
| 3224 views |
  • submit to reddit
        // Ruby style mixin in Java using dynamic code generation

import net.sf.cglib.core.*;
import net.sf.cglib.asm.ClassVisitor;
import net.sf.cglib.asm.Type;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

public class MixinTest
{
  /*
   * The Demo
   */
  public interface Chimera extends Lion, Goat, Serpent, Animal
  {
  }

  public static void main(String[] args)
  {
    Chimera chimera = (Chimera) MixinFactory.create(Chimera.class, new AnimalImpl());
    System.out.println("The Chimera: ");
    chimera.roar();
    chimera.stomp();
    chimera.slither();
    chimera.action("Sing a song");

    System.out.println("Reflection Mixin");
    timeMixin();
    timeMixin();
    timeMixin();
    System.out.println("CGLib Mixin");
    timeCGMixin();
    timeCGMixin();
    timeCGMixin();
    System.out.println("Plain method call");
    timeNormal();
    timeNormal();
    timeNormal();
  }

  private static void timeMixin()
  {
    Chimera chimera = (Chimera) MixinFactory.create(Chimera.class, new AnimalImpl());
    int sampleSize = 1000000;
    long start = System.currentTimeMillis();
    for (int i = 0; i < sampleSize; i++)
    {
      chimera.roar();
    }
    long total = System.currentTimeMillis() - start;
    System.out.println("Total = " + total);
  }

  private static void timeCGMixin()
  {
    Chimera chimera = (Chimera) CGMixinFactory.create(Chimera.class, new AnimalImpl());
    int sampleSize = 1000000;
    long start = System.currentTimeMillis();
    for (int i = 0; i < sampleSize; i++)
    {
      chimera.roar();
    }
    long total = System.currentTimeMillis() - start;
    System.out.println("Total = " + total);
  }

  private static void timeNormal()
  {
    int sampleSize = 1000000;
    Lion lion = new LionImpl(new AnimalImpl());
    long start = System.currentTimeMillis();
    for (int i = 0; i < sampleSize; i++)
    {
      lion.roar();
    }
    long total = System.currentTimeMillis() - start;
    System.out.println("Total = " + total);
  }

  /*
   * The Interfaces
   */
  public interface Lion
  {
    void roar();
  }

  public interface Goat
  {
    void stomp();
  }

  public interface Serpent
  {
    void slither();
  }

  public interface Animal
  {
    void action(String description);
  }

  /*
   * The Implementations
   */
  public static class LionImpl implements Lion
  {
    private Animal animal;

    public LionImpl(Animal animal)
    {
      this.animal = animal;
    }

    public void roar()
    {
      animal.action("Roars with lion head");
    }
  }

  public static class GoatImpl implements Goat
  {
    private Animal animal;

    public GoatImpl(Animal animal)
    {
      this.animal = animal;
    }

    public void stomp()
    {
      animal.action("Stomps with goat foot");
    }
  }

  public static class SerpentImpl implements Serpent
  {
    private Animal animal;

    public SerpentImpl(Animal animal)
    {
      this.animal = animal;
    }

    public void slither()
    {
      animal.action("Slithers with serpent tail");
    }
  }

  public static class AnimalImpl implements Animal
  {
    String last;

    public void action(String description)
    {
      last = description;
      //System.out.println(description);
    }
  }


  public static class CGMixinFactory
  {
    public static Object create(Class interfaceClass, Object core)
    {
      CGGenerator generator = new CGGenerator(interfaceClass, delegates(interfaceClass, core).toArray());
      return generator.create();
    }

    private static Collection delegates(Class interfaceClass, Object core)
    {
      Collection delegates = new HashSet();
      for (int i = 0; i < interfaceClass.getInterfaces().length; i++)
      {
        Class anInterface = interfaceClass.getInterfaces()[i];
        try
        {
          Class delegateClass = Class.forName(anInterface.getName() + "Impl");
          Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, core.getClass());
          if (delegateConstructor != null)
          {
            delegates.add(delegateConstructor.newInstance(new Object[]{core}));
          }
          else
          {
            delegates.add(core);
          }
        }
        catch (Exception e)
        {
          throw new RuntimeException(e);
        }
      }
      return delegates;
    }

    private static Constructor findBestMatchConstrustor(Class delegateClass, Class coreClass)
    {
      if (coreClass == null)
      {
        return null;
      }
      try
      {
        return delegateClass.getConstructor(new Class[]{coreClass});
      }
      catch (NoSuchMethodException e)
      {
        Class[] interfaces = coreClass.getInterfaces();
        for (int i = 0; interfaces != null && i < interfaces.length; i++)
        {
          Class interfaceClass = interfaces[i];
          Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, interfaceClass);
          if (delegateConstructor != null)
          {
            return delegateConstructor;
          }
        }
        Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, coreClass.getSuperclass());
        if (delegateConstructor != null)
        {
          return delegateConstructor;
        }
      }
      return null;
    }
  }

  public static class CGGenerator extends AbstractClassGenerator
  {
    private static final MixinKey KEY_FACTORY =
      (MixinKey)KeyFactory.create(MixinKey.class, KeyFactory.CLASS_BY_NAME);

    interface MixinKey
    {
      public Object newInstance(Class classes, int[] route);
    }

    private static final Source SOURCE = new Source(CGMixinFactory.class.getName());
    private Class interfaceClass;
    private Object[] delegates;
    private int route[];
    private Method methods[];

    public CGGenerator(Class interfaceClass, Object delegates[])
    {
      super(SOURCE);
      this.interfaceClass = interfaceClass;
      this.delegates = delegates;
      methods = interfaceClass.getMethods();
      calculateRoute();
    }

    private void calculateRoute()
    {
      route = new int[methods.length];
      Map delegateIndexes = calculateDelegateIndexes();
      for (int i = 0; i < methods.length; i++)
      {
        route[i] = ((Integer)delegateIndexes.get(methods[i].getDeclaringClass().getName())).intValue();
      }
    }

    private Map calculateDelegateIndexes()
    {
      Map delegateIndexes = new HashMap();
      for (int i = 0; i < delegates.length; i++)
      {
        delegateIndexes.put(interfaceNameForImpl(delegates[i]), new Integer(i));
      }
      return delegateIndexes;
    }

    private String interfaceNameForImpl(Object delegate)
    {
      return delegate.getClass().getName().replaceAll("Impl", "");
    }

    public Object create()
    {
      return super.create(KEY_FACTORY.newInstance(interfaceClass, route));
    }
    protected ClassLoader getDefaultClassLoader()
    {
      return interfaceClass.getClassLoader();
    }

    protected Object firstInstance(Class type) throws Exception
    {
      return ReflectUtils.newInstance(type, new Class[] {Object[].class}, new Object[] {delegates});
    }

    protected Object nextInstance(Object instance) throws Exception
    {
      return firstInstance(instance.getClass());
    }

    public void generateClass(ClassVisitor classVisitor) throws Exception
    {
      new CGMixinEmitter(classVisitor, interfaceClass, getClassName(), methods, route);
    }
  }

  public static class CGMixinEmitter extends ClassEmitter
  {
    private static final String FIELD_NAME = "CGLIB$DELEGATES";
    private static final Signature CSTRUCT_OBJECT_ARRAY = TypeUtils.parseConstructor("Object[]");

    public CGMixinEmitter(ClassVisitor classVisitor, Class interfaceClass, String className, Method methods[], int route[])
    {
      super(classVisitor);

      begin_class(Constants.V1_3,
                  Constants.ACC_PUBLIC,
                  className,
                  getMixinSuperType(),
                  TypeUtils.getTypes(new Class[]{interfaceClass}),
                  Constants.SOURCE_FILE);

      declare_field(Constants.ACC_PRIVATE, FIELD_NAME, Constants.TYPE_OBJECT_ARRAY, null, null);

      CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT_ARRAY, null, null);
      e.load_this();
      e.super_invoke_constructor();
      e.load_this();
      e.load_arg(0);
      e.putfield(FIELD_NAME);
      e.return_value();
      e.end_method();

      for (int j = 0; j < methods.length; j++)
      {
        MethodInfo method = ReflectUtils.getMethodInfo(methods[j]);
        e = EmitUtils.begin_method(this, method, Constants.ACC_PUBLIC);
        e.load_this();
        e.getfield(FIELD_NAME);
        e.aaload(route[j]);
        e.checkcast(method.getClassInfo().getType());
        e.load_args();
        e.invoke(method);
        e.return_value();
        e.end_method();
      }

      end_class();
    }

    private Type getMixinSuperType()
    {
      return TypeUtils.getTypes(new Class[]{Object.class})[0];
    }
  }

  /*
   * The Guts
   */
  public static class MixinFactory
  {
    private static class MixinInvocationHandler implements InvocationHandler
    {
      private Map implMap = new HashMap();
      private Map methodCache = new HashMap();

      public MixinInvocationHandler(Class clazz, Object core)
      {
        for (int i = 0; i < clazz.getInterfaces().length; i++)
        {
          Class anInterface = clazz.getInterfaces()[i];
          try
          {
            Class delegateClass = Class.forName(anInterface.getName() + "Impl");
            Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, core.getClass());
            if (delegateConstructor != null)
            {
              implMap.put(anInterface, delegateConstructor.newInstance(new Object[]{core}));
            }
            else
            {
              implMap.put(anInterface, core);
            }
          }
          catch (Exception e)
          {
            throw new RuntimeException(e);
          }
        }
      }

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
      {
        Object delegate = implMap.get(method.getDeclaringClass());

        return getImplMethod(delegate.getClass(), method).invoke(delegate, args);
      }

      private Method getImplMethod(Class implClass, Method method) throws NoSuchMethodException
      {
        if (methodCache.containsKey(method))
        {
          return (Method) methodCache.get(method);
        }
        else
        {
          Method declaredMethod = implClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
          methodCache.put(method, declaredMethod);
          return declaredMethod;
        }
      }

      private static Constructor findBestMatchConstrustor(Class delegateClass, Class coreClass)
      {
        if (coreClass == null)
        {
          return null;
        }
        try
        {
          return delegateClass.getConstructor(new Class[]{coreClass});
        }
        catch (NoSuchMethodException e)
        {
          Class[] interfaces = coreClass.getInterfaces();
          for (int i = 0; interfaces != null && i < interfaces.length; i++)
          {
            Class interfaceClass = interfaces[i];
            Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, interfaceClass);
            if (delegateConstructor != null)
            {
              return delegateConstructor;
            }
          }
          Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, coreClass.getSuperclass());
          if (delegateConstructor != null)
          {
            return delegateConstructor;
          }
        }
        return null;
      }
    }

    public static Object create(Class clazz, Object core)
    {
      return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
                                    new MixinInvocationHandler(clazz, core));
    }
  }
}