Friday, July 8, 2011

Slightly tidier version of super-type tokens without anonymous inner classes

If you have ever used or written a fluent API eventually you have to deal with the fact that in Java generic types are erased. For example in the current Jersey client API you find the following problems:


   // Can't do generic class literals, compile error

   List<Employee> result = resource.get(List<Employee>.class);

   // Code won't function as it doesn't know the generic type

   List<Employee> result = resource.get(List.class);

Most API's solve this using Grafter's Gadget which creates a class where a trivial subclass bakes in the generic type. For example in REST the class in called GenericType:


   // Creates a anonymous instance of GenericType with the generic parameters
   // baked in

   List<Employee> result = resource.get(new GenericType<List<Employee>>() {});

I have never liked this because well it is just a little bit untidy for my liking, creating an inner class for this one time use doesn't feel right. You can of course extract this to a constant; but I was looking for a better way to define a literal. It turns out that with a big of playing about you can write code to do the following:


   private static final GenericType<List<String>> LIST_STRING =
           GenericType.field();

The trick here is to examine the calling stack when creating the GenericType instance to final the class that the field be being attached to. You can then go back and work out generic type of the field is when the constants is used. Here is a rough sketch of the code integrating with the Jersey GenericType:



public class GenericType<T> {

    public static final <U> GenericType<U> field()
    {
       return new FieldGenericType<U>();
    }

    // Rest of GenericType implementation
    // .....
}



class FieldGenericType<T>
      extends GenericType<T> {

    // TODO if we are not allowed to create a security manager just
    // use new Exception().getStackTraceElement() and then get hold
    // of the string name of the class
    static class ModifiedSecurityManager extends SecurityManager {

        public Class[] getClassContext() {
            return super.getClassContext();
        }
    }
    static final ModifiedSecurityManager sm = new ModifiedSecurityManager();


    /**
     * Provide an adapted version of the paramaterized type based on the
     * value of the field.
     */

    static class AdaptedParameterizedType implements ParameterizedType {

            // Find the class that created us, a few steps up the stack
            private Class owningClass = sm.getClassContext()[4];
            // The owning instances, can't be set in the constructor
            private FieldGenericType instance;
            private ParameterizedType type;

            /** Lazily create the type **/
            private ParameterizedType getType() {

                // TODO make thread safe
                //

                if (type == null) {

                    // Look for a field of type GenericType which is the same
                    // references the "instance" of the FieldGenericType
                    //

                    found:
                    for (Field field : owningClass.getDeclaredFields()) {
                        final Class<?> fieldType = field.getType();
                        if (GenericType.class.isAssignableFrom(fieldType))
                        {
                            try {
                                field.setAccessible(true);
                                if (field.get(owningClass) == instance) {
                                    // We need the generic parameter of the GenericType class
                                    //
                                    type = (ParameterizedType)
                                            ((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
                                }
                            } catch (IllegalArgumentException ex) {
                                Logger.getLogger(Example.class.getName()).log(Level.SEVERE, null, ex);
                            } catch (IllegalAccessException ex) {
                                Logger.getLogger(Example.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }

                    //

                    throw new Error("Cannot find the related field");
                }

                return type;
            }

            public Type[] getActualTypeArguments() {
                return getType().getActualTypeArguments();
            }

            public Type getRawType() {
                return getType().getRawType();
            }

            public Type getOwnerType() {
                return getType().getOwnerType();
            }

            public String toString() {
                return getType().toString();
            }
        }     


    public FieldGenericType() {
        super(new AdaptedParameterizedType() );

        // Inject the this value, now it has been created
        ((AdaptedParameterizedType)getType()).instance = this;


    }
}

Perhaps not that much tidier for day to day coding; but not using anonymous inner classes just feels like the right thing to do. I guess time will tell if I actually end up using it. I can see it being very useful when trying to generate boilerplate code in an IDE.

4 comments:

Paul Sandoz said...

If List<T> where T will be a type of Class is common then, off the top of my head, the following might work:

resource.get(ListGenericType.of(Employee.class));

"ListGenericType.of" could construct an instance of Type for the parameterized type List<Employee> and pass that to the constructor of GenericType. The method could also cache GenericType instances.

No so much saving of characters though, but less <>{} :-)

Gerard Davison said...

Ah yes, I guess most cases would be of this kind. With a bit of static importing and operator overloading you could have:

resource.get(list(Employee.class));
resource.get(map(Employee.class));
resource.get(future(list((Employee.class)));

This would actually cover a large number of basic cases. Nice one.

Gerard

Tiklup said...

Can you talk a bit about how you get the expected JSON out of this?

Either:
{
"employees":[
{...},
{...}
]
}

I couldn't do it well without hacking some awful stuff ... as shown by the comments in this gist, the only diff was that I was using products instead of employees:
https://gist.github.com/3614268

Gerard Davison said...

Tiklup,

This probably isn't the right forum for this, can you tried the jersey-users forum?

Gerard