/**
Copyright (C) 2012 Delcyon, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.delcyon.capo.util;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.delcyon.capo.CapoApplication;
import com.delcyon.capo.util.ToStringControl.Control;
/**
* @author jeremiah
*/
public class ReflectionUtility
{
@SuppressWarnings("rawtypes")
private static volatile ConcurrentHashMap<Class, Method[]> systemClassDeclaredMethodsHashMap = new ConcurrentHashMap<Class, Method[]>();
@SuppressWarnings("rawtypes")
private static volatile ConcurrentHashMap<Class, Field[]> systemClassDeclaredFieldsHashMap = new ConcurrentHashMap<Class, Field[]>();
@SuppressWarnings("rawtypes")
private static volatile ConcurrentHashMap<Class, Class[]> systemClassIneterfacesHashMap = new ConcurrentHashMap<Class, Class[]>();
@SuppressWarnings("rawtypes")
private static volatile ConcurrentHashMap<Class, Constructor[]> systemClassConstructorsHashMap = new ConcurrentHashMap<Class, Constructor[]>();
@SuppressWarnings("rawtypes")
private static volatile ConcurrentHashMap<Class, Vector<Method>> systemClassMethodVectorHashMap = new ConcurrentHashMap<Class, Vector<Method>>();
public static final String DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
public static final String DEFAULT_DATE_TIME_FORMAT = "MM/dd/yyyy HH:mm";
/**
* checks to see if a class is assignable from a primitive type This has to
* be extended beyond the standard isPrimitive call to handle "primitive"
* objects like Strings and Booleans
*
* @param clazz
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static boolean isPrimitive(Class clazz)
{
if (clazz.isPrimitive())
{
return true;
}
else if (Boolean.class.isAssignableFrom(clazz))
{
return true;
}
else if (String.class.isAssignableFrom(clazz))
{
return true;
}
else if (Date.class.isAssignableFrom(clazz))
{
return true;
}
else if (Number.class.isAssignableFrom(clazz))
{
return true;
}
else if (Enum.class.isAssignableFrom(clazz))
{
return true;
}
else
{
return false;
}
}
/**
* @param type
* @param valueString
* @return
* @throws Exception
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object getPrimitiveInstance(Type type, String valueString) throws Exception
{
Object typeInstanceObject = null;
try
{
if (valueString == null)
{
return null;
}
else if (type == int.class || type == Integer.class)
{
typeInstanceObject = Integer.parseInt(valueString);
}
else if (type == float.class || type == Float.class)
{
typeInstanceObject = Float.parseFloat(valueString);
}
else if (type == double.class || type == Double.class)
{
typeInstanceObject = Double.parseDouble(valueString);
}
else if (type == long.class || type == Long.class)
{
typeInstanceObject = Long.parseLong(valueString);
}
else if (type == short.class || type == Short.class)
{
typeInstanceObject = Short.parseShort(valueString);
}
else if (type == byte.class || type == Byte.class)
{
typeInstanceObject = Byte.parseByte(valueString);
}
else if (type == boolean.class || type == Boolean.class)
{
typeInstanceObject = Boolean.parseBoolean(valueString);
}
else if (type == String.class)
{
return new String(valueString);
}
else if (((Class) type).isEnum())
{
Class enumClass = (Class) type;
Enum[] objects = (Enum[]) enumClass.getEnumConstants();
for (Enum object : objects)
{
if (object.toString().equals(valueString))
{
typeInstanceObject = object;
break;
}
}
}
else if (type == Date.class)
{
if (valueString.matches("\\d{2}/\\d{2}/\\d{4} \\d{2}:\\d{2}") == true)
{
typeInstanceObject = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT).parse(valueString);
}
else if (valueString.matches("\\d{2}/\\d{2}/\\d{4}") == true)
{
typeInstanceObject = new SimpleDateFormat(DEFAULT_DATE_FORMAT).parse(valueString);
}
else
{
throw new Exception("bad date '" + valueString + "'");
}
}
else if (type == java.sql.Date.class)
{
if (valueString.matches("\\d{2}/\\d{2}/\\d{4}") == true)
{
typeInstanceObject = new SimpleDateFormat(DEFAULT_DATE_FORMAT).parse(valueString);
}
else
{
throw new Exception("bad date '" + valueString + "'");
}
}
else if (type == Timestamp.class)
{
if (valueString.matches("\\d{2}/\\d{2}/\\d{4} \\d{2}:\\d{2}") == true)
{
Date timestampDate = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT).parse(valueString);
if (timestampDate != null)
{
typeInstanceObject = new Timestamp(timestampDate.getTime());
}
else
{
throw new Exception("bad timestamp '" + valueString + "'");
}
}
else
{
throw new Exception("bad timestamp '" + valueString + "'");
}
}
}
//return null for bad number strings
catch (NumberFormatException numberFormatException)
{
return null;
}
return typeInstanceObject;
}
/**
* This walks all of the super classes and interfaces of an object and
* creates a vector of declared methods where the first method in the vector is the first
* method in the super most class
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Vector<Method> getMethodVector(Object object)
{
Vector<Class> classVector = new Vector<Class>();
Class currentClass = object.getClass();
Vector<Method> methodVector = systemClassMethodVectorHashMap.get(currentClass);
if(methodVector != null)
{
return methodVector;
}
else
{
methodVector = new Vector<Method>();
}
while (currentClass != null)
{
classVector.insertElementAt(currentClass, 0);
Class[] interfaceClasses = null;
if(systemClassIneterfacesHashMap.containsKey(currentClass))
{
interfaceClasses = systemClassIneterfacesHashMap.get(currentClass);
}
else
{
interfaceClasses = currentClass.getInterfaces();
systemClassIneterfacesHashMap.put(currentClass,interfaceClasses);
}
for (Class interfaceClass : interfaceClasses)
{
classVector.insertElementAt(interfaceClass, 0);
}
currentClass = currentClass.getSuperclass();
}
for (Class clazz : classVector)
{
Method[] methods = null;
if(systemClassDeclaredMethodsHashMap.containsKey(clazz))
{
methods = systemClassDeclaredMethodsHashMap.get(clazz);
}
else
{
methods = clazz.getDeclaredMethods();
systemClassDeclaredMethodsHashMap.put(clazz,methods);
}
for (Method method : methods)
{
methodVector.add(method);
}
}
systemClassMethodVectorHashMap.put(object.getClass(), methodVector);
return methodVector;
}
/**
* This walks all of the super classes and interfaces of an object and
* creates a vector of declared fields where the first field in the vector is the first
* field in the super most class
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Vector<Field> getFieldVector(Object object)
{
Vector<Class> classVector = new Vector<Class>();
Vector<Field> fieldVector = new Vector<Field>();
Class currentClass = object.getClass();
while (currentClass != null)
{
classVector.insertElementAt(currentClass, 0);
Class[] interfaceClasses = null;
if(systemClassIneterfacesHashMap.containsKey(currentClass))
{
interfaceClasses = systemClassIneterfacesHashMap.get(currentClass);
}
else
{
interfaceClasses = currentClass.getInterfaces();
systemClassIneterfacesHashMap.put(currentClass,interfaceClasses);
}
for (Class interfaceClass : interfaceClasses)
{
classVector.insertElementAt(interfaceClass, 0);
}
currentClass = currentClass.getSuperclass();
}
for (Class clazz : classVector)
{
Field[] fields = null;
if(systemClassDeclaredFieldsHashMap.containsKey(clazz))
{
fields = systemClassDeclaredFieldsHashMap.get(clazz);
}
else
{
fields = clazz.getDeclaredFields();
systemClassDeclaredFieldsHashMap.put(clazz,fields);
}
for (Field field : fields)
{
fieldVector.add(field);
}
}
return fieldVector;
}
/**
* This calls toString() on most things, but converts Date 's and whatnot to
* specific formats.
*
* @param object
* @return
*/
public static String getSerializedString(Object object)
{
if (object == null)
{
return null;
}
else if (object instanceof Date)
{
return new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT).format((Date) object);
}
else
{
return object.toString();// TODO use string utils to make this safe?
}
}
public static String processToString(Object toStringObject)
{
return processToString(toStringObject, ",");
}
/**
* Simple method for printing out all of the fields in an object
*
* @param toStringObject
* @return
*/
public static String processToString(Object toStringObject, String seperator)
{
if (seperator == null)
{
seperator = ",";
}
StringBuilder stringBuffer = new StringBuilder(toStringObject.getClass().getSimpleName() + "[");
Vector<Field> fieldVector = ReflectionUtility.getFieldVector(toStringObject);
Vector<Method> methodVector = ReflectionUtility.getMethodVector(toStringObject);
ToStringControl classToStringControl = toStringObject.getClass().getAnnotation(ToStringControl.class);
if(classToStringControl != null)
{
if(classToStringControl.control() == Control.exclude && classToStringControl.modifiers() == 0)
{
fieldVector.clear();
methodVector.clear();
}
}
int actualFieldCount = 0;
for (int currentField = 0; currentField < fieldVector.size(); currentField++)
{
Field field = fieldVector.get(currentField);
field.setAccessible(true);
ToStringControl fieldToStringControl = field.getAnnotation(ToStringControl.class);
boolean forceInclude = false;
if(fieldToStringControl != null)
{
if(fieldToStringControl.control() == Control.exclude)
{
continue;
}
else
{
forceInclude = true;
}
}
if(classToStringControl != null && forceInclude == false)
{
if(classToStringControl.control() == Control.exclude)
{
if((field.getModifiers() & classToStringControl.modifiers()) != 0) //exclude if the modifiers match
{
continue;
}
}
else
{
if((field.getModifiers() & classToStringControl.modifiers()) == 0) //exclude if the modifiers don't match
{
continue;
}
}
}
if (actualFieldCount != 0)
{
stringBuffer.append(seperator);
}
actualFieldCount++;
try
{
Object fieldValue = field.get(toStringObject);
String value = "null";
if (fieldValue != null)
{
// start array processing
if (field.getType().isArray())
{
int length = Array.getLength(fieldValue);
StringBuilder arrayStringBuilder = new StringBuilder("{");
for (int index = 0; index < length; index++)
{
if (index != 0)
{
arrayStringBuilder.append(',');
}
if (Array.get(fieldValue, index) != null)
{
arrayStringBuilder.append(Array.get(fieldValue, index).toString());
}
else
{
arrayStringBuilder.append("");
}
}
arrayStringBuilder.append('}');
value = arrayStringBuilder.toString();
} // end array processing
else
{
value = fieldValue.toString();
}
}
stringBuffer.append(field.getName() + "='" + value + "'");
} catch (Exception e)
{ // Don't care
}
}
if (actualFieldCount != 0)
{
stringBuffer.append(seperator);
}
int actualMethodCount = 0;
for (Method method : methodVector)
{
if(method.getAnnotation(ToStringControl.class) != null)
{
if(method.getAnnotation(ToStringControl.class).control() == Control.include)
{
method.setAccessible(true);
if(method.getParameterTypes().length == 0)
{
try
{
if(actualMethodCount > 0)
{
stringBuffer.append(seperator);
}
stringBuffer.append(method.getName() + "='" + method.invoke(toStringObject, new Object[]{}) + "'");
actualMethodCount++;
}
catch (Exception e)
{
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Error invoking "+method.getName()+" on "+toStringObject.getClass().getCanonicalName(), e);
}
}
}
}
}
stringBuffer.append("]");
return stringBuffer.toString();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object getComplexInstance(Class type,Object...cloneableFieldInstances) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
{
Constructor[] constructors = null;
if(systemClassConstructorsHashMap.containsKey(type))
{
constructors = systemClassConstructorsHashMap.get(type);
}
else
{
constructors = type.getDeclaredConstructors();
systemClassConstructorsHashMap.put(type, constructors);
}
Constructor defaultConstructor = null;
for (Constructor constructor : constructors)
{
constructor.setAccessible(true);
if (constructor.getParameterTypes().length == 0)
{
defaultConstructor = constructor;
break;
}
}
if (defaultConstructor != null)
{
return defaultConstructor.newInstance(new Object[0]);
}
else
{
Object instanceObject = ReflectionUtility.getMarshalWrapperInstance(type.getCanonicalName());
if (instanceObject == null)
{
Method cloneMethod = null;
//check to see if this complex type implements clone
Vector<Method> childMethodVector = ReflectionUtility.getMethodVector(type);
for (Method childMethod : childMethodVector)
{
if(childMethod.getName().equals("clone") && childMethod.getParameterTypes().length == 0 && Modifier.isPublic(childMethod.getModifiers()) == true)
{
cloneMethod = childMethod;
break;
}
}
if(cloneMethod != null && cloneableFieldInstances.length > 0)
{
return cloneMethod.invoke(cloneableFieldInstances[0]);
}
throw new InstantiationException("Couldn't find a default contructor for "+type.getCanonicalName());
}
else
{
return instanceObject;
}
}
}
public static Object getMarshalWrapperInstance(String className) throws InstantiationException, IllegalAccessException
{
Object instanceObject = null;
Set<String> marshalWrapperSet = CapoApplication.getAnnotationMap().get(MarshalWrapper.class.getCanonicalName());
for (String wrapperClassName : marshalWrapperSet)
{
try
{
MarshalWrapper marshalWrapper = Class.forName(wrapperClassName).getAnnotation(MarshalWrapper.class);
if(marshalWrapper.marshalledClass().getCanonicalName().equals(className))
{
MarshalWrapperInterface marshalWrapperInterface = (MarshalWrapperInterface) Class.forName(wrapperClassName).newInstance();
return marshalWrapperInterface.getInstance();
}
} catch (ClassNotFoundException classNotFoundException)
{
CapoApplication.logger.log(Level.WARNING, "Error getting document providers",classNotFoundException);
}
}
return instanceObject;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Constructor getDefaultConstructor(Class type)
{
Constructor[] constructors = null;
if(systemClassConstructorsHashMap.containsKey(type))
{
constructors = systemClassConstructorsHashMap.get(type);
}
else
{
constructors = type.getDeclaredConstructors();
systemClassConstructorsHashMap.put(type, constructors);
}
for (Constructor constructor : constructors)
{
constructor.setAccessible(true);
if (constructor.getParameterTypes().length == 0)
{
return constructor;
}
}
return null;
}
@SuppressWarnings("rawtypes")
public static boolean hasDefaultContructor(Class type)
{
Constructor[] constructors = null;
if(systemClassConstructorsHashMap.containsKey(type))
{
constructors = systemClassConstructorsHashMap.get(type);
}
else
{
constructors = type.getDeclaredConstructors();
systemClassConstructorsHashMap.put(type, constructors);
}
for (Constructor constructor : constructors)
{
constructor.setAccessible(true);
if (constructor.getParameterTypes().length == 0)
{
return true;
}
}
return false;
}
}