/* | |
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//beanutils/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java,v 1.3 2001/10/14 00:54:24 craigmcc Exp $ | |
* $Revision: 1.3 $ | |
* $Date: 2001/10/14 00:54:24 $ | |
* | |
* ==================================================================== | |
* | |
* The Apache Software License, Version 1.1 | |
* | |
* Copyright (c) 1999-2001 The Apache Software Foundation. All rights | |
* reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* | |
* 2. 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. | |
* | |
* 3. The end-user documentation included with the redistribution, if | |
* any, must include the following acknowlegement: | |
* "This product includes software developed by the | |
* Apache Software Foundation (http://www.apache.org/)." | |
* Alternately, this acknowlegement may appear in the software itself, | |
* if and wherever such third-party acknowlegements normally appear. | |
* | |
* 4. The names "The Jakarta Project", "Commons", and "Apache Software | |
* Foundation" must not be used to endorse or promote products derived | |
* from this software without prior written permission. For written | |
* permission, please contact apache@apache.org. | |
* | |
* 5. Products derived from this software may not be called "Apache" | |
* nor may "Apache" appear in their names without prior written | |
* permission of the Apache Group. | |
* | |
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR | |
* ITS 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. | |
* ==================================================================== | |
* | |
* This software consists of voluntary contributions made by many | |
* individuals on behalf of the Apache Software Foundation. For more | |
* information on the Apache Software Foundation, please see | |
* <http://www.apache.org/>. | |
* | |
*/ | |
package org.apache.commons.beanutils; | |
import java.beans.IntrospectionException; | |
import java.beans.FeatureDescriptor; | |
import java.beans.PropertyDescriptor; | |
import java.lang.reflect.*; | |
import java.security.*; | |
/** | |
* 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 | |
* <p><code>get<strong>Property</strong>(String key)<code> and | |
* <p><code>set<Property>(String key, Object value)<code>, | |
* <p>where <code><strong>Property</strong></code> must be replaced | |
* by the name of the property. | |
* @see java.beans.PropertyDescriptor | |
* | |
* @author Rey François | |
* @author Gregor Raýman | |
* @version $Revision: 1.3 $ $Date: 2001/10/14 00:54:24 $ | |
*/ | |
public class MappedPropertyDescriptor extends PropertyDescriptor { | |
// ----------------------------------------------------- Instance Variables | |
/** | |
* The underlying data type of the property we are describing. | |
*/ | |
private Class mappedPropertyType; | |
/** | |
* The reader method for this property (if any). | |
*/ | |
private Method mappedReadMethod; | |
/** | |
* The writer method for this property (if any). | |
*/ | |
private Method mappedWriteMethod; | |
/** | |
* The parameter types array for the reader method signature. | |
*/ | |
private static final Class[] stringClassArray = new Class[] {String.class}; | |
// ----------------------------------------------------------- Constructors | |
/** | |
* 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. | |
* | |
* @exception IntrospectionException if an exception occurs during | |
* introspection. | |
*/ | |
public MappedPropertyDescriptor(String propertyName, Class beanClass) | |
throws IntrospectionException { | |
super(propertyName, null, null); | |
if (propertyName == null || propertyName.length() == 0) { | |
throw new IntrospectionException("bad property name"); | |
} | |
setName(propertyName); | |
String base = capitalize(propertyName); | |
// Look for mapped get and set methods | |
try { | |
mappedReadMethod = findMethod(beanClass, "get" + base, 1, | |
stringClassArray); | |
Class params[] = | |
{ String.class, mappedReadMethod.getReturnType() }; | |
mappedWriteMethod = findMethod(beanClass, "set" + base, 2, | |
params); | |
} catch (IntrospectionException e) { | |
; | |
} | |
if ((mappedReadMethod == null) && (mappedWriteMethod == null)) | |
throw new IntrospectionException("Property '" + propertyName + | |
"' not found on " + | |
beanClass.getName()); | |
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. | |
* | |
* @exception IntrospectionException if an exception occurs during | |
* introspection. | |
*/ | |
public MappedPropertyDescriptor(String propertyName, Class beanClass, | |
String mappedGetterName, String mappedSetterName) | |
throws IntrospectionException { | |
super(propertyName, null, null); | |
if (propertyName == null || propertyName.length() == 0) { | |
throw new IntrospectionException("bad property name"); | |
} | |
setName(propertyName); | |
// search the mapped get and set methods | |
mappedReadMethod = | |
findMethod(beanClass, mappedGetterName, 1, stringClassArray); | |
if (mappedReadMethod != null) { | |
Class params[] = { String.class, | |
mappedReadMethod.getReturnType() }; | |
mappedWriteMethod = | |
findMethod(beanClass, mappedSetterName, 2, params); | |
} else { | |
mappedWriteMethod = | |
findMethod(beanClass, mappedSetterName, 2); | |
} | |
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. | |
* | |
* @exception IntrospectionException if an exception occurs during | |
* introspection. | |
*/ | |
public MappedPropertyDescriptor(String propertyName, | |
Method mappedGetter, Method mappedSetter) | |
throws IntrospectionException { | |
super(propertyName, mappedGetter, mappedSetter); | |
if (propertyName == null || propertyName.length() == 0) { | |
throw new IntrospectionException("bad property name"); | |
} | |
setName(propertyName); | |
mappedReadMethod = mappedGetter; | |
mappedWriteMethod = mappedSetter; | |
findMappedPropertyType(); | |
} | |
// -------------------------------------------------------- Public Methods | |
/** | |
* 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. | |
* <p> | |
* This is the type that will be returned by the mappedReadMethod. | |
*/ | |
public Class getMappedPropertyType() { | |
return mappedPropertyType; | |
} | |
/** | |
* 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 mappedReadMethod; | |
} | |
/** | |
* Sets the method that should be used to read one of the property value. | |
* | |
* @param getter The new getter method. | |
*/ | |
public void setMappedReadMethod(Method mappedGetter) | |
throws IntrospectionException { | |
mappedReadMethod = 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 mappedWriteMethod; | |
} | |
/** | |
* Sets the method that should be used to write the property value. | |
* | |
* @param setter The new setter method. | |
*/ | |
public void setMappedWriteMethod(Method mappedSetter) | |
throws IntrospectionException { | |
mappedWriteMethod = mappedSetter; | |
findMappedPropertyType(); | |
} | |
// ------------------------------------------------------- Private Methods | |
/** | |
* Introspect our bean class to identify the corresponding getter | |
* and setter methods. | |
*/ | |
private void findMappedPropertyType() throws IntrospectionException { | |
try { | |
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) { | |
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]; | |
} | |
} catch (IntrospectionException ex) { | |
throw ex; | |
} | |
} | |
/** | |
* Return a capitalized version of the specified property name. | |
* | |
* @param s The property name | |
*/ | |
static String capitalize(String s) { | |
if (s.length() == 0) { | |
return s; | |
} | |
char chars[] = s.toCharArray(); | |
chars[0] = Character.toUpperCase(chars[0]); | |
return new String(chars); | |
} | |
//====================================================================== | |
// Package private support methods (copied from java.beans.Introspector). | |
//====================================================================== | |
// Cache of Class.getDeclaredMethods: | |
private static java.util.Hashtable | |
declaredMethodCache = new java.util.Hashtable(); | |
/* | |
* Internal method to return *public* methods within a class. | |
*/ | |
private static synchronized Method[] getPublicDeclaredMethods(Class clz) { | |
// Looking up Class.getDeclaredMethods is relatively expensive, | |
// so we cache the results. | |
final Class fclz = clz; | |
Method[] result = (Method[])declaredMethodCache.get(fclz); | |
if (result != null) { | |
return result; | |
} | |
// We have to raise privilege for getDeclaredMethods | |
result = (Method[]) | |
AccessController.doPrivileged(new PrivilegedAction() { | |
public Object run() { | |
return fclz.getDeclaredMethods(); | |
} | |
}); | |
// Null out any non-public methods. | |
for (int i = 0; i < result.length; i++) { | |
Method method = result[i]; | |
int mods = method.getModifiers(); | |
if (!Modifier.isPublic(mods)) { | |
result[i] = null; | |
} | |
} | |
// Add it to the cache. | |
declaredMethodCache.put(clz, result); | |
return result; | |
} | |
/** | |
* Internal support for finding a target methodName on a given class. | |
*/ | |
private static Method internalFindMethod(Class start, String methodName, | |
int argCount) { | |
// 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 cl = start; cl != null; cl = cl.getSuperclass()) { | |
Method methods[] = getPublicDeclaredMethods(cl); | |
for (int i = 0; i < methods.length; i++) { | |
Method method = methods[i]; | |
if (method == null) { | |
continue; | |
} | |
// skip static methods. | |
int mods = method.getModifiers(); | |
if (Modifier.isStatic(mods)) { | |
continue; | |
} | |
if (method.getName().equals(methodName) && | |
method.getParameterTypes().length == argCount) { | |
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. | |
Class ifcs[] = start.getInterfaces(); | |
for (int i = 0 ; i < ifcs.length; i++) { | |
Method m = internalFindMethod(ifcs[i], methodName, argCount); | |
if (m != null) { | |
return m; | |
} | |
} | |
return null; | |
} | |
/** | |
* Internal support for finding a target methodName with a given | |
* parameter list on a given class. | |
*/ | |
private static Method internalFindMethod(Class start, String methodName, | |
int argCount, Class args[]) { | |
// For overriden methods we need to find the most derived version. | |
// So we start with the given class and walk up the superclass chain. | |
for (Class cl = start; cl != null; cl = cl.getSuperclass()) { | |
Method methods[] = getPublicDeclaredMethods(cl); | |
for (int i = 0; i < methods.length; i++) { | |
Method method = methods[i]; | |
if (method == null) { | |
continue; | |
} | |
// skip static methods. | |
int mods = method.getModifiers(); | |
if (Modifier.isStatic(mods)) { | |
continue; | |
} | |
// make sure method signature matches. | |
Class params[] = method.getParameterTypes(); | |
if (method.getName().equals(methodName) && | |
params.length == argCount) { | |
boolean different = false; | |
if (argCount > 0) { | |
for (int j = 0; j < argCount; j++) { | |
if (params[j] != args[j]) { | |
different = true; | |
continue; | |
} | |
} | |
if (different) { | |
continue; | |
} | |
} | |
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. | |
Class ifcs[] = start.getInterfaces(); | |
for (int i = 0 ; i < ifcs.length; i++) { | |
Method m = internalFindMethod(ifcs[i], methodName, argCount); | |
if (m != null) { | |
return m; | |
} | |
} | |
return null; | |
} | |
/** | |
* Find a target methodName on a given class. | |
*/ | |
static Method findMethod(Class cls, String methodName, int argCount) | |
throws IntrospectionException { | |
if (methodName == null) { | |
return null; | |
} | |
Method m = internalFindMethod(cls, methodName, argCount); | |
if (m != null ) { | |
return m; | |
} | |
// We failed to find a suitable method | |
throw new IntrospectionException("No method \"" + methodName + | |
"\" with " + argCount + " arg(s)"); | |
} | |
/** | |
* Find a target methodName with specific parameter list on a given class. | |
*/ | |
static Method findMethod(Class cls, String methodName, int argCount, | |
Class args[]) throws IntrospectionException { | |
if (methodName == null) { | |
return null; | |
} | |
Method m = internalFindMethod(cls, methodName, argCount, args); | |
if (m != null ) { | |
return m; | |
} | |
// We failed to find a suitable method | |
throw new IntrospectionException("No method \"" + methodName + | |
"\" with " + argCount + " arg(s) of matching types."); | |
} | |
/** | |
* Return true if class a is either equivalent to class b, or | |
* if class a is a subclass of class b, i.e. if a either "extends" | |
* or "implements" b. | |
* Note tht either or both "Class" objects may represent interfaces. | |
*/ | |
static boolean isSubclass(Class a, Class b) { | |
// We rely on the fact that for any given java class or | |
// primtitive type there is a unqiue Class object, so | |
// we can use object equivalence in the comparisons. | |
if (a == b) { | |
return true; | |
} | |
if (a == null || b == null) { | |
return false; | |
} | |
for (Class x = a; x != null; x = x.getSuperclass()) { | |
if (x == b) { | |
return true; | |
} | |
if (b.isInterface()) { | |
Class interfaces[] = x.getInterfaces(); | |
for (int i = 0; i < interfaces.length; i++) { | |
if (isSubclass(interfaces[i], b)) { | |
return true; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* Return true iff the given method throws the given exception. | |
*/ | |
private boolean throwsException(Method method, Class exception) { | |
Class exs[] = method.getExceptionTypes(); | |
for (int i = 0; i < exs.length; i++) { | |
if (exs[i] == exception) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} |