February 17, 2011

Doing smart stuff with List and Iterator

Many times the objects in a list must be converted into some others objects, i.e. when
we need to get a different list with converted objects. It's trivial from first glance:
List toJmsMessageList(List originalMyMessageList) {
 List resultList = new ArrayList(originalMyMessageList.size());
 for(MyMessage msg : originalMyMessageList) {
  resultList.add(Converter.convert(msg));
 }
 return resultList;
}
Nothing wrong with the above code, except:
1. it's boring and verbose
2. it creates new list
3. it iterates over original list
4. problems might occur when original list is ORM proxy list obtained as a result
of sql query, so operations such as size() on it - would cause problems such as retrieval of whole result set' content into memory

So, here's solution. What I actually want - convert list' stuff into list of another stuff, I would like to have something like:
List resultList = ListFactory.createList(originalList, callback);
So whenever resultList iteration being called I want to have already converted stuff in it! So, here's what I came to:
 List createList(final Iterator originalIterator, final Callable c) {
 // iteration handler for custom list implementation
 InvocationHandler invocationHandler = new InvocationHandler() {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) {
   if (method.getName().equals("iterator")) {
    return new Iterator() {
     @Override
     public boolean hasNext() {
      return originalIterator.hasNext();
     }

     @Override
     public T next() {
      try {
       // call 'custom behavior'
       return c.call();
      } catch (Exception e) {
       log.error("Error during user defined operation on originalIterator", e);
       throw new RuntimeException(e);
      }
     }

     @Override
     public void remove() {
      // 'remove' isn't supported
      throw new UnsupportedOperationException();
     }
    };
   }
   // everthying other than iteration isn't supported
   throw new UnsupportedOperationException();
  }
 };
 return (List) Proxy.newProxyInstance(
     Thread.currentThread().getClass().getClassLoader(),
     new Class[] {List.class}, 
     invocationHandler);
}
Here's possible use cases:
return ListFactory.createList(originalListIterator, new Callable() {
 private int fetchedNum = 0; // the number of fetched objects
 @Override
 public MessageEntity call() {
  if (++fetchedNum % fetchSize == 0) {
   // remove fetched MessageEntity entities from iternal jpa's identity map
   // when number of fetched objects reaches 'fetchedNum'
   getEntityManager().clear();
  }
  // don't forget call iterator.next()
  return originalListIterator.next();
 }
});
or
ListFactory.createList(messagesIterator, new Callable() {
 @Override
 public Entry call() {
  try {
   return toEntry(messagesIterator.next());
  } catch (Exception e) {
   log.error("Error occured while converting list of jms messages", e);
   throw new RuntimeException(e);
  }
 }
});

Thanks for reading.