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

org.apache.commons.beanutils2.MappedPropertyDescriptor Maven / Gradle / Ivy

Go to download

Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.beanutils2;


import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;


/**
 * A MappedPropertyDescriptor describes one mapped property.
 * Mapped properties are multivalued properties like indexed properties
 * but that are accessed with a String key instead of an index.
 * Such property values are typically stored in a Map collection.
 * For this class to work properly, a mapped value must have
 * getter and setter methods of the form
 * 

{@code getProperty(String key)} and *

{@code setProperty(String key, Object value)}, *

where {@code Property} must be replaced * by the name of the property. * @see java.beans.PropertyDescriptor * */ public class MappedPropertyDescriptor extends PropertyDescriptor { /** * The underlying data type of the property we are describing. */ private Reference> mappedPropertyTypeRef; /** * The reader method for this property (if any). */ private MappedMethodReference mappedReadMethodRef; /** * The writer method for this property (if any). */ private MappedMethodReference mappedWriteMethodRef; /** * The parameter types array for the reader method signature. */ private static final Class[] STRING_CLASS_PARAMETER = new Class[]{String.class}; /** * Constructs a MappedPropertyDescriptor for a property that follows * the standard Java convention by having getFoo and setFoo * accessor methods, with the addition of a String parameter (the key). * Thus if the argument name is "fred", it will * assume that the writer method is "setFred" and the reader method * is "getFred". Note that the property name should start with a lower * case character, which will be capitalized in the method names. * * @param propertyName The programmatic name of the property. * @param beanClass The Class object for the target bean. For * example sun.beans.OurButton.class. * * @throws IntrospectionException if an exception occurs during * introspection. */ public MappedPropertyDescriptor(final String propertyName, final Class beanClass) throws IntrospectionException { super(propertyName, null, null); if (propertyName == null || propertyName.isEmpty()) { throw new IntrospectionException("bad property name: " + propertyName + " on class: " + beanClass.getClass().getName()); } setName(propertyName); final String base = capitalizePropertyName(propertyName); // Look for mapped read method and matching write method Method mappedReadMethod = null; Method mappedWriteMethod = null; try { try { mappedReadMethod = getMethod(beanClass, "get" + base, STRING_CLASS_PARAMETER); } catch (final IntrospectionException e) { mappedReadMethod = getMethod(beanClass, "is" + base, STRING_CLASS_PARAMETER); } final Class[] params = { String.class, mappedReadMethod.getReturnType() }; mappedWriteMethod = getMethod(beanClass, "set" + base, params); } catch (final IntrospectionException e) { /* Swallow IntrospectionException * TODO: Why? */ } // If there's no read method, then look for just a write method if (mappedReadMethod == null) { mappedWriteMethod = getMethod(beanClass, "set" + base, 2); } if (mappedReadMethod == null && mappedWriteMethod == null) { throw new IntrospectionException("Property '" + propertyName + "' not found on " + beanClass.getName()); } mappedReadMethodRef = new MappedMethodReference(mappedReadMethod); mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod); findMappedPropertyType(); } /** * This constructor takes the name of a mapped property, and method * names for reading and writing the property. * * @param propertyName The programmatic name of the property. * @param beanClass The Class object for the target bean. For * example sun.beans.OurButton.class. * @param mappedGetterName The name of the method used for * reading one of the property values. May be null if the * property is write-only. * @param mappedSetterName The name of the method used for writing * one of the property values. May be null if the property is * read-only. * * @throws IntrospectionException if an exception occurs during * introspection. */ public MappedPropertyDescriptor(final String propertyName, final Class beanClass, final String mappedGetterName, final String mappedSetterName) throws IntrospectionException { super(propertyName, null, null); if (propertyName == null || propertyName.isEmpty()) { throw new IntrospectionException("bad property name: " + propertyName); } setName(propertyName); // search the mapped get and set methods Method mappedReadMethod; Method mappedWriteMethod = null; mappedReadMethod = getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER); if (mappedReadMethod != null) { final Class[] params = { String.class, mappedReadMethod.getReturnType() }; mappedWriteMethod = getMethod(beanClass, mappedSetterName, params); } else { mappedWriteMethod = getMethod(beanClass, mappedSetterName, 2); } mappedReadMethodRef = new MappedMethodReference(mappedReadMethod); mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod); findMappedPropertyType(); } /** * This constructor takes the name of a mapped property, and Method * objects for reading and writing the property. * * @param propertyName The programmatic name of the property. * @param mappedGetter The method used for reading one of * the property values. May be be null if the property * is write-only. * @param mappedSetter The method used for writing one the * property values. May be null if the property is read-only. * * @throws IntrospectionException if an exception occurs during * introspection. */ public MappedPropertyDescriptor(final String propertyName, final Method mappedGetter, final Method mappedSetter) throws IntrospectionException { super(propertyName, mappedGetter, mappedSetter); if (propertyName == null || propertyName.isEmpty()) { throw new IntrospectionException("bad property name: " + propertyName); } setName(propertyName); mappedReadMethodRef = new MappedMethodReference(mappedGetter); mappedWriteMethodRef = new MappedMethodReference(mappedSetter); findMappedPropertyType(); } /** * Gets the Class object for the property values. * * @return The Java type info for the property values. Note that * the "Class" object may describe a built-in Java type such as "int". * The result may be "null" if this is a mapped property that * does not support non-keyed access. *

* This is the type that will be returned by the mappedReadMethod. */ public Class getMappedPropertyType() { return mappedPropertyTypeRef.get(); } /** * Gets the method that should be used to read one of the property value. * * @return The method that should be used to read the property value. * May return null if the property can't be read. */ public Method getMappedReadMethod() { return mappedReadMethodRef.get(); } /** * Sets the method that should be used to read one of the property value. * * @param mappedGetter The mapped getter method. * @throws IntrospectionException If an error occurs finding the * mapped property */ public void setMappedReadMethod(final Method mappedGetter) throws IntrospectionException { mappedReadMethodRef = new MappedMethodReference(mappedGetter); findMappedPropertyType(); } /** * Gets the method that should be used to write one of the property value. * * @return The method that should be used to write one of the property value. * May return null if the property can't be written. */ public Method getMappedWriteMethod() { return mappedWriteMethodRef.get(); } /** * Sets the method that should be used to write the property value. * * @param mappedSetter The mapped setter method. * @throws IntrospectionException If an error occurs finding the * mapped property */ public void setMappedWriteMethod(final Method mappedSetter) throws IntrospectionException { mappedWriteMethodRef = new MappedMethodReference(mappedSetter); findMappedPropertyType(); } /** * Introspect our bean class to identify the corresponding getter * and setter methods. */ private void findMappedPropertyType() throws IntrospectionException { final Method mappedReadMethod = getMappedReadMethod(); final Method mappedWriteMethod = getMappedWriteMethod(); Class mappedPropertyType = null; if (mappedReadMethod != null) { if (mappedReadMethod.getParameterTypes().length != 1) { throw new IntrospectionException ("bad mapped read method arg count"); } mappedPropertyType = mappedReadMethod.getReturnType(); if (mappedPropertyType == Void.TYPE) { throw new IntrospectionException ("mapped read method " + mappedReadMethod.getName() + " returns void"); } } if (mappedWriteMethod != null) { final Class[] params = mappedWriteMethod.getParameterTypes(); if (params.length != 2) { throw new IntrospectionException ("bad mapped write method arg count"); } if (mappedPropertyType != null && mappedPropertyType != params[1]) { throw new IntrospectionException ("type mismatch between mapped read and write methods"); } mappedPropertyType = params[1]; } mappedPropertyTypeRef = new SoftReference<>(mappedPropertyType); } /** * Gets a capitalized version of the specified property name. * * @param s The property name */ private static String capitalizePropertyName(final String s) { if (s.isEmpty()) { return s; } final char[] chars = s.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } /** * Find a method on a class with a specified number of parameters. */ private static Method internalGetMethod(final Class initial, final String methodName, final int parameterCount) { // For overridden methods we need to find the most derived version. // So we start with the given class and walk up the superclass chain. for (Class clazz = initial; clazz != null; clazz = clazz.getSuperclass()) { final Method[] methods = clazz.getDeclaredMethods(); for (final Method method : methods) { if (method == null) { continue; } // skip static methods. final int mods = method.getModifiers(); if (!Modifier.isPublic(mods) || Modifier.isStatic(mods)) { continue; } if (method.getName().equals(methodName) && method.getParameterTypes().length == parameterCount) { return method; } } } // Now check any inherited interfaces. This is necessary both when // the argument class is itself an interface, and when the argument // class is an abstract class. final Class[] interfaces = initial.getInterfaces(); for (final Class interface1 : interfaces) { final Method method = internalGetMethod(interface1, methodName, parameterCount); if (method != null) { return method; } } return null; } /** * Find a method on a class with a specified number of parameters. */ private static Method getMethod(final Class clazz, final String methodName, final int parameterCount) throws IntrospectionException { if (methodName == null) { return null; } final Method method = internalGetMethod(clazz, methodName, parameterCount); if (method != null) { return method; } // No Method found throw new IntrospectionException("No method \"" + methodName + "\" with " + parameterCount + " parameter(s)"); } /** * Find a method on a class with a specified parameter list. */ private static Method getMethod(final Class clazz, final String methodName, final Class[] parameterTypes) throws IntrospectionException { if (methodName == null) { return null; } final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes); if (method != null) { return method; } final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length; // No Method found throw new IntrospectionException("No method \"" + methodName + "\" with " + parameterCount + " parameter(s) of matching types."); } /** * Holds a {@link Method} in a {@link SoftReference} so that it * it doesn't prevent any ClassLoader being garbage collected, but * tries to re-create the method if the method reference has been * released. * * See https://issues.apache.org/jira/browse/BEANUTILS-291 */ private static class MappedMethodReference { private String className; private String methodName; private Reference methodRef; private Reference> classRef; private Reference> writeParamTypeRef0; private Reference> writeParamTypeRef1; private String[] writeParamClassNames; MappedMethodReference(final Method m) { if (m != null) { className = m.getDeclaringClass().getName(); methodName = m.getName(); methodRef = new SoftReference<>(m); classRef = new WeakReference<>(m.getDeclaringClass()); final Class[] types = m.getParameterTypes(); if (types.length == 2) { writeParamTypeRef0 = new WeakReference<>(types[0]); writeParamTypeRef1 = new WeakReference<>(types[1]); writeParamClassNames = new String[2]; writeParamClassNames[0] = types[0].getName(); writeParamClassNames[1] = types[1].getName(); } } } private Method get() { if (methodRef == null) { return null; } Method m = methodRef.get(); if (m == null) { Class clazz = classRef.get(); if (clazz == null) { clazz = reLoadClass(); if (clazz != null) { classRef = new WeakReference<>(clazz); } } if (clazz == null) { throw new RuntimeException("Method " + methodName + " for " + className + " could not be reconstructed - class reference has gone"); } Class[] paramTypes = null; if (writeParamClassNames != null) { paramTypes = new Class[2]; paramTypes[0] = writeParamTypeRef0.get(); if (paramTypes[0] == null) { paramTypes[0] = reLoadClass(writeParamClassNames[0]); if (paramTypes[0] != null) { writeParamTypeRef0 = new WeakReference<>(paramTypes[0]); } } paramTypes[1] = writeParamTypeRef1.get(); if (paramTypes[1] == null) { paramTypes[1] = reLoadClass(writeParamClassNames[1]); if (paramTypes[1] != null) { writeParamTypeRef1 = new WeakReference<>(paramTypes[1]); } } } else { paramTypes = STRING_CLASS_PARAMETER; } try { m = clazz.getMethod(methodName, paramTypes); // Un-comment following line for testing // System.out.println("Recreated Method " + methodName + " for " + className); } catch (final NoSuchMethodException e) { throw new RuntimeException("Method " + methodName + " for " + className + " could not be reconstructed - method not found"); } methodRef = new SoftReference<>(m); } return m; } /** * Try to re-load the class */ private Class reLoadClass() { return reLoadClass(className); } /** * Try to re-load the class */ private Class reLoadClass(final String name) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Try the context class loader if (classLoader != null) { try { return classLoader.loadClass(name); } catch (final ClassNotFoundException e) { // ignore } } // Try this class's class loader classLoader = MappedPropertyDescriptor.class.getClassLoader(); try { return classLoader.loadClass(name); } catch (final ClassNotFoundException e) { return null; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy