Wednesday, June 30, 2010

Reified Types: Conversions Using Generic Types

One of the most interesting aspects of the Blueprint specification was the ReifiedType. I ran into this nifty class again today because we want create a Conversion service that is useful by the new upcoming OSGi shell service. We had decided to reuse the Blueprint conversion model, but now as a separate service, something which we probably should have done already for Blueprint.

The problem that Reified Type solved for us is how to convert an Object to a specific target type by taking any generic information into account. For example, if you convert an int[] to a Collection<Long> you want to be sure that the collection contains Long objects and not Integer objects. That is, a naive implementation would convert this to Collection<Integer>, which would miserably fail later on when someone uses the collection.

I can hear many readers thinking: "But generic information is erased, isn't it????" Yup, generics are erased, but not completely! When you have an object there is no way to know the generic parameters (the parameters between the angled brackets like T, K, V etc.). I am not sure why they decided to not provide this information with the object because it seems very doable but it clearly is not present. However, the VM maintains extensive generic information everywhere else but in the object. If you have an instance field then it can describe exactly what its generic type constraints are. For example, if you have a field numbers, declared like:

Map<String,Class<T>> numbers;

Then you can get the generic type constraints with the getGenericType method that returns a Type. Virtually all reflective methods have been duplicated to provide this Type object where they used to return a Class object.

OSGi APIs are written in a restrictive subset of Java so that they can be used in a wide variety of VMs. Though we found a solution allowing us to use generics in Java 1.4 (JSR 14), we must assume a target of Java 1.4 with the minimum execution environment for our daily work. This means there is no Type class we can rely on in our APIs.

Obviously, in enterprise scenarios the VM is Java 5 or later, and the Type class is readily available. Is there a way we can eat our cake (use the generics info in our Conversion service) and have it too (no dependency on Java 5)?

The first solution that comes to mind is to duplicate all the sub-types of the Type interface in our own name space. There are actually quite a few sub-types of the Type interface. These sub-types models (and more!) all the flexibility of the generics constraints system: type variables, arrays, wild-cards (super, extends), and parametrized types.

Hmm, not only is duplicating no fun, it also turned out that the hierarchy is not that easy to work with. For our Conversion service we only need to know the raw type of a type parameter. That is, if our target type is Collection<Long> than the we only need to know that the type parameter is Long.

After struggling with this problem we found an interesting solution: the ReifiedType class. A Reified Type collapses all the intermediate levels of wildcards, variables, and arrays and provides direct access to the most specific types that can be used. It turns out that by traversing the network of Type objects it is always possible to end in a raw class, even with variables, wildcards, and arrays.

A Reified Type is always associated with a single raw class. The raw class is the class without any generics information. It provides access to the type parameters by their position. For example, the Map<String, ? extends Number> has a raw class of Map and 2 type parameters: String and Number. For this example, the previous Refied Type has a size() of 2, and provides a ReifiedType<String> and a ReifiedType<Number> for the type parameters.

In the Blueprint specification we provide a concrete implementation for Java 1.4 VMs. In Java 1.4, there are no generics so this implementations hard codes the number of type parameters to 0 by definition. We will do the same in the Conversion service. However, users of this service will be able to pass subclasses that provide the generic information to get better conversions.

So it looks like this was a case where we could have our cake and eat it too! You can already look at Reified Type in the Blueprint specification and in the next release I hope we can have a new Conversion service based on this model.

Peter Kriens

No comments:

Post a Comment