All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twelvemonkeys.lang.BeanUtil Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.lang;

import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;

/**
 * A utility class with some useful bean-related functions.
 * 

* NOTE: This class is not considered part of the public API and may be changed without notice *

* * @author Harald Kuhr * @author last modified by $Author: haku $ * * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/BeanUtil.java#2 $ */ public final class BeanUtil { // Disallow creating objects of this type private BeanUtil() { } /** * Gets a property value from the given object, using reflection. * Now supports getting values from properties of properties * (recursive). * * @param pObject The object to get the property from * @param pProperty The name of the property * * @return A string containing the value of the given property, or {@code null} * if it can not be found. */ public static Object getPropertyValue(Object pObject, String pProperty) { // TODO: Remove System.err's... Create new Exception? Hmm.. // TODO: Support get(Object) method of Collections! // Handle lists and arrays with [] (index) operator if (pObject == null || pProperty == null || pProperty.length() < 1) { return null; } Class objClass = pObject.getClass(); Object result = pObject; // Method for method... String subProp; int begIdx = 0; int endIdx = begIdx; while (begIdx < pProperty.length() && begIdx >= 0) { endIdx = pProperty.indexOf('.', endIdx + 1); if (endIdx > 0) { subProp = pProperty.substring(begIdx, endIdx); begIdx = endIdx + 1; } else { // The final property! // If there's just the first-level property, subProp will be // equal to property subProp = pProperty.substring(begIdx); begIdx = -1; } // Check for "[" and "]" Object[] param = null; Class[] paramClass = new Class[0]; int begBracket; if ((begBracket = subProp.indexOf('[')) > 0) { // An error if there is no matching bracket if (!subProp.endsWith("]")) { return null; } String between = subProp.substring(begBracket + 1, subProp.length() - 1); subProp = subProp.substring(0, begBracket); // If brackets exist, check type of argument between brackets param = new Object[1]; paramClass = new Class[1]; //try { // TODO: isNumber returns true, even if too big for integer... if (StringUtil.isNumber(between)) { // We have a number // Integer -> array subscript -> getXXX(int i) try { // Insert param and it's Class param[0] = Integer.valueOf(between); paramClass[0] = Integer.TYPE; // int.class } catch (NumberFormatException e) { // ?? // Probably too small or too large value.. } } else { //catch (NumberFormatException e) { // Not a number... Try String // String -> Hashtable key -> getXXX(String str) // Insert param and it's Class param[0] = between.toLowerCase(); paramClass[0] = String.class; } } Method method; String methodName = "get" + StringUtil.capitalize(subProp); try { // Try to get the "get" method for the given property method = objClass.getMethod(methodName, paramClass); } catch (NoSuchMethodException e) { System.err.print("No method named \"" + methodName + "()\""); // The array might be of size 0... if (paramClass.length > 0 && paramClass[0] != null) { System.err.print(" with the parameter " + paramClass[0].getName()); } System.err.println(" in class " + objClass.getName() + "!"); return null; } // If method for some reason should be null, give up if (method == null) { return null; } try { // We have a method, try to invoke it // The resutling object will be either the property we are // Looking for, or the parent // System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")"); result = method.invoke(result, param); } catch (InvocationTargetException e) { System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param)); e.getTargetException().printStackTrace(); e.printStackTrace(); return null; } catch (IllegalAccessException e) { e.printStackTrace(); return null; } catch (NullPointerException e) { System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")"); e.printStackTrace(); return null; } if (result != null) { // Get the class of the reulting object objClass = result.getClass(); } else { return null; } } // while return result; } /** * Sets the property value to an object using reflection. * Supports setting values of properties that are properties of * properties (recursive). * * @param pObject The object to get a property from * @param pProperty The name of the property * @param pValue The property value * * @throws NoSuchMethodException if there's no write method for the * given property * @throws InvocationTargetException if invoking the write method failed * @throws IllegalAccessException if the caller class has no access to the * write method */ public static void setPropertyValue(Object pObject, String pProperty, Object pValue) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // // TODO: Support set(Object, Object)/put(Object, Object) methods // of Collections! // Handle lists and arrays with [] (index) operator Class paramType = pValue != null ? pValue.getClass() : Object.class; // Preserve references Object obj = pObject; String property = pProperty; // Recurse and find real parent if property contains a '.' int dotIdx = property.indexOf('.'); if (dotIdx >= 0) { // Get real parent obj = getPropertyValue(obj, property.substring(0, dotIdx)); // Get the property of the parent property = property.substring(dotIdx + 1); } // Find method Object[] params = {pValue}; Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property), new Class[] {paramType}, params); // Invoke it method.invoke(obj, params); } private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) throws NoSuchMethodException { // NOTE: This method assumes pParams.length == 1 && pValues.length == 1 Method method = null; Class paramType = pParams[0]; try { method = pObject.getClass().getMethod(pName, pParams); } catch (NoSuchMethodException e) { // No direct match // 1: If primitive wrapper, try unwrap conversion first /*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type params[0] = ReflectUtil.wrapType(paramType); } else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) { pParams[0] = ReflectUtil.unwrapType(paramType); } try { // If this does not throw an exception, it works method = pObject.getClass().getMethod(pName, pParams); } catch (Throwable t) { // Ignore } // 2: Try any super-types of paramType, to see if we have a match if (method == null) { while ((paramType = paramType.getSuperclass()) != null) { pParams[0] = paramType; try { // If this does not throw an exception, it works method = pObject.getClass().getMethod(pName, pParams); } catch (Throwable t) { // Ignore/Continue continue; } break; } } // 3: Try to find a different method with the same name, that has // a parameter type we can convert to... // NOTE: There's no ordering here.. // TODO: Should we try to do that? What would the ordering be? if (method == null) { Method[] methods = pObject.getClass().getMethods(); for (Method candidate : methods) { if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName) && candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) { // NOTE: Assumes paramTypes.length == 1 Class type = candidate.getParameterTypes()[0]; try { pValues[0] = convertValueToType(pValues[0], type); } catch (Throwable t) { continue; } // We were able to convert the parameter, let's try method = candidate; break; } } } // Give up... if (method == null) { throw e; } } return method; } private static Object convertValueToType(Object pValue, Class pType) throws ConversionException { if (pType.isPrimitive()) { if (pType == Boolean.TYPE && pValue instanceof Boolean) { return pValue; } else if (pType == Byte.TYPE && pValue instanceof Byte) { return pValue; } else if (pType == Character.TYPE && pValue instanceof Character) { return pValue; } else if (pType == Double.TYPE && pValue instanceof Double) { return pValue; } else if (pType == Float.TYPE && pValue instanceof Float) { return pValue; } else if (pType == Integer.TYPE && pValue instanceof Integer) { return pValue; } else if (pType == Long.TYPE && pValue instanceof Long) { return pValue; } else if (pType == Short.TYPE && pValue instanceof Short) { return pValue; } } // TODO: Convert value to single-value array if needed // TODO: Convert CSV String to string array (or potentially any type of array) // TODO: Convert other types if (pValue instanceof String) { Converter converter = Converter.getInstance(); return converter.toObject((String) pValue, pType); } else if (pType == String.class) { Converter converter = Converter.getInstance(); return converter.toString(pValue); } else { throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName()); } } /** * Creates an object from the given class' single argument constructor. * * @param pClass The class to create instance from * @param pParam The parameters to the constructor * * @return The object created from the constructor. * If the constructor could not be invoked for any reason, null is * returned. * * @throws InvocationTargetException if the constructor failed */ // TODO: Move to ReflectUtil public static T createInstance(Class pClass, Object pParam) throws InvocationTargetException { return createInstance(pClass, new Object[] {pParam}); } /** * Creates an object from the given class' constructor that matches * the given paramaters. * * @param pClass The class to create instance from * @param pParams The parameters to the constructor * * @return The object created from the constructor. * If the constructor could not be invoked for any reason, null is * returned. * * @throws InvocationTargetException if the constructor failed */ // TODO: Move to ReflectUtil public static T createInstance(Class pClass, Object... pParams) throws InvocationTargetException { T value; try { // Create param and argument arrays Class[] paramTypes = null; if (pParams != null && pParams.length > 0) { paramTypes = new Class[pParams.length]; for (int i = 0; i < pParams.length; i++) { paramTypes[i] = pParams[i].getClass(); } } // Get constructor Constructor constructor = pClass.getConstructor(paramTypes); // Invoke and create instance value = constructor.newInstance(pParams); } /* All this to let InvocationTargetException pass on */ catch (NoSuchMethodException nsme) { return null; } catch (IllegalAccessException iae) { return null; } catch (IllegalArgumentException iarge) { return null; } catch (InstantiationException ie) { return null; } catch (ExceptionInInitializerError err) { return null; } return value; } /** * Gets an object from any given static method, with the given parameter. * * @param pClass The class to invoke method on * @param pMethod The name of the method to invoke * @param pParam The parameter to the method * * @return The object returned by the static method. * If the return type of the method is a primitive type, it is wrapped in * the corresponding wrapper object (int is wrapped in an Integer). * If the return type of the method is void, null is returned. * If the method could not be invoked for any reason, null is returned. * * @throws InvocationTargetException if the invocation failed */ // TODO: Move to ReflectUtil // TODO: Rename to invokeStatic? public static Object invokeStaticMethod(Class pClass, String pMethod, Object pParam) throws InvocationTargetException { return invokeStaticMethod(pClass, pMethod, new Object[] {pParam}); } /** * Gets an object from any given static method, with the given parameter. * * @param pClass The class to invoke method on * @param pMethod The name of the method to invoke * @param pParams The parameters to the method * * @return The object returned by the static method. * If the return type of the method is a primitive type, it is wrapped in * the corresponding wrapper object (int is wrapped in an Integer). * If the return type of the method is void, null is returned. * If the method could not be invoked for any reason, null is returned. * * @throws InvocationTargetException if the invocation failed */ // TODO: Move to ReflectUtil // TODO: Rename to invokeStatic? public static Object invokeStaticMethod(Class pClass, String pMethod, Object... pParams) throws InvocationTargetException { Object value = null; try { // Create param and argument arrays Class[] paramTypes = new Class[pParams.length]; for (int i = 0; i < pParams.length; i++) { paramTypes[i] = pParams[i].getClass(); } // Get method // *** If more than one such method is found in the class, and one // of these methods has a RETURN TYPE that is more specific than // any of the others, that method is reflected; otherwise one of // the methods is chosen ARBITRARILY. // java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[]) Method method = pClass.getMethod(pMethod, paramTypes); // Invoke public static method if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { value = method.invoke(null, pParams); } } /* All this to let InvocationTargetException pass on */ catch (NoSuchMethodException nsme) { return null; } catch (IllegalAccessException iae) { return null; } catch (IllegalArgumentException iarge) { return null; } return value; } /** * Configures the bean according to the given mapping. * For each {@code Map.Entry} in {@code Map.values()}, * a method named * {@code set + capitalize(entry.getKey())} is called on the bean, * with {@code entry.getValue()} as its argument. *

* Properties that has no matching set-method in the bean, are simply * discarded. *

* * @param pBean The bean to configure * @param pMapping The mapping for the bean * * @throws NullPointerException if any of the parameters are null. * @throws InvocationTargetException if an error occurs when invoking the * setter-method. */ // TODO: Add a version that takes a ConfigurationErrorListener callback interface // TODO: ...or a boolean pFailOnError parameter // TODO: ...or return Exceptions as an array?! // TODO: ...or something whatsoever that makes clients able to determine something's not right public static void configure(final Object pBean, final Map pMapping) throws InvocationTargetException { configure(pBean, pMapping, false); } /** * Configures the bean according to the given mapping. * For each {@code Map.Entry} in {@code Map.values()}, * a method named * {@code set + capitalize(entry.getKey())} is called on the bean, * with {@code entry.getValue()} as its argument. *

* Optionally, lisp-style names are allowed, and automatically converted * to Java-style camel-case names. *

*

* Properties that has no matching set-method in the bean, are simply * discarded. *

* * @see StringUtil#lispToCamel(String) * * @param pBean The bean to configure * @param pMapping The mapping for the bean * @param pLispToCamel Allow lisp-style names, and automatically convert * them to Java-style camel-case. * * @throws NullPointerException if any of the parameters are null. * @throws InvocationTargetException if an error occurs when invoking the * setter-method. */ public static void configure(final Object pBean, final Map pMapping, final boolean pLispToCamel) throws InvocationTargetException { // Loop over properties in mapping for (final Map.Entry entry : pMapping.entrySet()) { try { // Configure each property in turn final String property = StringUtil.valueOf(entry.getKey()); try { setPropertyValue(pBean, property, entry.getValue()); } catch (NoSuchMethodException ignore) { // If invocation failed, convert lisp-style and try again if (pLispToCamel && property.indexOf('-') > 0) { setPropertyValue(pBean, StringUtil.lispToCamel(property, false), entry.getValue()); } } } catch (NoSuchMethodException nsme) { // This property was not configured } catch (IllegalAccessException iae) { // This property was not configured } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy