org.xillium.base.beans.Beans Maven / Gradle / Ivy
package org.xillium.base.beans;
import java.beans.*;
import java.lang.reflect.*;
import java.math.*;
import java.util.*;
import java.util.regex.*;
import org.xillium.base.Bifunctor;
import org.xillium.base.type.typeinfo;
import org.xillium.base.util.ValueOf;
/**
* A collection of commonly used bean manipulation utilities.
*/
public class Beans {
/**
* Tests whether a non-primitive type is directly displayable.
*
* @param type the class object to test
* @return whether the type is displayable
*/
public static boolean isDisplayable(Class> type) {
return Enum.class.isAssignableFrom(type)
|| type == java.net.URL.class || type == java.io.File.class
|| java.math.BigInteger.class.isAssignableFrom(type)
|| java.math.BigDecimal.class.isAssignableFrom(type)
|| java.util.Date.class.isAssignableFrom(type)
|| java.sql.Date.class.isAssignableFrom(type)
|| java.sql.Time.class.isAssignableFrom(type)
|| java.sql.Timestamp.class.isAssignableFrom(type);
}
/**
* Class lookup by name, which also accepts primitive type names.
*
* @param name the full name of the class
* @return the class object
* @throws ClassNotFoundException if the class is not found
*/
public static Class> classForName(String name) throws ClassNotFoundException {
if ("void".equals(name)) return void.class;
if ("char".equals(name)) return char.class;
if ("boolean".equals(name)) return boolean.class;
if ("byte".equals(name)) return byte.class;
if ("short".equals(name)) return short.class;
if ("int".equals(name)) return int.class;
if ("long".equals(name)) return long.class;
if ("float".equals(name)) return float.class;
if ("double".equals(name)) return double.class;
return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
}
/**
* Tests whether a class is a primitive type. Different from Class.isPrimitive, this method consider
* the following also as "primitive types".
*
* - Wrapper classes of the primitive types
*
- Class
*
- String
*
*
* @param type the type to test
* @return whether the type is primitive
*/
public static boolean isPrimitive(Class> type) {
return type == Class.class || type == String.class
|| type == Character.class || type == Character.TYPE
|| type == Boolean.class || type == Boolean.TYPE
|| type == Byte.class || type == Byte.TYPE
|| type == Short.class || type == Short.TYPE
|| type == Integer.class || type == Integer.TYPE
|| type == Long.class || type == Long.TYPE
|| type == Float.class || type == Float.TYPE
|| type == Double.class || type == Double.TYPE
|| type == Void.TYPE;
}
/**
* Converts a boxed type to its primitive counterpart.
*
* @param type the type to convert
* @return the primitive counterpart
*/
public static Class> toPrimitive(Class> type) {
if (type.isPrimitive()) {
return type;
} else if (type == Boolean.class) {
return Boolean.TYPE;
} else if (type == Character.class) {
return Character.TYPE;
} else if (type == Byte.class) {
return Byte.TYPE;
} else if (type == Short.class) {
return Short.TYPE;
} else if (type == Integer.class) {
return Integer.TYPE;
} else if (type == Long.class) {
return Long.TYPE;
} else if (type == Float.class) {
return Float.TYPE;
} else if (type == Double.class) {
return Double.TYPE;
} else {
return null;
}
}
/**
* Boxes a primitive type.
*
* @param type the primitive type to box
* @return the boxed type
*/
public static Class> boxPrimitive(Class> type) {
if (!type.isPrimitive()) {
return type;
} else if (type == byte.class) {
return Byte.class;
} else if (type == short.class) {
return Short.class;
} else if (type == int.class) {
return Integer.class;
} else if (type == long.class) {
return Long.class;
} else if (type == boolean.class) {
return Boolean.class;
} else if (type == float.class) {
return Float.class;
} else if (type == double.class) {
return Double.class;
} else if (type == char.class) {
return Character.class;
} else {
return type;
}
}
/**
* Boxes primitive types.
*
* @param types an array of primitive types to box
* @return an array of boxed types
*/
public static Class>[] boxPrimitives(Class>[] types) {
for (int i = 0; i < types.length; ++i) {
types[i] = boxPrimitive(types[i]);
}
return types;
}
/**
* Returns a known field by name from the given class disregarding its access control setting, looking through
* all super classes if needed.
*
* @param type the class to start with
* @param name the name of the field
* @return a Field object representing the known field
* @throws NoSuchFieldException if the field is not found
*/
public static Field getKnownField(Class> type, String name) throws NoSuchFieldException {
NoSuchFieldException last = null;
do {
try {
Field field = type.getDeclaredField(name);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException x) {
last = x;
type = type.getSuperclass();
}
} while (type != null);
throw last;
}
/**
* Returns all known fields in the given class and all its super classes.
*
* @param type the class to start with
* @return an array of Field objects representing all known fields
* @throws SecurityException if a security manager denies access
*/
public static Field[] getKnownFields(Class> type) throws SecurityException {
List fields = new ArrayList();
while (type != null) {
for (Field field: type.getDeclaredFields()) {
field.setAccessible(true);
fields.add(field);
}
type = type.getSuperclass();
}
return fields.toArray(new Field[fields.size()]);
}
/**
* Returns all known fields in the given class and all its super classes.
*
* @param type the class to start with
* @return an array of Field objects representing all known instance fields
* @throws SecurityException if a security manager denies access
*/
public static Field[] getKnownInstanceFields(Class> type) throws SecurityException {
List fields = new ArrayList();
while (type != null) {
for (Field field: type.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) continue;
field.setAccessible(true);
fields.add(field);
}
type = type.getSuperclass();
}
return fields.toArray(new Field[fields.size()]);
}
/**
* Overrides access control of an AccessibleObject, facilitating fluent coding style.
*
* @param the type of the accessible object
* @param object the object to start with
* @return the same object with its access control overridden to allow all access
* @throws SecurityException if a security manager denies access
*/
public static T accessible(T object) throws SecurityException {
object.setAccessible(true);
return object;
}
/**
* Creates an instance of a given type by choosing the best constructor that matches the given list of arguments.
*
* @param the type of the object to create
* @param type the type of the object to create
* @param args the arguments to pass to the constructor
* @return the object created
* @throws NoSuchMethodException if an appropriate constructor cannot be found
* @throws IllegalAccessException if an appropriate constructor cannot be accessed
* @throws InvocationTargetException if errors occur while invoking the constructor
* @throws InstantiationException if the constructor itself fails in any way
*/
public static T create(Class type, Object... args) throws
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class>[] argumentTypes = new Class>[args.length];
for (int i = 0; i < args.length; i++) {
argumentTypes[i] = args[i].getClass();
}
return type.cast(choose(type.getConstructors(), new ConstructorParameterExtractor(), null, argumentTypes).newInstance(args));
}
/**
* Creates an instance of a given type by choosing the best constructor that matches the given list of arguments.
*
* @param the type of the object to create
* @param type the type of the object to create
* @param args the arguments to pass to the constructor
* @param offset the offset into the args array from which to retrieve arguments
* @param count the number of arguments from the args array to pass to the constructor
* @return the object created
* @throws NoSuchMethodException if an appropriate constructor cannot be found
* @throws IllegalAccessException if an appropriate constructor cannot be accessed
* @throws InvocationTargetException if errors occur while invoking the constructor
* @throws InstantiationException if the constructor itself fails in any way
*/
public static T create(Class type, Object[] args, int offset, int count) throws
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (offset != 0 || count != args.length) {
return create(type, Arrays.copyOfRange(args, offset, offset + count));
} else {
return create(type, args);
}
}
/**
* Invokes a bean method by choosing the best method that matches the given name and the list of arguments.
*
* @param bean the invocation target
* @param name the name of the method to invoke
* @param args the arguments to pass to the method
* @param offset the offset into the args array from which to retrieve arguments
* @param count the number of arguments from the args array to pass to the method
* @return the return value from the method
* @throws NoSuchMethodException if an appropriate method cannot be found
* @throws IllegalAccessException if an appropriate method cannot be accessed
* @throws InvocationTargetException if errors occur while invoking the method
*/
public static Object invoke(Object bean, String name, Object[] args, int offset, int count) throws
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
if (offset != 0 || count != args.length) {
return invoke(bean, name, Arrays.copyOfRange(args, offset, offset + count));
} else {
return invoke(bean, name, args);
}
}
/**
* Invokes a bean method by choosing the best method that matches the given name and the list of arguments.
*
* @param bean the invocation target
* @param name the name of the method to invoke
* @param args the arguments to pass to the method
* @return the return value from the method
* @throws NoSuchMethodException if an appropriate method cannot be found
* @throws IllegalAccessException if an appropriate method cannot be accessed
* @throws InvocationTargetException if errors occur while invoking the method
*/
public static Object invoke(Object bean, String name, Object... args) throws
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class>[] argumentTypes = new Class>[args.length];
for (int i = 0; i < args.length; i++) {
argumentTypes[i] = args[i].getClass();
}
return choose(bean.getClass().getMethods(), new MethodParameterExtractor(), name, argumentTypes).invoke(bean, args);
}
/**
* Chooses a method/constructor of a given name, whose signature best matches the given list of argument types.
*
* @param the type of the member/constructor to return
* @param candidates the list of method/constructor candidates to choose from
* @param pe the ParameterExtractor
* @param name the name of the method/constructor
* @param argumentTypes the argument types
* @return the method/constructor
* @throws NoSuchMethodException if an appropriate method/constructor cannot be found
*/
public static T choose(T[] candidates, ParameterExtractor pe, String name, Class>[] argumentTypes)
throws NoSuchMethodException {
T chosenCandidate = null;
Class>[] chosenParamTypes = null;
// try to find the most applicable candidate
Search: for (int i = 0; i < candidates.length; i++) {
//System.err.println("Looking at candidate " + candidates[i]);
// ignore functions with different name
if (name != null && !candidates[i].getName().equals(name)) continue;
// ignore covariance on static candidates
if (Modifier.isStatic(candidates[i].getModifiers())) continue;
//Class>[] parameterTypes = candidates[i].getParameterTypes();
Class>[] parameterTypes = pe.getParameterTypes(candidates[i]);
// ignore functions with wrong number of parameters
if (parameterTypes.length != argumentTypes.length) continue;
// coerce the primitives to objects
boxPrimitives(parameterTypes);
// ignore functions with incompatible parameter types
for (int j = 0; j < parameterTypes.length; j++) {
if (!parameterTypes[j].isAssignableFrom(argumentTypes[j])) continue Search;
}
//System.err.println("Considering candidate " + candidates[i]);
// if this is the first match then save it
if (chosenCandidate == null) {
chosenCandidate = candidates[i];
chosenParamTypes = parameterTypes;
} else {
// if this candidate is more specific in compatibility then save it
for (int j = 0; j < chosenParamTypes.length; j++) {
if (!chosenParamTypes[j].isAssignableFrom(parameterTypes[j])) continue Search;
}
// this is the best fit so far
chosenCandidate = candidates[i];
chosenParamTypes = parameterTypes;
//System.err.println("Best candidate so far " + chosenCandidate);
}
}
// return to the caller indicating that candidate was not found
if (chosenCandidate == null) {
throw(new NoSuchMethodException("Method not found: " + name));
}
//System.err.println("Chosen candidate is " + chosenCandidate);
// return the covariant candidate
return accessible(chosenCandidate); // Java bug #4071957 - have to call setAccessible even on public methods
}
private static interface ParameterExtractor {
public Class>[] getParameterTypes(Object object);
}
private static class ConstructorParameterExtractor implements ParameterExtractor {
@SuppressWarnings("unchecked")
public Class>[] getParameterTypes(Object object) {
return ((Constructor)object).getParameterTypes();
}
}
private static class MethodParameterExtractor implements ParameterExtractor {
public Class>[] getParameterTypes(Object object) {
return ((Method)object).getParameterTypes();
}
}
/**
* Assigns a value to the field in an object, converting value type as necessary.
*
* @param object an object
* @param field a field of the object
* @param value a value to set to the field
* @throws IllegalArgumentException if the value is not acceptable by the field
* @throws IllegalAccessException if a security manager denies access
*/
@SuppressWarnings("unchecked")
public static void setValue(Object object, Field field, Object value) throws IllegalArgumentException, IllegalAccessException {
if (value == null) {
//if (Number.class.isAssignableFrom(field.getType())) {
//value = java.math.BigDecimal.ZERO;
//} else return;
return;
}
try {
field.setAccessible(true);
field.set(object, value);
} catch (IllegalArgumentException x) {
if (value instanceof Number) {
// size of "value" bigger than that of "field"?
try {
Number number = (Number)value;
Class> ftype = field.getType();
if (Enum.class.isAssignableFrom(ftype)) {
field.set(object, ftype.getEnumConstants()[number.intValue()]);
} else if (BigDecimal.class.isAssignableFrom(ftype)) {
field.set(object, BigDecimal.valueOf(number.doubleValue()));
} else if (BigInteger.class.isAssignableFrom(ftype)) {
field.set(object, BigInteger.valueOf(number.longValue()));
} else if (Double.TYPE == ftype || Double.class.isAssignableFrom(ftype)) {
field.set(object, number.doubleValue());
} else if (Float.TYPE == ftype || Float.class.isAssignableFrom(ftype)) {
field.set(object, number.floatValue());
} else if (Long.TYPE == ftype || Long.class.isAssignableFrom(ftype)) {
field.set(object, number.longValue());
} else if (Integer.TYPE == ftype || Integer.class.isAssignableFrom(ftype)) {
field.set(object, number.intValue());
} else if (Short.TYPE == ftype || Short.class.isAssignableFrom(ftype)) {
field.set(object, number.shortValue());
} else {
field.set(object, number.byteValue());
}
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
} else if (value instanceof java.sql.Timestamp) {
try {
field.set(object, new java.sql.Date(((java.sql.Timestamp)value).getTime()));
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
} else if (value instanceof String) {
field.set(object, new ValueOf(field.getType(), field.getAnnotation(typeinfo.class)).invoke((String)value));
} else {
throw x;
}
}
}
/**
* Converts a String representation into a value of a given type. Conversion attempts are made in the following order:
*
* - Try to invoke {@code public static SomeClass valueOf(String text)}.
* - Try to invoke {@code public static SomeClass valueOf(Class
type, String text)},
* or {@code public static SomeClass valueOf(Class type1, Class type2, ..., String text)} if a @typeinfo annotation exists to
* give argument types {@code T}, {@code V}, ...
* - Try to invoke {@code
(String text)}.
*
*
* @param the target class
* @param type the target class
* @param value the string to convert
* @param annotation an optional typeinfo annotation containing type arguments to {@code type}, which should be a generic type in this case
* @return the converted value
* @throws IllegalArgumentException if all conversion attempts fail
*/
@Deprecated
@SuppressWarnings("unchecked")
public static T valueOf(Class type, String value, typeinfo annotation) {
if (type.equals(String.class)) {
return type.cast(value);
} else {
try {
Class> boxed = boxPrimitive(type);
try {
return (T)boxed.getMethod("valueOf", String.class).invoke(null, value);
} catch (NoSuchMethodException x) {
try {
return (T)boxed.getMethod("valueOf", Class.class, String.class).invoke(null, annotation != null ? annotation.value()[0] : type, value);
} catch (NoSuchMethodException y) {
return (T)boxed.getConstructor(String.class).newInstance(value);
}
}
} catch (Exception x) {
throw new IllegalArgumentException(x.getMessage(), x);
}
}
}
/**
* Converts a String representation into a value of a given type. This method calls
* {@code valueOf(type, value, null)}.
*
* @param the target class
* @param type the target class
* @param value the string to convert
* @return the converted value
* @throws IllegalArgumentException if all conversion attempts fail
*/
@Deprecated
public static T valueOf(Class type, String value) {
return valueOf(type, value, null);
}
/**
* Fills empty, identically named public fields with values from another object.
*
* @param the class of the destination object
* @param destination a destination object
* @param source a source object
* @return the same destination object with fields filled by source
*/
public static T fill(T destination, Object source) {
if (destination != source) {
Class> stype = source.getClass();
for (Field field: destination.getClass().getFields()) {
try {
Object value = field.get(destination);
if (value == null) {
field.set(destination, stype.getField(field.getName()).get(source));
}
} catch (Exception x) {
}
}
}
return destination;
}
/**
* Overrides identically named public fields with non-empty values from another object.
*
* @param the class of the destination object
* @param destination a destination object
* @param source a source object
* @return the same destination object with fields overridden by source
*/
public static T override(T destination, Object source) {
if (destination != source) {
Class> dtype = destination.getClass();
for (Field field: source.getClass().getFields()) {
try {
Object value = field.get(source);
if (value != null) {
dtype.getField(field.getName()).set(destination, value);
}
} catch (Exception x) {
}
}
}
return destination;
}
/**
* Updates an object by modifying all of its public fields that match a certain name pattern or a super type
* with a transforming function.
*
* @param the class of the object
* @param the class of the object's public fields to be updated
* @param object a Java object
* @param type an upper bound Class matching, or boxing, the type of the data members
* @param pattern an optional regex that matches the fields' names
* @param updater a Bifunctor
* @return the same object with fields updated
*/
public static T update(T object, Class type, String pattern, Bifunctor updater) {
if (type.isPrimitive()) {
throw new IllegalArgumentException("Primitive type must be boxed");
}
Pattern regex = pattern != null ? Pattern.compile(pattern) : null;
for (Field field: object.getClass().getFields()) {
if ((regex == null || regex.matcher(field.getName()).matches()) && type.isAssignableFrom(boxPrimitive(field.getType()))) {
try {
field.set(object, updater.invoke(field.getName(), type.cast(field.get(object))));
} catch (Exception x) {}
}
}
return object;
}
/**
* A convenience utility method to convert a bean to a formatted string.
*
* @param bean an object
* @return a string representation of the object
*/
public static String toString(Object bean) {
try {
return bean != null ? print(new StringBuilder(), bean, 0).toString() : null;
} catch (IntrospectionException x) {
return String.valueOf(bean) + "(***" + x.getMessage() + ')';
}
}
/**
* Prints a bean to the StringBuilder.
*
* @param sb a StringBuilder
* @param bean an object
* @param level indentation level
* @return the original StringBuilder
* @throws IntrospectionException if bean introspection fails by {@link java.beans.Introspector Introspector}
*/
public static StringBuilder print(StringBuilder sb, Object bean, int level) throws IntrospectionException {
return print(sb, Collections.newSetFromMap(new IdentityHashMap
© 2015 - 2025 Weber Informatics LLC | Privacy Policy