Open Source Repository

Home /csv/supercsv-1.52 | Repository Home


org/supercsv/util/MethodCache.java
package org.supercsv.util;

import java.lang.reflect.Method;
import java.util.HashMap;

import org.supercsv.exception.SuperCSVException;
import org.supercsv.exception.SuperCSVReflectionException;

import spiffy.core.util.HashMapBuilder;
import spiffy.core.util.ThreeDHashMap;
import spiffy.core.util.TwoDHashMap;

/**
 * This class cache's method lookup. Hence first time it introspects the instance's class, while subsequent method
 * lookups are super fast.
 */
public class MethodCache {
/**
 * defines a lookup between classes and what class signature they may match due to the autoboxing feature in java
 */
private final HashMap<Class, Class> autoboxingConverter = new HashMapBuilder<Class, Class>()//
  .add(long.class, Long.class)//
  .add(Long.class, long.class)//
  .add(int.class, Integer.class)//
  .add(Integer.class, int.class)//
  .add(char.class, Character.class)//
  .add(Character.class, char.class)//
  .add(byte.class, Byte.class)//
  .add(Byte.class, byte.class)//
  .add(short.class, Short.class)//
  .add(Short.class, short.class)//
  .add(boolean.class, Boolean.class)//
  .add(Boolean.class, boolean.class)//
  .add(double.class, Double.class)//
  .add(Double.class, double.class)//
  .add(float.class, Float.class)//
  .add(Float.class, float.class)//
  .build();

/**
 * A map containing mapping "classname -> HashMap". The inner HashMap is a "methodname->Method" mapping
 */
private final ThreeDHashMap<Class /* class */, Class /* type */, String /* variableName */, Method> setMethodsCache = new ThreeDHashMap<Class, Class, String, Method>();

/**
 * A map containing mapping "classname -> HashMap". The inner HashMap is a "methodname->Method" mapping
 */
// private final HashMap<String, HashMap<String, Method>> getCache = new HashMap<String, HashMap<String, Method>>();
private final TwoDHashMap<String, String, Method> getCache = new TwoDHashMap<String, String, Method>();

/** Given an instance and a variable name, return the given method */
public Method getGetMethod(final Object destinationObject, final String variableName) {
  return getMethod(getCache, destinationObject, "get", variableName);
}

/**
 * using either get or set cache lookup a method. This approach saves a subString(), concatenations and toUpperCase()
 * since now the variable name uniquely identify either get or set method access
 */
// Method getMethod(final HashMap<String, HashMap<String, Method>> cache, final Object destinationObject,
// final String methodPrefix, final String variableName) {
// final String className = destinationObject.getClass().getName();
// HashMap<String, Method> methodCache = cache.get(className);
// if( methodCache == null ) {
// methodCache = new HashMap<String, Method>();
// cache.put(className, methodCache);
// }
// Method method = methodCache.get(variableName);
// if( method == null ) {
// method = inspectClass(destinationObject, methodPrefix, variableName, 0);
// methodCache.put(variableName, method);
// }
//  
// return method;
// }
Method getMethod(final TwoDHashMap<String, String, Method> cache, final Object destinationObject,
  final String methodPrefix, final String variableName) {
  Method method = cache.get(destinationObject.getClass().getName(), variableName);
  ifmethod == null ) {
    method = inspectClass(destinationObject, methodPrefix, variableName, 0);
    cache.set(destinationObject.getClass().getName(), variableName, method);
  }
  return method;
}

public <T> Method getSetMethod(final Object destinationObject, final String variableName, final Class<?> variableType) {
  Method method = setMethodsCache.get(destinationObject.getClass(), variableType, variableName);
  ifmethod == null ) {
    // we don't know the destination type for the set method, just use whatever we can find
    ifvariableType == null ) {
      method = findSetMethodWithNonPrimitiveParameter(destinationObject, variableName);
    }
    else {
      method = inspectClassForSetMethods(destinationObject, variableType, variableName);
    }
    setMethodsCache.set(destinationObject.getClass(), variableType, variableName, method);
  }
  return method;
}

/**
 @param destinationObject
 *            the object on which to call the method
 @param methodPrefix
 *            "get" (not used with "set" anymore due to overloading lookup
 @param variableName
 *            specifies method to search for
 @param requiredNumberOfArgs
 *            the number of arguments the method to search for has to have
 @return
 */
private Method inspectClass(final Object destinationObject, final String methodPrefix, final String variableName,
  final int requiredNumberOfArgs) {
  final String methodName = methodPrefix + variableName.substring(01).toUpperCase() + variableName.substring(1);
  
  // find method by traversal of the object
  forfinal Method meth : destinationObject.getClass().getMethods() ) {
    ifmeth.getName().equals(methodName//
      && meth.getParameterTypes().length == requiredNumberOfArgs ) {
      // System.out.println("found method " + meth.toString());
      return meth;
    }
  }
  throw new SuperCSVReflectionException(String.format(//
    "Can't find method '%s' in class '%s'", methodName, destinationObject.getClass().getName()));
}

private Method findSetMethodWithNonPrimitiveParameter(final Object destinationObject, final String variableName) {
  final String methodName = "set" + variableName.substring(01).toUpperCase() + variableName.substring(1);
  
  // find method by traversal of the object
  forfinal Method meth : destinationObject.getClass().getMethods() ) {
    ifmeth.getName().equals(methodName//
      && meth.getParameterTypes().length == //
      && meth.getParameterTypes()[0].isPrimitive() == false ) {
      // System.out.println("found method " + meth.toString());
      return meth;
    }
  }
  throw new SuperCSVReflectionException(String.format(//
    "Can't find method '%s' in class '%s'", methodName, destinationObject.getClass().getName()));
}

/**
 * find the method by inspecting the objects' class or throws an exception
 
 @param destinationObject
 @param variableType
 @param variableName
 @throws SuperCSVReflectionException
 *             when the method is not found
 @return the method
 */
Method inspectClassForSetMethods(final Object destinationObject, final Class variableType, final String variableName) {
  final String methodName = "set" + variableName.substring(01).toUpperCase() + variableName.substring(1);
  
  try {
    return destinationObject.getClass().getMethod(methodName, variableType);
  }
  catch(final SecurityException e) {
    throwException(destinationObject, variableType, methodName, e);
  }
  catch(final NoSuchMethodException e) {
    // retry again due to autoboxing in java we need to try both cases
    try {
      ifautoboxingConverter.containsKey(variableType== false ) {
        throwException(destinationObject, variableType, methodName, e);
      }
      return destinationObject.getClass().getMethod(methodName, autoboxingConverter.get(variableType));
    }
    catch(final SecurityException e1) {
      throwException(destinationObject, variableType, methodName, e1);
    }
    catch(final NoSuchMethodException e1) {
      throwException(destinationObject, variableType, methodName, e1);
    }
  }
  throw new SuperCSVException("This can never happen!");
}

/**
 @param destinationObject
 @param variableType
 @param methodName
 @param e
 @throws SuperCSVReflectionException
 */
private void throwException(final Object destinationObject, final Class variableType, final String methodName,
  final Exception ethrows SuperCSVReflectionException {
  e.printStackTrace();
  throw new SuperCSVReflectionException(String.format("Can't find method '%s(%s)' in class '%s'. "
    "Is the name correctly spelled in the NameMapping? "
    "Have you forgot to convert the data so that a wrong set method is called?", methodName, variableType,
    destinationObject.getClass().getName()), e);
}
}