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);
if( method == 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);
if( method == null ) {
// we don't know the destination type for the set method, just use whatever we can find
if( variableType == 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(0, 1).toUpperCase() + variableName.substring(1);
// find method by traversal of the object
for( final Method meth : destinationObject.getClass().getMethods() ) {
if( meth.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(0, 1).toUpperCase() + variableName.substring(1);
// find method by traversal of the object
for( final Method meth : destinationObject.getClass().getMethods() ) {
if( meth.getName().equals(methodName) //
&& meth.getParameterTypes().length == 1 //
&& 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(0, 1).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 {
if( autoboxingConverter.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 e) throws 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);
}
}
|