The Artima Developer Community
Sponsored Link

Doing Things with Java that Should Not Be Possible
Object Adapter based on Dynamic Proxy
by Heinz Kabutz
May 11, 2005
Summary
The Object Adapter Design Pattern is often used for modifying an interface into another interface that the client expects. A drawback with this pattern is that you need to implement all the methods of the target interface. In this newsletter, solve the problem with dynamice proxies

Advertisement

In the latest upgrade of my website (many more to come soon) I have added an RSS feed for The Java(tm) Specialists' Newsletter for those who would prefer receiving the newsletter via RSS rather than as an email. The RSS feed will always only have the most current newsletter on it, but the old issues will, as always, still be available for free on our website.

Object Adapter based on Dynamic Proxy

A few weeks ago, I presented my Design Patterns Course to a wonderfully inspiring audience in Austria. One of the three Doctors of Computer Science in the course, Dr Klaus Wiederaenders suggested an approach to solve a problem that had been bugging me with the Object Adapter Pattern.

There are two different types of Adapter Design Patterns: Object Adapter and Class Adapter. The Object Adapter has the advantage that it can be used to adapt a whole hierarchy of objects, whereas the Class Adapter has the advantage that you do not need to override all the methods.

Application of Object Adapter

Java 5 has a neat little feature, that is not widely known. You can change the return type of overridden methods. For example, clone() now returns the correct type of the object, so you do not need to downcast the result anymore.

One of my annoyances with Java has been that Collection.toArray() returns an Object[] and not the correct type. Say you have a collection containing Strings, then you have to pass a String[] into the toArray() method. This seems clumsy to me. It would have been nice if this had been changed in Java 5. However, generics cannot solve the problem due to erasure. There is no handle to the type of generic, once the code has been compiled. You therefore have to change the construction of the collection object to also have a handle to the class of the generic type.

My first solution was to write a Class Adapter, which extended java.util.ArrayList with my adapter, which I called BetterArrayList. I have put these classes in a package so that we can do static imports later on:

package com.maxoft.tjsn.util;

import java.lang.reflect.Array;
import java.util.*;

public class BetterArrayList extends ArrayList {
  private final Class valueType;
  public BetterArrayList(int initialCapacity, Class valueType) {
    super(initialCapacity);
    this.valueType = valueType;
  }
  public BetterArrayList(Class valueType) {
    this.valueType = valueType;
  }
  public BetterArrayList(Collectionextends T> ts,
                         Class valueType) {
    super(ts);
    this.valueType = valueType;
  }
  // You can modify the return type of an overridden method in
  // Java 5, with some restrictions.
  public T[] toArray() {
    return toArray((T[]) Array.newInstance(valueType, size()));
  }
}

We can now use this in our code instead of the ArrayList, and then we do not need to have such an awkward syntax for converting it to a type-safe array. We have to pass the class object into the constructor, but the compiler checks that it is the correct class object for the generic type.

package com.maxoft.tjsn.util;

public class BetterArrayListTest {
  public static void main(String[] args) {
    BetterArrayList names =
        new BetterArrayList(String.class);
    names.add("Wolfgang");
    names.add("Leander");
    names.add("Klaus");
    names.add("Reinhard");
    String[] nameArray = names.toArray();
    for (String s : nameArray) {
      System.out.println(s);
    }
  }
}

This would be a reasonable solution if we only ever wanted to use ArrayLists. However, we have to write a class adapter for every collection class that we might want to use.

Here is a new interface, called the BetterCollection, that extends the Collection interface, and changes the return type of the toArray() method:

package com.maxoft.tjsn.util;

import java.util.Collection;

public interface BetterCollection  extends Collection {
  T[] toArray();
}

We can then implement that interface in an object adapter (note how much more code this is!):

package com.maxoft.tjsn.util;

import java.lang.reflect.Array;
import java.util.*;

public class BetterCollectionObjectAdapter
    implements BetterCollection {
  private final Collection adaptee;
  private final Class valueType;
  public BetterCollectionObjectAdapter(Collection adaptee,
                                       Class valueType) {
    this.adaptee = adaptee;
    this.valueType = valueType;
  }
  public T[] toArray() {
    return adaptee.toArray((T[]) Array.newInstance(valueType,
        adaptee.size()));
  }
  // this is a typical problem with the Object Adapter Design
  // Pattern - you have implement all the methods :-(
  public int size() {
    return adaptee.size();
  }
  public boolean isEmpty() {
    return adaptee.isEmpty();
  }
  public boolean contains(Object o) {
    return adaptee.contains(o);
  }
  public Iterator iterator() {
    return adaptee.iterator();
  }
  public  T[] toArray(T[] ts) {
    return adaptee.toArray(ts);
  }
  public boolean add(T t) {
    return adaptee.add(t);
  }
  public boolean remove(Object o) {
    return adaptee.remove(o);
  }
  public boolean containsAll(Collection c) {
    return adaptee.containsAll(c);
  }
  public boolean addAll(Collection ts) {
    return adaptee.addAll(ts);
  }
  public boolean removeAll(Collection c) {
    return adaptee.removeAll(c);
  }
  public boolean retainAll(Collection c) {
    return adaptee.retainAll(c);
  }
  public void clear() {
    adaptee.clear();
  }
}

We can use this as an adapter for any type of collection, for example:

package com.maxoft.tjsn.util;

import java.util.LinkedList;

public class BetterCollectionTest {
  public static void main(String[] args) {
    BetterCollection names =
      new BetterCollectionObjectAdapter(
        new LinkedList(), String.class);
    names.add("Wolfgang");
    names.add("Leander");
    names.add("Klaus");
    names.add("Reinhard");
    String[] nameArray = names.toArray();
    for (String s : nameArray) {
      System.out.println(s);
    }
  }
}
  

This solution works, but I dont like being exposed to changes in the interface. Should Sun ever decide to add a new method to the Collection interface, our class would not compile anymore. Also, it is a lot of code to implement all those methods, and to support the extended interfaces of List, Set, SortedSet, etc., I would need to again write other adapters. I know the chance of Sun changing java.util.Collection is rather remote, but I did have this experience a few times with the java.sql.Connection interface that I had adapted.

Dynamic Object Adapter using Dynamic Proxies

Now that we have seen the problem, let's examine the solution based on dynamic proxies (with thanks to Dr Klaus Wiederaenders for the idea). In the past I have written several object adapters based on interfaces. Besides being a lot of boring work, we experience pain new methods are added to the interface. It can easily occur that your object adapter then only works for one specific version of Java.

The first piece of the puzzle is a DynamicObjectAdapterFactory. This contains the method adapt, which takes an adaptee (the object that we are adapting), the target (the interface that we want to return) and the adapter (the object that contains methods which override adaptee behaviour). We then create a dynamic proxy of the target interface. The invocation handler gets called whenever a method is called on the dymanic proxy. Each of the declared methods in the adapter is put into a map using an identifier that is based on the name and parameter list of the method. This way, there does not have to be an inheritance relationship between the adapter and the target.

package com.maxoft.tjsn.util;

import java.lang.reflect.*;
import java.util.*;

public class DynamicObjectAdapterFactory {
  public static  T adapt(final Object adaptee,
                            final Class target,
                            final Object adapter) {
    return (T) Proxy.newProxyInstance(
        Thread.currentThread().getContextClassLoader(),
        new Class[]{target},
        new InvocationHandler() {
          private Map adaptedMethods =
              new HashMap();
          // initializer block - find all methods in adapter object
          {
            Method[] methods = adapter.getClass().getDeclaredMethods();
            for (Method m : methods) {
              adaptedMethods.put(new MethodIdentifier(m), m);
            }
          }
          public Object invoke(Object proxy, Method method,
                               Object[] args) throws Throwable {
            try {
              Method other = adaptedMethods.get(
                  new MethodIdentifier(method));
              if (other != null) {
                return other.invoke(adapter, args);
              } else {
                return method.invoke(adaptee, args);
              }
            } catch (InvocationTargetException e) {
              throw e.getTargetException();
            }
          }
        });
  }

  private static class MethodIdentifier {
    private final String name;
    private final Class[] parameters;
    public MethodIdentifier(Method m) {
      name = m.getName();
      parameters = m.getParameterTypes();
    }
    // we can save time by assuming that we only compare against
    // other MethodIdentifier objects
    public boolean equals(Object o) {
      MethodIdentifier mid = (MethodIdentifier) o;
      return name.equals(mid.name) &&
          Arrays.equals(parameters, mid.parameters);
    }
    public int hashCode() {
      return name.hashCode();
    }
  }
}
  

I have used Java 5 generics in the dynamic object adapter factory, but the solution would also work with JDK 1.3. Here is how I would use the dynamic object adapter factory to make an object adapter for my BetterCollection. Note that this code generates a compiler warning, which we may safely ignore. This type of code is what the @SuppressWarnings annotation is meant for, but it seems to not have been implemented in the compiler.

package com.maxoft.tjsn.util;

import java.lang.reflect.Array;
import java.util.Collection;
import static com.maxoft.tjsn.util.DynamicObjectAdapterFactory.*;

public class BetterCollectionFactory {
  public static  BetterCollection asBetterCollection(
      final Collection adaptee, final Class valueType) {
    return adapt(adaptee,
        BetterCollection.class,
        // this anonymous inner class contains the method that
        // we want to adapt
        new Object() {
          public T[] toArray() {
            return adaptee.toArray((T[]) Array.newInstance(
                valueType, adaptee.size()));
          }
          // Whilst we are at it, we could also make it into a
          // checked collection, see java.util.Collections for
          // an example.
          public boolean add(T o) {
            if (!valueType.isInstance(o))
              throw new ClassCastException("Attempt to insert " +
                  o.getClass() +
                  " value into collection with value type " + valueType);
            return adaptee.add(o);
          }
          // addAll left as an exercise for the reader :-)
        });
  }
}
  

Here is how we would use this BetterCollectionFactory. The static imports in Java 5 save us some typing. We can simply write asBetterCollection() and it then wraps our collection with the BetterCollection using the adapter factory.

package com.maxoft.tjsn.util;

import java.util.*;
import static com.maxoft.tjsn.util.BetterCollectionFactory.*;

public class BestCollectionTest {
  public static void main(String[] args) {
    BetterCollection names = asBetterCollection(
        new ArrayList(), String.class);
    names.add("Wolfgang");
    names.add("Leander");
    names.add("Klaus");
    names.add("Reinhard");
    String[] nameArray = names.toArray();
    for (String s : nameArray) {
      System.out.println(s);
    }
  }
}
  

Next Thursday (19th May 2005) I am scheduled to present a talk at the University of Crete on Design Patterns, specifically on how to use dynamic proxies in Java to autogenerate code for virtual proxies and security proxies. Please send me an email if you are in or near Iraklion, Crete, Greece next week and would like to attend.

Kind regards

Heinz

Talk Back!

Have an opinion? Readers have already posted 4 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Heinz Kabutz adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Heinz Kabutz enjoys driving Java to the limits, and then a bit beyond. He has been programming in Java since 1997 on several very unimportant projects. During that time, he has picked up some horrifying tips on how you can get the most out of Java. These are published on his bi-monthly "The Java(tm) Specialists' Newsletter" (http://www.javaspecialists.co.za). It is not for the uninitiated :-) Heinz received a PhD in Computer Science from the University of CapeTown. He loves living in South Africa as it is both beautiful and interesting. Professionally, Heinz survives by writing Java code, insulting, ahem, consulting, and presenting courses on Java and Design Patterns.

This weblog entry is Copyright © 2005 Heinz Kabutz. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us