Friday, August 21, 2009

THC, and a bit of Thunking. (Creative ways to deal with multiple return types)

Often is the case that you really do want a method to return multiple values as different client require different parts back. This is often simpler than creating multiple methods; but it might not be worth creating a bean class for. For simple two value return types you can use a nice generic version of Pair; but things get harder if you have more value. (You can use a holder class to simulate IN/OUT parameters, but it doesn't result in pretty code) I was working on a method that needed to return four values depending on the calling context so I turned to a type safe heterogeneous container from the Effective java book:

/**
 * An implementation of a type safe heterogeneous container, taken from
 * effective java
 */
public class THC
{
   final private Map<Class<?>, Object> container = new HashMap<Class<?>, Object>();
   
   public <T> void put(Class<T> key, T value)
   {
      if (key==null) throw new NullPointerException("Key cannot be null");
      container.put(key, value);
   }
   
   
   public <T> T get(Class<T> key)
   {
      // Return the value of think the thunk if required
      //
      Object object = container.get(key);
      return key.cast(object);
   }
}

It doesn't provide full compile time safety; but it is close enough for our purposes. You can write code that looks like:


   THC thc = getSSLConfiguration(...);
   SSLContext sslContext = thc.get(SSLContext.class);
 
   // or in another case

   KeyManager kms[] = thc.get(KeyManager[].class);
   TrustManager tms[] = thc.get(TrustManager[].class);


So this is fine, except as you see from the example above that depending on the context I need different subset of the objects. Some are derived from others so I could end up doing extra work to populate a part of the return value that is never used.

I decided to look at using a Thunk as a way to optimize the work done on the return value of this method. So I modified the THC class to include a new public interface Thunk with one method that takes a reference to the original container.

/**
 * An implementation of a type safe heterogeneous container, taken from
 * effective java
 */
public class THC
{
   /**
    * Allow the client to provide a derived value
    * @param <T>
    */
   public interface Thunk<T>
   {
      public T get(THC that);
   }

   /**
    * Place a value in the structure that has yet to be derived.
    * @param key
    * @param value The value to be thought of in the future
    */
   public <T> void put(Class<T> key, Thunk<T> value)
   {
      if (key==null) throw new NullPointerException("Key cannot be null");
      container.put(key, value);
   }
   
   public <T> T get(Class<T> key)
   {
      // Return the value of thinking the thunk if required
      //
      Object object = container.get(key);
      if (object instanceof Thunk)
      {
         object = ((Thunk)object).get(this);
         container.put(key, object);
      }

      return key.cast(object);
   }

}

You find that because Thunk takes a parameter of the THC the adapter classes can be constants, this saves a little bit on object creation. So I can write something like:



  static final private Thunk<SSLContext> contextT = new Thunk<SSLContext>()
  {
     public SSLContext get(THC that)
     {
        SSLContext c = SSLContext.getInstance("SSL");
        c.init(that.get(KeyManager[].class)
               that.get(TrustManager[].class), null);
        return c;
     }
  }

  static final private Thunk<SSLSocketFactory> factoryT = new Thunk<SSLSocketFactory>()
  {
     public SSLSocketFactory get(THC that)
     {
        return that.get(SSLSocketFactory.class).getSSLSocketFactory();
     }
  }

  public THC getSSLConfiguration(...)
  {
     ...

     THC thc = new THC();
     thc.put(KeyManager[].class, kms);
     thc.put(TrustManager[].class, tms);
     thc.put(SSLContext.class, contextT);
     thc.put(SSLSocketFactory.class, factoryT);
  }

So I can now write my client code as follows and the SSLContext and SSLSocketFactory are created on demand:

   THC context = getSSLConfiguration(...);
   SSLSocketFactory sf = context.get(SSLSocketFactory.class);

Perhaps not a technique that is applicable to all situations; but interesting all the same.

Update: Some time later it occurred that the THC interface if of course not particularly pretty, so an obvious extension is to have THC return a Proxy based on a interface that can be used to define the return types. Not written this yet; but it would be pretty trivial to do.

No comments: