2

I am currently working on a serialization routine which uses a library of generically typed adapters. If the object being serialized is an instance of one of the specific adapters I have, then I need to call that adapter on the object prior to performing my other serialization procedures.

The following code works:

private final static String serialize(Object obj, Map<Class<?>, 
        XmlAdapter<?,?>> classToAdapterMap) throws JAXBException 
{
    Object adaptedObj = null;

    for (Class<?> clazz : classToAdapterMap.keySet()) {
        if (clazz.isInstance(obj)) {
            XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
            Class<?>[] argTypes = new Class[] {clazz};
            try {
                Method method = adapter.getClass().getMethod("marshal", argTypes);
                adaptedObj = method.invoke(adapter, obj);
                break;
            } catch (Exception e) {
                // handle method retrieval and invocation related exceptions
            }
        }
    }

    // serialize
}

However, I had originally thought that I would be able to do this more simply, for example with code like:

/* DOES NOT WORK */
private final static String serialize(Object obj, Map<Class<?>, 
        XmlAdapter<?,?>> classToAdapterMap) throws JAXBException 
{
    Object adaptedObj = null;

    for (Class<?> clazz : classToAdapterMap.keySet()) {
        if (clazz.isInstance(obj)) {
            XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
            adaptedObj = adapter.marshal(clazz.cast(obj));
            break;
        }
    }

    // serialize
}

Clearly the problem is that the wildcard generically typed adapter isn't guaranteed to handle an object of type clazz. However, I can't indicate that these two are the same by changing the method signature—as I might otherwise do—to private final static <T> String serialize(Object obj, Map<Class<T>, XmlAdapter<?,T>> classToAdapterMap), because the map needs to hold adapters of all different types.

What would be a better way to do this? Or should I stick with the Reflection based solution?

Thanks in advance,

-Dan

Dan
  • 147
  • 14

2 Answers2

1

There is a simpler and safer way without using reflection:

At first, we need a small specialization of the XmlAdapter as it allows us to ask the adapter for the type it can handle.

public abstract class TalkingXmlAdapter<ValueType, BoundType> extends XmlAdapter<ValueType, BoundType> {

    public abstract Class<BoundType> getBoundType();

}

My custom adapters now need to extend TalkingXmlAdapter:

public class AppleXmlAdapter extends TalkingXmlAdapter<String, Apple> {

    @Override
    public Class<Apple> getBoundType() {
        return Apple.class;
    }

    @Override
    public Apple unmarshal(String v) throws Exception {
        System.out.println("Unmarshalling Apple");
        return new Apple();
    }

    @Override
    public String marshal(Apple v) throws Exception {
        System.out.println("Marshalling Apple");
        return "Apple";
    }

}


public class BananaXmlAdapter extends TalkingXmlAdapter<String, Banana> {

    @Override
    public Class<Banana> getBoundType() {
        return Banana.class;
    }

    @Override
    public Banana unmarshal(String v) throws Exception {
        System.out.println("Unmarshalling Banana");
        return new Banana();
    }

    @Override
    public String marshal(Banana v) throws Exception {
        System.out.println("Marshalling Banana");
        return "Banana";
    }

}

That allows us to write a simplified serialization method:

public class SimpleSerializer {

    public static final String serialize(Object obj, List<TalkingXmlAdapter> allAdapters) throws Exception {
        Object adaptedObj = null;

        for (TalkingXmlAdapter adapter : allAdapters) {
            if (adapter.getBoundType().isInstance(obj)) {
                adaptedObj = adapter.marshal(obj);
                break;
            }
        }
        // serialize
        System.out.println("Simple serializing for " + obj.toString());
        return "Simply serialized " + obj.toString();
    }

}

Using the code e.g. like in the subsequent listing shows the behavior you want:

    List<TalkingXmlAdapter> allAdapters = new ArrayList<>();
    allAdapters.add(new AppleXmlAdapter());
    allAdapters.add(new BananaXmlAdapter());

    SimpleSerializer.serialize(new Banana(), allAdapters);
    SimpleSerializer.serialize("Lemmon", allAdapters);
    SimpleSerializer.serialize(new Apple(), allAdapters);

Output:

Marshalling Banana
Simple serializing for generic.adapter.Banana@659e0bfd
Simple serializing for Lemmon
Marshalling Apple
Simple serializing for generic.adapter.Apple@2a139a55

To sum this up, the solution gives you following advantages:

  • You don't need reflection which simplifies your code.
  • You need fewer generic programming in your serialization routine which simplifies your code.
  • The solution is more safe. Note that no type cast is needed. Every adapter accepts the type Object. However by using the generic method getBoundType() you can ensure the specific runtime type is the correct one. When building your map as in the reflection solution, a wrongly mapped class results in a runtime exception. In the proposed solution the super class TalkingXmlAdapter enforces each adapter to state their correct type by using generics.

The price you pay is:

  • Introduction of a new super type.
  • Requires small adaptions for your custom adapters.

Hope that helps!

  • Thanks Bastian. The idea of using a list of subclassed XmlAdapters which add methods to report there bound and value types was actually an approach I settled on after making this post. As Didier mentions, the way around reflection here is by using raw types (which I did not consider at all). – Dan Apr 26 '15 at 20:04
  • I would like to mention though, that there is a very important difference between `adapter.getBoundType().equals(obj.getClass())` and what I'm sure you meant which would be `adapter.getBoundType().isInstance(obj)`. The latter allows me to use adapters typed to handle super-classes of my actual objects being serialized. This is very important to my actual use case. It does bring up the possible issue (which I glossed over in my post) that the behavior is undefined if multiple adapters handle different super-classes, but I avoid this through choice of adapters. – Dan Apr 26 '15 at 20:11
  • You're right Dan, using `isInstance(obj)` improves flexibility. I adapted my solution. Thanks for the remark. – Bastis Programming Corner Apr 29 '15 at 18:37
1

There are several solutions to circumvent this problem.

Most likely, the easiest one is using raw types: don't specify the type parameters for the adapter, and the compiler will happily accept the marshall call (with a raw type warning of course):

XmlAdapter adapter = classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);

(This is actually roughly the same solution as Bastian's, without the intermediate type)

If you don't like raw types, you may choose the unchecked cast to an Object-parameterized adapter. It's not really better, but it also works (by tricking the compiler…):

XmlAdapter<?, Object> adapter = (XmlAdapter<?, Object>) classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);

My last solution is to use a type parameter at the method level. This time, what you do is semantically correct (as long as the map itself is correct), and the unchecked cast really means “I know what I am doing here”:

private final static <T> String serialize(T obj, Map<Class<?>, 
        XmlAdapter<?,?>> classToAdapterMap) throws JAXBException 
{
    Object adaptedObj = null;

    for (Class<?> clazz : classToAdapterMap.keySet()) {
        if (clazz.isInstance(obj)) {
            try {
                XmlAdapter<?, ? super T> adapter = (XmlAdapter<?, ? super T>) classToAdapterMap.get(clazz);
                adaptedObj = adapter.marshal(obj);
                break;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    // serialize
}

The semantic correctness comes from the following:

  • you may consider T to be the actual class of obj since T is a method-bound parameter, not used elsewhere in the signature;
  • clazz is a super type of the type of T since we checked clazz.isInstance(obj);
  • adapter can handle instances of clazz or a super-type of it since it is how the map was built;
  • by consequent, adapter can handle all instances of an (unknown) super type of T, hence the ? super T declaration.
Didier L
  • 18,905
  • 10
  • 61
  • 103
  • Thanks Didier. This is a great summary of available options. I ended up going with your third solution—adding a type parameter to the serialization method—combined with Bastian's suggestion of subclassing XmlAdapter. (I realized shortly after posting this question that I am getting no benefit out of using a map here, because the _bound type_ of the desired adapter may be a super-class, rather than a match, to the type of the object being serialized. Thus a list of adapters capable of reporting their type seems cleaner to me.) – Dan Apr 26 '15 at 20:20