/**
* $Id: ReflectUtils.java 128 2014-03-18 22:27:33Z azeckoski $
* $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/ReflectUtils.java $
* ReflectUtil.java - entity-broker - 24 Aug 2007 6:43:14 PM - azeckoski
**************************************************************************
* Copyright (c) 2008 Aaron Zeckoski
* Licensed under the Apache License, Version 2
*
* A copy of the Apache License, Version 2 has been included in this
* distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Aaron Zeckoski (azeckoski@gmail.com) (aaronz@vt.edu) (aaron@caret.cam.ac.uk)
*/
package org.azeckoski.reflectutils;
import org.azeckoski.reflectutils.ClassFields.FieldFindMode;
import org.azeckoski.reflectutils.ClassFields.FieldsFilter;
import org.azeckoski.reflectutils.beanutils.Resolver;
import org.azeckoski.reflectutils.converters.api.Converter;
import org.azeckoski.reflectutils.exceptions.FieldnameNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Reflection utilities and utilities related to working with classes and their fields<br/>
* <br/>
* These are built to be compatible with <a href="http://commons.apache.org/beanutils/">Apache Commons BeanUtils</a>
* and the nesting structure works the same, refer to the apache BeanUtils project docs for details<br/>
* <br/>
* Handles field operations for properties (getters and setters), partial properties (only getter or only setter), and fields.
* This improves upon the BeanUtils limitation of handling only properties or the Google utilities limitation of handling
* only fields.<br/>
* <br/>
* Getting and setting fields supports simple, nested, indexed, and mapped values<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
* <br/>
* Includes support for dealing with annotations and working with field which have annotations on them. Methods
* for finding fields with an annotation and finding all annotations in a class or on a fields are included.
* <br/>
* Includes support for deep cloning, deep copying, and populating objects using auto-conversion. Also
* includes support for fuzzy copies where object data can be copied from one object to another without
* the objects being the same type.<br/>
* <br/>
* Also includes an extendable conversion system for converting between java types. This system also handles
* conversions between arrays, maps, collections, and scalars and improves upon the apache system by
* handling more types and handling object holders.<br/>
* <br/>
* Support for construction of any class and a set of utilities for determining what types of objects
* you are working with are also included. A method for executing a specific constructor can be used if more control if needed.
* <br/>
* The utilities cache reflection data for high performance operation but uses weak/soft caching to avoid holding open
* ClassLoaders and causing the caches to exist in memory permanently. The ability to override the caching mechanism
* with your own is supported.<br/>
* <br/>
* The utilities are modular and are meant to be extendable and overridable. All methods are protected or public so
* that the various utility classes can be easily overridden if needed.
*
* @author Aaron Zeckoski (azeckoski@gmail.com)
*/
public class ReflectUtils {
// CONSTRUCTORS
/**
* default constructor - protected since it should not really be used
*/
protected ReflectUtils() {
this( null, null, null, null );
}
/**
* Create a new copy of the reflection utilities so you can control various things like the field
* finding mode and the caching<br/>
* <b>WARNING</b>: This is expensive and recreating it over and over is not a good idea,
* you should use the static instance if you are not prepared to cache this object somewhere<br/>
* Use {@link ReflectUtils#getInstance()} to avoid recreating this over and over
*
* @param fieldFindMode (optional) see {@link FieldFindMode} enum for details, null for default
* @param reflectionCache (optional) a cache for holding class cache data (implements map), null to use the default internal cache
* @param resolver (optional) the field path name resolver to use when resolving EL style paths, null for default
* @param converters (optional) a map of converters to add to the default set, null to use the default set
*/
@SuppressWarnings({"unchecked", "SameParameterValue"})
public ReflectUtils(FieldFindMode fieldFindMode, Map<Class<?>, ClassFields> reflectionCache, Resolver resolver, Map<Class<?>, Converter<?>> converters) {
setFieldFindMode(fieldFindMode);
setReflectionCache(reflectionCache);
setResolver(resolver);
setConverters(converters);
ReflectUtils.setInstance(this);
}
// setter passthroughs
/**
* Set the mode used to find fields on classes (default {@link FieldFindMode#HYBRID}) <br/>
* <b>WARNING</b>: changing modes will clear the existing cache
*
* @param fieldFindMode see {@link FieldFindMode} enum for details, null for default
* @see FieldFindMode
*/
public void setFieldFindMode(FieldFindMode fieldFindMode) {
getClassDataCacher().setFieldFindMode(fieldFindMode);
}
/**
* Set the cache to be used for holding the reflection data,
* this allows control over where the reflection caches are stored,
* this should store the data in a way that it will not hold open the classloader the class comes from <br/>
* Note that you can set this to a map implementation which does not store anything to disable caching if you like
*
* @param reflectionCache a cache for holding class cache data (implements map), null to use the default internal cache
*/
@SuppressWarnings("unchecked")
public void setReflectionCache(Map<Class<?>, ClassFields> reflectionCache) {
getClassDataCacher().setReflectionCache(reflectionCache);
}
/**
* Set the field name path resolver to use
* @param resolver the field path name resolver to use when resolving EL style paths, null for default
* @see FieldUtils#setResolver(Resolver)
*/
public void setResolver(Resolver resolver) {
getFieldUtils().setResolver(resolver);
}
/**
* Set the object converters to add to the default converters
* @param converters a map of converters to add to the default set, null to just use the default set
*/
public void setConverters(Map<Class<?>, Converter<?>> converters) {
getConversionUtils().setConverters(converters);
}
/**
* Add a converter to the default set which will convert objects to the supplied type
* @param type the type this converter will convert objects to
* @param converter the converter
*/
public void addConverter(Class<?> type, Converter<?> converter) {
getConversionUtils().addConverter(type, converter);
}
/**
* Set the option to include the "class" field when reflecting over objects,
* the default for this is false (do not include)
* @param includeClassField if true then include the value from the "getClass()" method as "class" when encoding beans and maps
*/
public void setIncludeClassField(boolean includeClassField) {
getClassDataCacher().setIncludeClassField(includeClassField);
}
/*
* Everything below is a pass-through to the methods in the other utilities classes
* (sometimes with a little extra tie together logic),
* reflect utils is just a convenient way to access the various methods and allows
* a developer to override any of the methods as they are all happy and public and non-final
*/
/**
* Analyze a class and produce an object which contains information about it and its fields
* @param <T>
* @param beanClass any object class
* @return the ClassFields analysis object which contains the information about this object class
* @throws IllegalArgumentException if class is null or primitive
*/
public <T> ClassFields<T> analyzeClass(Class<T> beanClass) {
return getFieldUtils().analyzeClass(beanClass);
}
/**
* Construct an object for the class of the given type regardless of whether it has a default constructor,
* this will construct anything which has a valid class type including primitives,
* arrays, collections and even classes without default constructors,
* this will attempt to use the default constructor first if available though.
* It must be possible to construct the class without knowing something about it beforehand,
* (i.e. classes with only constructors which require non-null arguments will not be able
* to be constructed)
*
* @param <T>
* @param beanClass any object class
* @return the newly constructed object of the given class type
* (if primitive then a wrapped object will be returned which java will unwrap automatically)
*/
public <T> T constructClass(Class<T> beanClass) {
return getConstructorUtils().constructClass(beanClass);
}
/**
* Get the value of a field or property (getter method) from an object<br/>
* Getting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param fieldName the name of the field (property) to get the value of or the getter method without the "get" and lowercase first char
* @throws FieldnameNotFoundException if this fieldName does not exist on the object
* @throws IllegalArgumentException if a failure occurs while getting the field value
*/
public Object getFieldValue(Object object, String fieldName) {
return getFieldUtils().getFieldValue(object, fieldName);
}
/**
* Get the value of a field or getter method from an object allowing an annotation to override<br/>
* Getting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param fieldName the name of the field (property) to get the value of or the getter method without the "get" and lowercase first char
* @param annotationClass if the annotation class is set then we will attempt to get the value from the annotated field or getter method first
* @return the value of the field OR null if the value is null
* @throws FieldnameNotFoundException if this fieldName does not exist on the object and no annotation was found
* @throws IllegalArgumentException if a failure occurs while getting the field value
*/
public Object getFieldValue(Object object, String fieldName, Class<? extends Annotation> annotationClass) {
Object value;
Class<?> elementClass = object.getClass();
if (annotationClass != null) {
// try to find annotation first
String annotatedField = getFieldNameWithAnnotation(elementClass, annotationClass);
if (annotatedField != null) {
fieldName = annotatedField;
}
}
value = getFieldValue(object, fieldName);
return value;
}
/**
* Get the string value of a field or getter method from an object allowing an annotation to override<br/>
* Getting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param fieldName the name of the field (property) to get the value of or the getter method without the "get" and lowercase first char
* @param annotationClass if the annotation class is set then we will attempt to get the value from the annotated field or getter method first
* @return the string value of the field OR null if the value is null
* @throws FieldnameNotFoundException if this fieldName does not exist on the object and no annotation was found
* @throws IllegalArgumentException if a failure occurs while getting the field value
*/
public String getFieldValueAsString(Object object, String fieldName, Class<? extends Annotation> annotationClass) {
String sValue = null;
Object value = getFieldValue(object, fieldName, annotationClass);
if (value != null) {
sValue = getConversionUtils().convert(value, String.class); //value.toString();
}
return sValue;
}
/**
* Set the value on the object field or setter method, will convert if needed<br/>
* Setting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param fieldName the name of the field (property) to set the value of or the setter method without the "set" and lowercase first char
* @param value the value to set on this field, must match the type in the object (will not attempt to covert)
* @throws FieldnameNotFoundException if the fieldName could not be found in this object
* @throws UnsupportedOperationException if the value could not be converted to the field type
* @throws IllegalArgumentException if the params are null or other failures occur setting the value
*/
public void setFieldValue(Object object, String fieldName, Object value) {
getFieldUtils().setFieldValue(object, fieldName, value);
}
/**
* Sets a value on the field of an object and will attempt to convert the property if configured to do so<br/>
* Setting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param fieldName the name of the field (property) to set the value of or the setter method without the "set" and lowercase first char
* @param value the value to set on this field, does not have to match the type in the object,
* the type will be determined and then we will attempt to convert this value to the type in the field
* @param autoConvert if true then automatically try to convert the value to the type of the field being set,
* otherwise only set the field if the value matches the type
* @throws FieldnameNotFoundException if the fieldName could not be found in this object
* @throws IllegalArgumentException if the value type does not match the field type or the type could not be converted to the field type
* @throws UnsupportedOperationException if the value could not be converted to the field type
*/
public void setFieldValue(Object object, String fieldName, Object value, boolean autoConvert) {
getFieldUtils().setFieldValue(object, fieldName, value, autoConvert);
}
/**
* Get the types of the fields of a specific class type,
* returns the method names without the "get"/"is" part and camelCased
* @param type any class
* @return a map of field name -> class type
*/
public Map<String, Class<?>> getFieldTypes(Class<?> type) {
return getFieldTypes(type, null);
}
/**
* Get the types of the fields of a specific class type,
* returns the method names without the "get"/"is" part and camelCased
* @param type any class
* @param filter (optional) indicates the fields to return the types for, can be null for defaults
* @return a map of field name -> class type
*/
@SuppressWarnings("SameParameterValue")
public Map<String, Class<?>> getFieldTypes(Class<?> type, FieldsFilter filter) {
Map<String, Class<?>> types = getFieldUtils().getFieldTypes(type, filter);
return types;
}
/**
* Get the type of a field from a class
* @param type any class
* @param fieldName the name of the field (property) or a getter method converted to a field name
* @return the type of object stored in the field
* @throws FieldnameNotFoundException if the fieldName could not be found in this object
*/
public Class<?> getFieldType(Class<?> type, String fieldName) {
return getFieldUtils().getFieldType(type, fieldName);
}
/**
* Get a map of all fieldName -> value and all getterMethodName -> value without the word "get"
* where the method takes no arguments, in other words, all values available from an object (readable values)
* @param object any object
* @return a map of name -> value
* @throws IllegalArgumentException if failures occur
*/
public Map<String, Object> getObjectValues(Object object) {
return getObjectValues(object, FieldsFilter.READABLE, false);
}
/**
* Get a map of all fieldName -> value and all getterMethodName -> value without the word "get"
* where the method takes no arguments, in other words, all values available from an object (readable values)
* @param object any object
* @param filter (optional) indicates the fields to return the values for, can be null for defaults
* @param includeClassField if true then the value from the "getClass()" method is returned as part of the
* set of object values with a type of {@link Class} and a field name of "class"
* @return a map of name -> value
* @throws IllegalArgumentException if failures occur
*/
public Map<String, Object> getObjectValues(Object object, FieldsFilter filter, boolean includeClassField) {
return getFieldUtils().getFieldValues(object, filter, includeClassField);
}
/**
* Find the getter field on a class which has the given annotation
* @param elementClass any class
* @param annotationClass the annotation type which is expected to be on the field
* @return the name of the field or null if no fields are found with the indicated annotation
* @throws IllegalArgumentException if the annotation class is null
*/
public String getFieldNameWithAnnotation(Class<?> elementClass, Class<? extends Annotation> annotationClass) {
String fieldName;
if (annotationClass == null) {
throw new IllegalArgumentException("the annotationClass must not be null");
}
ClassFields<?> cf = getFieldUtils().analyzeClass(elementClass);
fieldName = cf.getFieldNameByAnnotation(annotationClass);
return fieldName;
}
/**
* Deep clone an object and all the values in it into a brand new object of the same type,
* this will traverse the bean and will make new objects for all non-null values contained in the object
*
* @param <T>
* @param object any java object, this can be a list, map, array, or any simple
* object, it does not have to be a custom object or even a java bean,
* also works with DynaBeans
* @param maxDepth the number of objects to follow when traveling through the object and copying
* the values from it, 0 means to only copy the simple values in the object, any objects will
* be ignored and will end up as nulls, 1 means to follow the first objects found and copy all
* of their simple values as well, and so forth
* @param fieldNamesToSkip the names of fields to skip while cloning this object,
* this only has an effect on the bottom level of the object, any fields found
* on child objects will always be copied (if the maxDepth allows)
* @return the cloned object
*/
public <T> T clone(T object, int maxDepth, String[] fieldNamesToSkip) {
return getDeepUtils().deepClone(object, maxDepth, fieldNamesToSkip);
}
/**
* Deep copies one object into another, this is primarily for copying between identical types of objects but
* it can also handle copying between objects which are quite different,
* this does not just do a reference copy of the values but actually creates new objects in the current classloader
*
* @param orig the original object to copy from
* @param dest the object to copy the values to (must have the same fields with the same types)
* @param maxDepth the number of objects to follow when traveling through the object and copying
* the values from it, 0 means to only copy the simple values in the object, any objects will
* be ignored and will end up as nulls, 1 means to follow the first objects found and copy all
* of their simple values as well, and so forth
* @param fieldNamesToSkip the names of fields to skip while cloning this object,
* this only has an effect on the bottom level of the object, any fields found
* on child objects will always be copied (if the maxDepth allows)
* @param ignoreNulls if true then nulls are not copied and the destination retains the value it has,
* if false then nulls are copied and the destination value will become a null if the original value is a null
* @throws IllegalArgumentException if the copy cannot be completed because the objects to copy do not have matching fields or types
*/
public void copy(Object orig, Object dest, int maxDepth, String[] fieldNamesToSkip, boolean ignoreNulls) {
getDeepUtils().deepCopy(orig, dest, maxDepth, fieldNamesToSkip, ignoreNulls);
}
/**
* This handles the cloning of objects to maps, it is recursive and is a deep operation which
* traverses the entire object and clones every part of it, when converting to a map this will ensure
* that there are no objects which are not part of java.lang or java.util in the new map<br/>
* NOTE: This can handle simple objects (non-maps and non-beans) but will have to make up the initial map key
* in the returned map, "data" will be used as the key<br/>
* NOTE: Nulls are allowed to pass through this method (i.e. passing in a null object results in a null output)
*
* @param object any java object
* @param maxDepth the number of objects to follow when traveling through the object and copying
* the values from it, 0 means to only copy the simple values in the object, any objects will
* be ignored and will end up as nulls, 1 means to follow the first objects found and copy all
* of their simple values as well, and so forth
* @param fieldNamesToSkip the names of fields to skip while cloning this object,
* this only has an effect on the bottom level of the object, any fields found
* on child objects will always be copied (if the maxDepth allows)
* @param ignoreNulls if true then nulls are not copied and the destination retains the value it has,
* if false then nulls are copied and the destination value will become a null if the original value is a null
* @param ignoreTransient if true then all transient fields will be skipped, useful when serializing
* @param initialKey (optional) the initial key to use when converting simpler objects to maps
* @return the resulting map which contains the cloned data from the object
*/
public Map<String, Object> map(Object object, int maxDepth, String[] fieldNamesToSkip, boolean ignoreNulls, boolean ignoreTransient, String initialKey) {
return getDeepUtils().deepMap(object, maxDepth, fieldNamesToSkip, ignoreNulls, ignoreTransient, initialKey);
}
/**
* Populates an object with the values in the properties map,
* this will not fail if the fieldName in the map is not a property on the
* object or the fieldName cannot be written to with the value in the object.
* This will attempt to convert the provided object values into the right values
* to place in the object<br/>
* <b>NOTE:</b> simple types like numbers and strings can almost always be converted from
* just about anything though they will probably end up as 0 or ""<br/>
* Setting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param properties a map of fieldNames -> Object
* @return the list of fieldNames which were successfully written to the object
* @throws IllegalArgumentException if the arguments are invalid
*/
public List<String> populate(Object object, Map<String, Object> properties) {
return getDeepUtils().populate(object, properties);
}
/**
* Populates an object with the String array values in the params map,
* this will not fail if the fieldName in the map is not a property on the
* object or the fieldName cannot be written to with the value in the object<br/>
* Arrays which are length 1 will be converted to a string before they are set on the target field <br/>
* Setting fields supports simple, nested, indexed, and mapped values:<br/>
* <b>Simple:</b> Get/set a field in a bean (or map), Example: "title", "id"<br/>
* <b>Nested:</b> Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"<br/>
* <b>Indexed:</b> Get/set a list/array item by index in a bean, Example: "myList[1]", "anArray[2]"<br/>
* <b>Mapped:</b> Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"<br/>
*
* @param object any object
* @param params a map of fieldNames -> String[] (normally from an http request)
* @return the list of fieldNames which were successfully written to the object
* @throws IllegalArgumentException if the arguments are invalid
*/
public List<String> populateFromParams(Object object, Map<String, String[]> params) {
return getDeepUtils().populateFromParams(object, params);
}
/**
* Converts an object to any other object if possible
* @see ConversionUtils#convert(Object, Class)
*
* @param <T>
* @param object any object
* @param type any class type that you want to try to convert the object to
* @return the converted value (allows null to pass through except in the case of primitives which become the primitive default)
* @throws UnsupportedOperationException if the conversion cannot be completed
*/
public <T> T convert(Object object, Class<T> type) {
T convert = getConversionUtils().convert(object, type);
return convert;
}
// STATIC methods
/**
* @param methodName a getter or setter style method name (e.g. getThing, setStuff, isType)
* @return the fieldName equivalent (thing, stuff, type)
*/
public static String makeFieldNameFromMethod(String methodName) {
return ClassFields.makeFieldNameFromMethod(methodName);
}
/**
* Capitalize a string
* @param input any string
* @return the string capitalized (e.g. myString -> MyString)
*/
public static String capitalize(String input) {
return ClassFields.capitalize(input);
}
/**
* undo string capitalization
* @param input any string
* @return the string uncapitalized (e.g. MyString -> myString)
*/
public static String unCapitalize(String input) {
return ClassFields.unCapitalize(input);
}
/**
* @return a list of all superclasses and implemented interfaces by the supplied class,
* recursively to the base, up to but excluding Object.class. These will be listed in order from
* the supplied class, all concrete superclasses in ascending order, and then finally all
* interfaces in recursive ascending order.<br/>
* This will include duplicates if any superclasses implement the same classes
*/
public static List<Class<?>> getSuperclasses(Class<?> clazz) {
return ClassLoaderUtils.getSuperclasses(clazz);
}
/**
* Finds a class type that is in the containing collection,
* will always return something (failsafe to Object.class)
* @param collection
* @return the class type contained in this collecion
*/
@SuppressWarnings("unchecked")
public static Class<?> getClassFromCollection(Collection collection) {
return ClassLoaderUtils.getClassFromCollection(collection);
}
/**
* Checks to see if an array contains a value,
* will return false if a null value is supplied
*
* @param <T>
* @param array any array of objects
* @param value the value to check for
* @return true if the value is found, false otherwise
*/
public static <T> boolean contains(T[] array, T value) {
return ArrayUtils.contains(array, value);
}
/**
* Append an item to the end of an array and return the new array
*
* @param array an array of items
* @param value the item to append to the end of the new array
* @return a new array with value in the last spot
*/
public static <T> T[] appendArray(T[] array, T value) {
return ArrayUtils.appendArray(array, value);
}
/**
* Take an array of anything and turn it into a string
*
* @param array any array
* @return a string representing that array
*/
public static String arrayToString(Object[] array) {
return ArrayUtils.arrayToString(array);
}
/**
* @param text string to make MD5 hash from
* @param maxLength
* @return an MD5 hash no longer than maxLength
*/
public static String makeMD5(String text, int maxLength) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Stupid java sucks for MD5", e);
}
md.update(text.getBytes());
// convert the binary md5 hash into hex
StringBuilder sb = new StringBuilder();
byte[] b_arr = md.digest();
for (byte aB_arr : b_arr) {
// convert the high nibble
byte b = aB_arr;
b >>>= 4;
b &= 0x0f; // this clears the top half of the byte (intentional)
sb.append(Integer.toHexString(b));
// convert the low nibble
b = aB_arr;
b &= 0x0F;
sb.append(Integer.toHexString(b));
}
String md5 = sb.toString();
if (maxLength > 0 && md5.length() > maxLength) {
md5 = md5.substring(0, maxLength);
}
return md5;
}
/**
* Finds a map value for a key (or set of keys) if it exists in the map and returns the string value of it
* @param map any map with strings as keys
* @param keys an array of keys to try to find in order
* @return the string value OR null if it could not be found for any of the given keys
*/
public static String findMapStringValue(Map<String, ?> map, String[] keys) {
if (map == null || keys == null) {
return null;
}
String value = null;
try {
for (String key : keys) {
if (map.containsKey(key)) {
Object oVal = map.get(key);
if (oVal != null) {
value = oVal.toString();
break;
}
}
}
} catch (RuntimeException e) {
// in case the given map is not actually of the right types at runtime
value = null;
}
return value;
}
/**
* This is necessary because of the stupid capitalization rules used by Sun
* @param name a property descriptor name to compare to a fieldname
* @param fieldName a fieldName
* @return true if they are almost the same or actually the same
*/
public static boolean pdNameCompare(String name, String fieldName) {
// AZ try tweaking the name slightly (uncap the first char) and try again, this fixes the sThing to SThing issue
boolean match = false;
if (name == null || fieldName == null) {
match = false;
} else if (name.length() == 0 || fieldName.length() == 0) {
match = false;
} else if (name.equals(fieldName)) {
match = true;
} else if (ReflectUtils.unCapitalize(name).equals(fieldName)) {
match = true;
} else if (ReflectUtils.capitalize(name).equals(fieldName)) {
match = true;
}
return match;
}
@Override
public String toString() {
return "Reflect::c="+ReflectUtils.timesCreated+":s="+singleton+":" + getClassDataCacher() + ":" + getFieldUtils() + ":" + getConversionUtils();
}
// STATIC access
protected static SoftReference<ReflectUtils> instanceStorage;
// protected static Map<ClassLoader, ReflectUtil> utilByClassLoader =
// new ReferenceMap<ClassLoader, ReflectUtil>(ReferenceType.WEAK, ReferenceType.STRONG);
/**
* Get a singleton instance of this class to work with (stored statically) <br/>
* <b>WARNING</b>: do not hold onto this object or cache it yourself, call this method again if you need it again
* @return a singleton instance of this class
*/
public static ReflectUtils getInstance() {
ReflectUtils instance = (instanceStorage == null ? null : instanceStorage.get());
if (instance == null) {
instance = ReflectUtils.setInstance(null);
}
return instance;
}
/**
* Set the singleton instance of the class which will be stored statically
* @param newInstance the instance to use as the singleton instance
*/
public static ReflectUtils setInstance(ReflectUtils newInstance) {
ReflectUtils instance = newInstance;
if (instance == null) {
instance = new ReflectUtils();
instance.singleton = true;
}
ReflectUtils.timesCreated++;
instanceStorage = new SoftReference<ReflectUtils>(instance);
return instance;
}
public static void clearInstance() {
instanceStorage.clear();
}
private static int timesCreated = 0;
public static int getTimesCreated() {
return timesCreated;
}
private boolean singleton = false;
/**
* @return true if this is a singleton instance
*/
public boolean isSingleton() {
return singleton;
}
// utils getters
protected FieldUtils getFieldUtils() {
return FieldUtils.getInstance();
}
protected ConstructorUtils getConstructorUtils() {
return ConstructorUtils.getInstance();
}
protected ClassDataCacher getClassDataCacher() {
return ClassDataCacher.getInstance();
}
protected ConversionUtils getConversionUtils() {
return ConversionUtils.getInstance();
}
protected DeepUtils getDeepUtils() {
return DeepUtils.getInstance();
}
}