|
The Java Specialists' Newsletter
Issue 108 2005-05-10
Category:
Language
Java version: Sun JDK 1.5.0_02 Object Adapter based on Dynamic Proxyby Dr. Heinz M. Kabutz
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! You can see the
list on our
website.
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.
Upcoming Java Specialist Master Courses:
- please click here to sign up.
As from May 2010, we are also offering this course on the island of Crete. We
only accept 6 students per class in Crete, due to the size of our conference
room. Please book early to avoid disappointment!
San Jose CA, Mar 16-19 2010, $3500 Ottawa, Canada, Mar 22-25 2010, $3500 Oslo, Norway, Apr 13-16 2010, Kr 24500 Montreal, Canada, Apr 20-23 2010, $3500 Toronto, Canada, May 17-20 2010, $3500 Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500
In-house courses if these dates or locations do not suit you - click here for more information. 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<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 com.maxoft.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 com.maxoft.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 com.maxoft.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 com.maxoft.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 com.maxoft.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 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 <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 com.maxoft.tjsn.util;
import java.util.*;
import static com.maxoft.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
Language Articles
Related Java Course
|