Tuesday 13 July 2010

A simple example of Java Reflection API

Java reflection API allows us to inspect classes on runtime. This is a very powerful tool but it's also very bad on performance. I use that technique on class converting. Suppose you have an entity you named User, and you don't want to serialize it to the client layer. You have to create a serializable class that has all the attributes you want to send back to the client and fill it with the entity content. This is a very good work for reflection.

I faced this problem on a project and I made a utility class to help resolve that. This is a very basic approach to a real solution but it works for me.

Let's see some code:

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import com.jflow.entities.GenericEntity;
import com.jflow.exceptions.EWConversionException;

public class ReflectionUtil {

    private static String capitalize(String s) {
        if (s.length() == 0) return s;
        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
    }
    
 public static <T> T convertClass(GenericEntity source, Class<T> destinationClass) throws EWConversionException {
  T desteny = null;
  try {
   if (source == null) {
    return desteny;
   }
   
   desteny = destinationClass.newInstance();
   Class<?> sourceClass = source.getClass();
         
         Field destFieldList[] = destinationClass.getDeclaredFields();
   for (Field field : destFieldList) {
    String name = field.getName();
    Field sourceField = null;
    
    try {
     sourceField = getField(sourceClass, name);
     
     field.setAccessible(true);
     sourceField.setAccessible(true);
     field.set(desteny, sourceField.get(source));
     
    } catch(IllegalAccessException e) {
     try {
      String methodName = capitalize(sourceField.getName());
      Method method;
      method = getMethod(sourceClass, "get" + methodName);
      method.setAccessible(true);
      field.set(desteny, method.invoke(source));
      
     } catch (IllegalArgumentException e1) {
      e1.printStackTrace();
     } catch (InvocationTargetException e1) {
      e1.printStackTrace();
     } catch (IllegalAccessException e1) {
      e1.printStackTrace();
     } catch (NoSuchMethodException e1) {
      e.printStackTrace();
     }
    } catch (NoSuchFieldException e) {
     e.printStackTrace();
    }
   }
  
  } catch (SecurityException e1) {
   throw new EWConversionException(e1.getMessage());
  } catch (InstantiationException e1) {
   throw new EWConversionException(e1.getMessage());
  } catch (IllegalAccessException e1) {
   throw new EWConversionException(e1.getMessage());
  }
  
  return desteny;
 }
 
 private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
  Field field = null;
  while (clazz != Object.class) {
       try {
        field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        break;
       } catch (NoSuchFieldException ex) {
        clazz = clazz.getSuperclass();
       }
  }
  
  if (field == null)
   throw new NoSuchFieldException(field + " no such a field.");
  
  // only needed if the two classes are in different packages
  return field;
 }
 
 private static Method getMethod(Class<?> clazz, String methodName) throws NoSuchMethodException {
  Method method = null;
  while (clazz != Object.class) {
       try {
        method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true); 
        break;
       } catch (NoSuchMethodException ex) {
        clazz = clazz.getSuperclass();
       }
  }
  
  if (method == null)
   throw new NoSuchMethodException(methodName + " no such a method.");
  
  // only needed if the two classes are in different packages
  return method;
 }
 
 @SuppressWarnings("unchecked")
 public static <T> HashSet<T> convertToHashSet(Collection<? extends GenericEntity> source, Class<T> destinationClass) throws EWConversionException {
  HashSet<T> returnCollection = new HashSet<T>();
  
  for (GenericEntity s : source) {
   T element = (T) ReflectionUtil.convertClass(s, destinationClass.getClass());
   returnCollection.add(element);
  }
  
  return returnCollection;
 }

 
 @SuppressWarnings("unchecked")
 public static <T> Map<Long, T> convertToMap(Collection<? extends GenericEntity> source, Class<T> destinationClass) throws EWConversionException {
  Map<Long, T> returnCollection = new HashMap<Long, T>();
  
  for (GenericEntity s : source) {
   T element = (T) ReflectionUtil.convertClass(s, destinationClass.getClass());
   returnCollection.put(s.getId(), element);
  }
  
  return returnCollection;
 }
 
 public static <T> List<T> convertToArrayList(Collection<? extends GenericEntity> source, Class<T> destinationClass) throws EWConversionException {
  List<T> returnCollection = new ArrayList<T>();
  
  for (GenericEntity s : source) {
   T element = (T) ReflectionUtil.convertClass(s, destinationClass);
   returnCollection.add(element);
  }
  
  return returnCollection;
 }
 
}

I also provide a couple of methods to help on more complex conversions, such as Map, List and Sets. GenericEntity is an abstract class from where all entities inherit.

I made some performance test with a class that has 5 attributes, based on the supposed that both has the same attributes. As a mean I get 2 milliseconds for the traditional way of fill one class with the content of the other one, and on the other hand an 8 milliseconds mean using the utility class.

I think that this is very useful for non real time applications and avoid you from being specific converting code for each class you need to send back to the client layer.

This is a wide open discussion field and most of you should have different opinions. The aim is to present one way of doing this. If you have buts or other ways to do this please don't hesitate to post a comment.

If you want to get more in deep details on "Java Reflection API" you can read this tutorial. He gave very good explanations on this concern.

See you!!

No comments:

Post a Comment