Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

108Object Adapter Based on Dynamic Proxy

Author: Dr. Heinz M. KabutzDate: 2005-05-10Java Version: 5Category: Language
 

Abstract: The object adapter pattern is similar in structure to the proxy pattern. This means that we can use the dynamic proxy mechanism in Java to also implement a dynamic object adaptor.

 

Welcome to the 108th edition of The Java(tm) Specialists' Newsletter, sent to you from Siegen, Germany. When I was in Austria a few weeks ago, it was suggested that I add a reference list of past courses to my website, for those who are trying to convince their bosses that they should invite us to present a course at their company. I was surprised that there were over 70 institutions around the world whose staff we had already trained!

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 newsletters on it, but the old issues will, as always, still be available for free on our website.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

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. We call this covariant return types. For example, clone() may now return 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 eu.javaspecialists.tjsn.util;

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

public class BetterArrayList<T> extends ArrayList<T> {
  private final Class<T> valueType;
  public BetterArrayList(int initialCapacity, Class<T> valueType) {
    super(initialCapacity);
    this.valueType = valueType;
  }
  public BetterArrayList(Class<T> valueType) {
    this.valueType = valueType;
  }
  public BetterArrayList(Collection<? extends T> ts,
                         Class<T> 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 eu.javaspecialists.tjsn.util;

public class BetterArrayListTest {
  public static void main(String[] args) {
    BetterArrayList<String> names =
        new BetterArrayList<String>(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 eu.javaspecialists.tjsn.util;

import java.util.Collection;

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

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

package eu.javaspecialists.tjsn.util;

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

public class BetterCollectionObjectAdapter<T>
    implements BetterCollection<T> {
  private final Collection<T> adaptee;
  private final Class<T> valueType;
  public BetterCollectionObjectAdapter(Collection<T> adaptee,
                                       Class<T> 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<T> iterator() {
    return adaptee.iterator();
  }
  public <T> 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<? extends T> 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 eu.javaspecialists.tjsn.util;

import java.util.LinkedList;

public class BetterCollectionTest {
  public static void main(String[] args) {
    BetterCollection<String> names =
      new BetterCollectionObjectAdapter<String>(
        new LinkedList<String>(), 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 eu.javaspecialists.tjsn.util;

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

public class DynamicObjectAdapterFactory {
  public static <T> T adapt(final Object adaptee,
                            final Class<T> target,
                            final Object adapter) {
    return (T) Proxy.newProxyInstance(
        target.getClassLoader(),
        new Class[]{target},
        new InvocationHandler() {
          private Map<MethodIdentifier, Method> adaptedMethods =
              new HashMap<MethodIdentifier, Method>();
          // 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 eu.javaspecialists.tjsn.util;

import java.lang.reflect.Array;
import java.util.Collection;
import static eu.javaspecialists.tjsn.util.DynamicObjectAdapterFactory.*;

public class BetterCollectionFactory {
  public static <T> BetterCollection<T> asBetterCollection(
      final Collection<T> adaptee, final Class<T> 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 eu.javaspecialists.tjsn.util;

import java.util.*;
import static eu.javaspecialists.tjsn.util.BetterCollectionFactory.*;

public class BestCollectionTest {
  public static void main(String[] args) {
    BetterCollection<String> names = asBetterCollection(
        new ArrayList<String>(), 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);
    }
  }
}

Kind regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...