blob: 785afdc3ad3429acce181cde5ab35a7f02f1f635 [file] [log] [blame]
/*
* 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 java.beans;
import static java.beans.Introspector.decapitalize;
import java.awt.Image;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;
import java.util.TooManyListenersException;
class StandardBeanInfo extends SimpleBeanInfo {
// Prefixes for methods that set or get a Property
private static final String PREFIX_IS = "is"; //$NON-NLS-1$
private static final String PREFIX_GET = "get"; //$NON-NLS-1$
private static final String PREFIX_SET = "set"; //$NON-NLS-1$
// Prefix and suffix for Event related methods
private static final String PREFIX_ADD = "add"; //$NON-NLS-1$
private static final String PREFIX_REMOVE = "remove"; //$NON-NLS-1$
private static final String SUFFIX_LISTEN = "Listener"; //$NON-NLS-1$
private static final String STR_NORMAL = "normal"; //$NON-NLS-1$
private static final String STR_INDEXED = "indexed"; //$NON-NLS-1$
private static final String STR_VALID = "valid"; //$NON-NLS-1$
private static final String STR_INVALID = "invalid"; //$NON-NLS-1$
private static final String STR_PROPERTY_TYPE = "PropertyType"; //$NON-NLS-1$
private static final String STR_IS_CONSTRAINED = "isConstrained"; //$NON-NLS-1$
private static final String STR_SETTERS = "setters"; //$NON-NLS-1$
private static final String STR_GETTERS = "getters"; //$NON-NLS-1$
private boolean explicitMethods = false;
private boolean explicitProperties = false;
private boolean explicitEvents = false;
private BeanInfo explicitBeanInfo = null;
private EventSetDescriptor[] events = null;
private MethodDescriptor[] methods = null;
private PropertyDescriptor[] properties = null;
BeanInfo[] additionalBeanInfo = null;
private Class<?> beanClass;
private int defaultEventIndex = -1;
private int defaultPropertyIndex = -1;
private static PropertyComparator comparator = new PropertyComparator();
private Object[] icon = new Object[4];
private boolean canAddPropertyChangeListener;
private boolean canRemovePropertyChangeListener;
StandardBeanInfo(Class<?> beanClass, BeanInfo explicitBeanInfo, Class<?> stopClass)
throws IntrospectionException {
this.beanClass = beanClass;
/*--------------------------------------------------------------------------------------
* There are 3 aspects of BeanInfo that must be supplied:
* a) PropertyDescriptors
* b) MethodDescriptors
* c) EventSetDescriptors
* Each of these may be optionally provided in the explicitBeanInfo object relating to
* this bean. Where the explicitBeanInfo provides one of these aspects, it is used
* without question and no introspection of the beanClass is performed for that aspect.
* There are also 3 optional items of BeanInfo that may be provided by the
* explicitBeanInfo object:
* 1) BeanDescriptor
* 2) DefaultEventIndex
* 3) DefaultPropertyIndex
* These aspects of the beanClass cannot be derived through introspection of the class.
* If they are not provided by the explicitBeanInfo, then they must be left null in the
* returned BeanInfo, otherwise they will be copied from the explicitBeanInfo
--------------------------------------------------------------------------------------*/
if (explicitBeanInfo != null) {
this.explicitBeanInfo = explicitBeanInfo;
events = explicitBeanInfo.getEventSetDescriptors();
methods = explicitBeanInfo.getMethodDescriptors();
properties = explicitBeanInfo.getPropertyDescriptors();
defaultEventIndex = explicitBeanInfo.getDefaultEventIndex();
if (defaultEventIndex < 0 || defaultEventIndex >= events.length) {
defaultEventIndex = -1;
}
defaultPropertyIndex = explicitBeanInfo.getDefaultPropertyIndex();
if (defaultPropertyIndex < 0
|| defaultPropertyIndex >= properties.length) {
defaultPropertyIndex = -1;
}
additionalBeanInfo = explicitBeanInfo.getAdditionalBeanInfo();
for (int i = 0; i < 4; i++) {
icon[i] = explicitBeanInfo.getIcon(i + 1);
}
if (events != null)
explicitEvents = true;
if (methods != null)
explicitMethods = true;
if (properties != null)
explicitProperties = true;
}
if (methods == null) {
methods = introspectMethods();
}
if (properties == null) {
properties = introspectProperties(stopClass);
}
if (events == null) {
events = introspectEvents();
}
}
@Override
public BeanInfo[] getAdditionalBeanInfo() {
return null;
}
@Override
public EventSetDescriptor[] getEventSetDescriptors() {
return events;
}
@Override
public MethodDescriptor[] getMethodDescriptors() {
return methods;
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return properties;
}
@Override
public BeanDescriptor getBeanDescriptor() {
if (explicitBeanInfo != null) {
BeanDescriptor beanDesc = explicitBeanInfo.getBeanDescriptor();
if (beanDesc != null) {
return beanDesc;
}
}
return new BeanDescriptor(beanClass);
}
@Override
public int getDefaultEventIndex() {
return this.defaultEventIndex;
}
@Override
public int getDefaultPropertyIndex() {
return this.defaultPropertyIndex;
}
@Override
public Image getIcon(int iconKind) {
return (Image)icon[iconKind - 1];
}
void mergeBeanInfo(BeanInfo beanInfo, boolean force)
throws IntrospectionException {
if (force || !explicitProperties) {
PropertyDescriptor[] superDescs = beanInfo.getPropertyDescriptors();
if (superDescs != null) {
if (getPropertyDescriptors() != null) {
properties = mergeProps(superDescs, beanInfo
.getDefaultPropertyIndex());
} else {
properties = superDescs;
defaultPropertyIndex = beanInfo.getDefaultPropertyIndex();
}
}
}
if (force || !explicitMethods) {
MethodDescriptor[] superMethods = beanInfo.getMethodDescriptors();
if (superMethods != null) {
if (methods != null) {
methods = mergeMethods(superMethods);
} else {
methods = superMethods;
}
}
}
if (force || !explicitEvents) {
EventSetDescriptor[] superEvents = beanInfo
.getEventSetDescriptors();
if (superEvents != null) {
if (events != null) {
events = mergeEvents(superEvents, beanInfo
.getDefaultEventIndex());
} else {
events = superEvents;
defaultEventIndex = beanInfo.getDefaultEventIndex();
}
}
}
}
/*
* merge the PropertyDescriptor with superclass
*/
private PropertyDescriptor[] mergeProps(PropertyDescriptor[] superDescs,
int superDefaultIndex) throws IntrospectionException {
// FIXME:change to OO way as EventSetD and MethodD
HashMap<String, PropertyDescriptor> subMap = internalAsMap(properties);
String defaultPropertyName = null;
if (defaultPropertyIndex >= 0
&& defaultPropertyIndex < properties.length) {
defaultPropertyName = properties[defaultPropertyIndex].getName();
} else if (superDefaultIndex >= 0
&& superDefaultIndex < superDescs.length) {
defaultPropertyName = superDescs[superDefaultIndex].getName();
}
for (int i = 0; i < superDescs.length; i++) {
PropertyDescriptor superDesc = superDescs[i];
String propertyName = superDesc.getName();
if (!subMap.containsKey(propertyName)) {
subMap.put(propertyName, superDesc);
continue;
}
Object value = subMap.get(propertyName);
// if sub and super are both PropertyDescriptor
Method subGet = ((PropertyDescriptor) value).getReadMethod();
Method subSet = ((PropertyDescriptor) value).getWriteMethod();
Method superGet = superDesc.getReadMethod();
Method superSet = superDesc.getWriteMethod();
Class<?> superType = superDesc.getPropertyType();
Class<?> superIndexedType = null;
Class<?> subType = ((PropertyDescriptor) value).getPropertyType();
Class<?> subIndexedType = null;
if (value instanceof IndexedPropertyDescriptor) {
subIndexedType = ((IndexedPropertyDescriptor) value)
.getIndexedPropertyType();
}
if (superDesc instanceof IndexedPropertyDescriptor) {
superIndexedType = ((IndexedPropertyDescriptor) superDesc)
.getIndexedPropertyType();
}
// if superDesc is PropertyDescriptor
if (superIndexedType == null) {
PropertyDescriptor subDesc = (PropertyDescriptor) value;
// Sub is PropertyDescriptor
if (subIndexedType == null) {
// Same property type
if (subType != null && superType != null
&& subType.getName() != null
&& subType.getName().equals(superType.getName())) {
if (superGet != null
&& (subGet == null || superGet.equals(subGet))) {
subDesc.setReadMethod(superGet);
}
if (superSet != null
&& (subSet == null || superSet.equals(subSet))) {
subDesc.setWriteMethod(superSet);
}
if (subType == boolean.class && subGet != null
&& superGet != null) {
if (superGet.getName().startsWith(PREFIX_IS)) {
subDesc.setReadMethod(superGet);
}
}
} else { // Different type
if ((subGet == null || subSet == null)
&& (superGet != null)) {
subDesc = new PropertyDescriptor(propertyName,
superGet, superSet);
if (subGet != null) {
String subGetName = subGet.getName();
Method method = null;
MethodDescriptor[] introspectMethods = introspectMethods();
for (MethodDescriptor methodDesc : introspectMethods) {
method = methodDesc.getMethod();
if (method != subGet
&& subGetName.equals(method
.getName())
&& method.getParameterTypes().length == 0
&& method.getReturnType() == superType) {
subDesc.setReadMethod(method);
break;
}
}
}
}
}
} else { // Sub is IndexedPropertyDescriptor and super is PropertyDescriptor
if (superType != null
&& (superType.isArray())
&& (superType.getComponentType().getName()
.equals(subIndexedType.getName()))) {
if ((subGet == null) && (superGet != null)) {
subDesc.setReadMethod(superGet);
}
if ((subSet == null) && (superSet != null)) {
subDesc.setWriteMethod(superSet);
}
} // different type do nothing
// sub is indexed pd and super is normal pd
if (subIndexedType == boolean.class
&& superType == boolean.class) {
Method subIndexedSet = ((IndexedPropertyDescriptor) subDesc)
.getIndexedWriteMethod();
if (subGet == null && subSet == null
&& subIndexedSet != null && superGet != null) {
try {
subSet = beanClass.getDeclaredMethod(
subIndexedSet.getName(), boolean.class);
} catch (Exception e) {
// ignored
}
if (subSet != null) {
// Cast sub into PropertyDescriptor
subDesc = new PropertyDescriptor(propertyName,
superGet, subSet);
}
}
}
}
subMap.put(propertyName, subDesc);
} else { // Super is IndexedPropertyDescriptor
if (subIndexedType == null) { // Sub is PropertyDescriptor
if (subType != null
&& subType.isArray()
&& (subType.getComponentType().getName()
.equals(superIndexedType.getName()))) {
// Same type
if (subGet != null) {
superDesc.setReadMethod(subGet);
}
if (subSet != null) {
superDesc.setWriteMethod(subSet);
}
subMap.put(propertyName, superDesc);
} else {
// subDesc is PropertyDescriptor
// superDesc is IndexedPropertyDescriptor
// fill null subGet or subSet method with superClass's
if (subGet == null || subSet == null) {
Class<?> beanSuperClass = beanClass.getSuperclass();
String methodSuffix = capitalize(propertyName);
Method method = null;
if (subGet == null) {
// subGet is null
if (subType == boolean.class) {
try {
method = beanSuperClass
.getDeclaredMethod(PREFIX_IS
+ methodSuffix);
} catch (Exception e) {
// ignored
}
} else {
try {
method = beanSuperClass
.getDeclaredMethod(PREFIX_GET
+ methodSuffix);
} catch (Exception e) {
// ignored
}
}
if (method != null
&& !Modifier.isStatic(method
.getModifiers())
&& method.getReturnType() == subType) {
((PropertyDescriptor) value)
.setReadMethod(method);
}
} else {
// subSet is null
try {
method = beanSuperClass.getDeclaredMethod(
PREFIX_SET + methodSuffix, subType);
} catch (Exception e) {
// ignored
}
if (method != null
&& !Modifier.isStatic(method
.getModifiers())
&& method.getReturnType() == void.class) {
((PropertyDescriptor) value)
.setWriteMethod(method);
}
}
}
subMap.put(propertyName, (PropertyDescriptor) value);
}
} else if (subIndexedType.getName().equals(
superIndexedType.getName())) {
// Sub is IndexedPropertyDescriptor and Same type
IndexedPropertyDescriptor subDesc = (IndexedPropertyDescriptor) value;
if ((subGet == null) && (superGet != null)) {
subDesc.setReadMethod(superGet);
}
if ((subSet == null) && (superSet != null)) {
subDesc.setWriteMethod(superSet);
}
IndexedPropertyDescriptor superIndexedDesc = (IndexedPropertyDescriptor) superDesc;
if ((subDesc.getIndexedReadMethod() == null)
&& (superIndexedDesc.getIndexedReadMethod() != null)) {
subDesc.setIndexedReadMethod(superIndexedDesc
.getIndexedReadMethod());
}
if ((subDesc.getIndexedWriteMethod() == null)
&& (superIndexedDesc.getIndexedWriteMethod() != null)) {
subDesc.setIndexedWriteMethod(superIndexedDesc
.getIndexedWriteMethod());
}
subMap.put(propertyName, subDesc);
} // Different indexed type, do nothing
}
mergeAttributes((PropertyDescriptor) value, superDesc);
}
PropertyDescriptor[] theDescs = new PropertyDescriptor[subMap.size()];
subMap.values().toArray(theDescs);
if (defaultPropertyName != null && !explicitProperties) {
for (int i = 0; i < theDescs.length; i++) {
if (defaultPropertyName.equals(theDescs[i].getName())) {
defaultPropertyIndex = i;
break;
}
}
}
return theDescs;
}
private String capitalize(String name) {
if (name == null) {
return null;
}
// The rule for decapitalize is that:
// If the first letter of the string is Upper Case, make it lower case
// UNLESS the second letter of the string is also Upper Case, in which case no
// changes are made.
if (name.length() == 0 || (name.length() > 1 && Character.isUpperCase(name.charAt(1)))) {
return name;
}
char[] chars = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return new String(chars);
}
private static void mergeAttributes(PropertyDescriptor subDesc,
PropertyDescriptor superDesc) {
// FIXME: this is just temp workaround, need more elegant solution to
// handle this
subDesc.hidden |= superDesc.hidden;
subDesc.expert |= superDesc.expert;
subDesc.preferred |= superDesc.preferred;
subDesc.bound |= superDesc.bound;
subDesc.constrained |= superDesc.constrained;
subDesc.name = superDesc.name;
if (subDesc.shortDescription == null
&& superDesc.shortDescription != null) {
subDesc.shortDescription = superDesc.shortDescription;
}
if (subDesc.displayName == null && superDesc.displayName != null) {
subDesc.displayName = superDesc.displayName;
}
}
/*
* merge the MethodDescriptor
*/
private MethodDescriptor[] mergeMethods(MethodDescriptor[] superDescs) {
HashMap<String, MethodDescriptor> subMap = internalAsMap(methods);
for (MethodDescriptor superMethod : superDescs) {
String methodName = getQualifiedName(superMethod.getMethod());
MethodDescriptor method = subMap.get(methodName);
if (method == null) {
subMap.put(methodName, superMethod);
} else {
method.merge(superMethod);
}
}
MethodDescriptor[] theMethods = new MethodDescriptor[subMap.size()];
subMap.values().toArray(theMethods);
return theMethods;
}
private EventSetDescriptor[] mergeEvents(EventSetDescriptor[] otherEvents,
int otherDefaultIndex) {
HashMap<String, EventSetDescriptor> subMap = internalAsMap(events);
String defaultEventName = null;
if (defaultEventIndex >= 0 && defaultEventIndex < events.length) {
defaultEventName = events[defaultEventIndex].getName();
} else if (otherDefaultIndex >= 0
&& otherDefaultIndex < otherEvents.length) {
defaultEventName = otherEvents[otherDefaultIndex].getName();
}
for (EventSetDescriptor event : otherEvents) {
String eventName = event.getName();
EventSetDescriptor subEvent = subMap.get(eventName);
if (subEvent == null) {
subMap.put(eventName, event);
} else {
subEvent.merge(event);
}
}
EventSetDescriptor[] theEvents = new EventSetDescriptor[subMap.size()];
subMap.values().toArray(theEvents);
if (defaultEventName != null && !explicitEvents) {
for (int i = 0; i < theEvents.length; i++) {
if (defaultEventName.equals(theEvents[i].getName())) {
defaultEventIndex = i;
break;
}
}
}
return theEvents;
}
private static HashMap<String, PropertyDescriptor> internalAsMap(
PropertyDescriptor[] propertyDescs) {
HashMap<String, PropertyDescriptor> map = new HashMap<String, PropertyDescriptor>();
for (int i = 0; i < propertyDescs.length; i++) {
map.put(propertyDescs[i].getName(), propertyDescs[i]);
}
return map;
}
private static HashMap<String, MethodDescriptor> internalAsMap(
MethodDescriptor[] theDescs) {
HashMap<String, MethodDescriptor> map = new HashMap<String, MethodDescriptor>();
for (int i = 0; i < theDescs.length; i++) {
String qualifiedName = getQualifiedName(theDescs[i].getMethod());
map.put(qualifiedName, theDescs[i]);
}
return map;
}
private static HashMap<String, EventSetDescriptor> internalAsMap(
EventSetDescriptor[] theDescs) {
HashMap<String, EventSetDescriptor> map = new HashMap<String, EventSetDescriptor>();
for (int i = 0; i < theDescs.length; i++) {
map.put(theDescs[i].getName(), theDescs[i]);
}
return map;
}
private static String getQualifiedName(Method method) {
String qualifiedName = method.getName();
Class[] paramTypes = method.getParameterTypes();
if (paramTypes != null) {
for (int i = 0; i < paramTypes.length; i++) {
qualifiedName += "_" + paramTypes[i].getName(); //$NON-NLS-1$
}
}
return qualifiedName;
}
/**
* Introspects the supplied class and returns a list of the public methods
* of the class
*
* @return An array of MethodDescriptors with the public methods. null if
* there are no public methods
*/
private MethodDescriptor[] introspectMethods() {
return introspectMethods(false, beanClass);
}
private MethodDescriptor[] introspectMethods(boolean includeSuper) {
return introspectMethods(includeSuper, beanClass);
}
private MethodDescriptor[] introspectMethods(boolean includeSuper,
Class<?> introspectorClass) {
// Get the list of methods belonging to this class
Method[] basicMethods = includeSuper ? introspectorClass.getMethods()
: introspectorClass.getDeclaredMethods();
if (basicMethods == null || basicMethods.length == 0)
return null;
ArrayList<MethodDescriptor> methodList = new ArrayList<MethodDescriptor>(
basicMethods.length);
// Loop over the methods found, looking for public non-static methods
for (int i = 0; i < basicMethods.length; i++) {
int modifiers = basicMethods[i].getModifiers();
if (Modifier.isPublic(modifiers)) {
// Allocate a MethodDescriptor for this method
MethodDescriptor theDescriptor = new MethodDescriptor(
basicMethods[i]);
methodList.add(theDescriptor);
}
}
// Get the list of public methods into the returned array
int methodCount = methodList.size();
MethodDescriptor[] theMethods = null;
if (methodCount > 0) {
theMethods = new MethodDescriptor[methodCount];
theMethods = methodList.toArray(theMethods);
}
return theMethods;
}
/**
* Introspects the supplied class and returns a list of the Properties of
* the class
*
* @param stopClass -
* the to introspecting at
* @return The list of Properties as an array of PropertyDescriptors
* @throws IntrospectionException
*/
@SuppressWarnings("unchecked")
private PropertyDescriptor[] introspectProperties(Class<?> stopClass)
throws IntrospectionException {
// Get descriptors for the public methods
MethodDescriptor[] methodDescriptors = introspectMethods();
if (methodDescriptors == null) {
return null;
}
ArrayList<MethodDescriptor> methodList = new ArrayList<MethodDescriptor>();
// Loop over the methods found, looking for public non-static methods
for (int index = 0; index < methodDescriptors.length; index++) {
int modifiers = methodDescriptors[index].getMethod().getModifiers();
if (!Modifier.isStatic(modifiers)) {
methodList.add(methodDescriptors[index]);
}
}
// Get the list of public non-static methods into an array
int methodCount = methodList.size();
MethodDescriptor[] theMethods = null;
if (methodCount > 0) {
theMethods = new MethodDescriptor[methodCount];
theMethods = methodList.toArray(theMethods);
}
if (theMethods == null) {
return null;
}
HashMap<String, HashMap> propertyTable = new HashMap<String, HashMap>(
theMethods.length);
// Search for methods that either get or set a Property
for (int i = 0; i < theMethods.length; i++) {
introspectGet(theMethods[i].getMethod(), propertyTable);
introspectSet(theMethods[i].getMethod(), propertyTable);
}
// fix possible getter & setter collisions
fixGetSet(propertyTable);
// If there are listener methods, should be bound.
MethodDescriptor[] allMethods = introspectMethods(true);
if (stopClass != null) {
MethodDescriptor[] excludeMethods = introspectMethods(true,
stopClass);
if (excludeMethods != null) {
ArrayList<MethodDescriptor> tempMethods = new ArrayList<MethodDescriptor>();
for (MethodDescriptor method : allMethods) {
if (!isInSuper(method, excludeMethods)) {
tempMethods.add(method);
}
}
allMethods = tempMethods
.toArray(new MethodDescriptor[0]);
}
}
for (int i = 0; i < allMethods.length; i++) {
introspectPropertyListener(allMethods[i].getMethod());
}
// Put the properties found into the PropertyDescriptor array
ArrayList<PropertyDescriptor> propertyList = new ArrayList<PropertyDescriptor>();
for (Map.Entry<String, HashMap> entry : propertyTable.entrySet()) {
String propertyName = entry.getKey();
HashMap table = entry.getValue();
if (table == null) {
continue;
}
String normalTag = (String) table.get(STR_NORMAL);
String indexedTag = (String) table.get(STR_INDEXED);
if ((normalTag == null) && (indexedTag == null)) {
continue;
}
Method get = (Method) table.get(STR_NORMAL + PREFIX_GET);
Method set = (Method) table.get(STR_NORMAL + PREFIX_SET);
Method indexedGet = (Method) table.get(STR_INDEXED + PREFIX_GET);
Method indexedSet = (Method) table.get(STR_INDEXED + PREFIX_SET);
PropertyDescriptor propertyDesc = null;
if (indexedTag == null) {
propertyDesc = new PropertyDescriptor(propertyName, get, set);
} else {
try {
propertyDesc = new IndexedPropertyDescriptor(propertyName,
get, set, indexedGet, indexedSet);
} catch (IntrospectionException e) {
// If the getter and the indexGetter is not compatible, try
// getter/setter is null;
propertyDesc = new IndexedPropertyDescriptor(propertyName,
null, null, indexedGet, indexedSet);
}
}
// RI set propretyDescriptor as bound. FIXME
// propertyDesc.setBound(true);
if (canAddPropertyChangeListener && canRemovePropertyChangeListener) {
propertyDesc.setBound(true);
} else {
propertyDesc.setBound(false);
}
if (table.get(STR_IS_CONSTRAINED) == Boolean.TRUE) { //$NON-NLS-1$
propertyDesc.setConstrained(true);
}
propertyList.add(propertyDesc);
}
PropertyDescriptor[] theProperties = new PropertyDescriptor[propertyList
.size()];
propertyList.toArray(theProperties);
return theProperties;
}
private boolean isInSuper(MethodDescriptor method,
MethodDescriptor[] excludeMethods) {
for (MethodDescriptor m : excludeMethods) {
if (method.getMethod().equals(m.getMethod())) {
return true;
}
}
return false;
}
@SuppressWarnings("nls")
private void introspectPropertyListener(Method theMethod) {
String methodName = theMethod.getName();
Class[] param = theMethod.getParameterTypes();
if (param.length != 1) {
return;
}
if (methodName.equals("addPropertyChangeListener")
&& param[0].equals(PropertyChangeListener.class))
canAddPropertyChangeListener = true;
if (methodName.equals("removePropertyChangeListener")
&& param[0].equals(PropertyChangeListener.class))
canRemovePropertyChangeListener = true;
}
@SuppressWarnings("unchecked")
private static void introspectGet(Method theMethod,
HashMap<String, HashMap> propertyTable) {
String methodName = theMethod.getName();
int prefixLength = 0;
String propertyName;
Class propertyType;
Class[] paramTypes;
HashMap table;
ArrayList<Method> getters;
if (methodName == null) {
return;
}
if (methodName.startsWith(PREFIX_GET)) {
prefixLength = PREFIX_GET.length();
}
if (methodName.startsWith(PREFIX_IS)) {
prefixLength = PREFIX_IS.length();
}
if (prefixLength == 0) {
return;
}
propertyName = decapitalize(methodName.substring(prefixLength));
// validate property name
if (!isValidProperty(propertyName)) {
return;
}
// validate return type
propertyType = theMethod.getReturnType();
if (propertyType == null || propertyType == void.class) {
return;
}
// isXXX return boolean
if (prefixLength == 2) {
if (!(propertyType == boolean.class)) {
return;
}
}
// validate parameter types
paramTypes = theMethod.getParameterTypes();
if (paramTypes.length > 1
|| (paramTypes.length == 1 && paramTypes[0] != int.class)) {
return;
}
table = propertyTable.get(propertyName);
if (table == null) {
table = new HashMap();
propertyTable.put(propertyName, table);
}
getters = (ArrayList<Method>) table.get(STR_GETTERS);
if (getters == null) {
getters = new ArrayList<Method>();
table.put(STR_GETTERS, getters);
}
// add current method as a valid getter
getters.add(theMethod);
}
@SuppressWarnings("unchecked")
private static void introspectSet(Method theMethod,
HashMap<String, HashMap> propertyTable) {
String methodName = theMethod.getName();
if (methodName == null) {
return;
}
String propertyName;
Class returnType;
Class[] paramTypes;
// setter method should never return type other than void
returnType = theMethod.getReturnType();
if (returnType != void.class) {
return;
}
if (methodName == null || !methodName.startsWith(PREFIX_SET)) {
return;
}
propertyName = decapitalize(methodName.substring(PREFIX_SET.length()));
// validate property name
if (!isValidProperty(propertyName)) {
return;
}
// It seems we do not need to validate return type
// validate param types
paramTypes = theMethod.getParameterTypes();
if (paramTypes.length == 0 || paramTypes.length > 2
|| (paramTypes.length == 2 && paramTypes[0] != int.class)) {
return;
}
HashMap table = propertyTable.get(propertyName);
if (table == null) {
table = new HashMap();
propertyTable.put(propertyName, table);
}
ArrayList<Method> setters = (ArrayList<Method>) table.get(STR_SETTERS);
if (setters == null) {
setters = new ArrayList<Method>();
table.put(STR_SETTERS, setters);
}
// handle constrained
Class[] exceptions = theMethod.getExceptionTypes();
for (Class e : exceptions) {
if (e.equals(PropertyVetoException.class)) {
table.put(STR_IS_CONSTRAINED, Boolean.TRUE); //$NON-NLS-1$
}
}
// add new setter
setters.add(theMethod);
}
/**
* Checks and fixs all cases when several incompatible checkers / getters
* were specified for single property.
*
* @param propertyTable
* @throws IntrospectionException
*/
private void fixGetSet(HashMap<String, HashMap> propertyTable)
throws IntrospectionException {
if (propertyTable == null) {
return;
}
for (Map.Entry<String, HashMap> entry : propertyTable.entrySet()) {
HashMap<String, Object> table = entry.getValue();
ArrayList<Method> getters = (ArrayList<Method>) table
.get(STR_GETTERS);
ArrayList<Method> setters = (ArrayList<Method>) table
.get(STR_SETTERS);
Method normalGetter = null;
Method indexedGetter = null;
Method normalSetter = null;
Method indexedSetter = null;
Class normalPropType = null;
Class indexedPropType = null;
if (getters == null) {
getters = new ArrayList<Method>();
}
if (setters == null) {
setters = new ArrayList<Method>();
}
// retrieve getters
for (Method getter : getters) {
// checks if it's a normal getter
if (getter.getParameterTypes() == null
|| getter.getParameterTypes().length == 0) {
// normal getter found
if (normalGetter == null
|| getter.getName().startsWith(PREFIX_IS)) {
normalGetter = getter;
}
}
// checks if it's an indexed getter
if (getter.getParameterTypes() != null
&& getter.getParameterTypes().length == 1
&& getter.getParameterTypes()[0] == int.class) {
// indexed getter found
if (indexedGetter == null
|| getter.getName().startsWith(PREFIX_IS)) {
indexedGetter = getter;
}
}
}
// retrieve normal setter
if (normalGetter != null) {
// Now we will try to look for normal setter of the same type.
Class propertyType = normalGetter.getReturnType();
for (Method setter : setters) {
if (setter.getParameterTypes().length == 1
&& propertyType
.equals(setter.getParameterTypes()[0])) {
normalSetter = setter;
break;
}
}
} else {
// Normal getter wasn't defined. Let's look for the last
// defined setter
for (Method setter : setters) {
if (setter.getParameterTypes().length == 1) {
normalSetter = setter;
}
}
}
// retrieve indexed setter
if (indexedGetter != null) {
// Now we will try to look for indexed setter of the same type.
Class propertyType = indexedGetter.getReturnType();
for (Method setter : setters) {
if (setter.getParameterTypes().length == 2
&& setter.getParameterTypes()[0] == int.class
&& propertyType
.equals(setter.getParameterTypes()[1])) {
indexedSetter = setter;
break;
}
}
} else {
// Indexed getter wasn't defined. Let's look for the last
// defined indexed setter
for (Method setter : setters) {
if (setter.getParameterTypes().length == 2
&& setter.getParameterTypes()[0] == int.class) {
indexedSetter = setter;
}
}
}
// determine property type
if (normalGetter != null) {
normalPropType = normalGetter.getReturnType();
} else if (normalSetter != null) {
normalPropType = normalSetter.getParameterTypes()[0];
}
// determine indexed getter/setter type
if (indexedGetter != null) {
indexedPropType = indexedGetter.getReturnType();
} else if (indexedSetter != null) {
indexedPropType = indexedSetter.getParameterTypes()[1];
}
// convert array-typed normal getters to indexed getters
if (normalGetter != null && normalGetter.getReturnType().isArray()) {
}
// RULES
// These rules were created after performing extensive black-box
// testing of RI
// RULE1
// Both normal getter and setter of the same type were defined;
// no indexed getter/setter *PAIR* of the other type defined
if (normalGetter != null && normalSetter != null
&& (indexedGetter == null || indexedSetter == null)) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE, normalPropType);
continue;
}
// RULE2
// normal getter and/or setter was defined; no indexed
// getters & setters defined
if ((normalGetter != null || normalSetter != null)
&& indexedGetter == null && indexedSetter == null) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE, normalPropType);
continue;
}
// RULE3
// mix of normal / indexed getters and setters are defined. Types
// are compatible
if ((normalGetter != null || normalSetter != null)
&& (indexedGetter != null || indexedSetter != null)) {
// (1)!A!B!C!D
if (normalGetter != null && normalSetter != null
&& indexedGetter != null && indexedSetter != null) {
if (indexedGetter.getName().startsWith(PREFIX_GET)) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE,
normalPropType);
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE,
indexedPropType);
} else {
if (normalPropType != boolean.class
&& normalGetter.getName().startsWith(PREFIX_IS)) {
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE,
indexedPropType);
} else {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE,
normalPropType);
}
}
continue;
}
// (2)!AB!C!D
if (normalGetter != null && normalSetter == null
&& indexedGetter != null && indexedSetter != null) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE, normalPropType);
table.put(STR_INDEXED, STR_VALID);
if (indexedGetter.getName().startsWith(PREFIX_GET)) {
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
}
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE, indexedPropType);
continue;
}
// (3)A!B!C!D
if (normalGetter == null && normalSetter != null
&& indexedGetter != null && indexedSetter != null) {
table.put(STR_INDEXED, STR_VALID);
if (indexedGetter.getName().startsWith(PREFIX_GET)) {
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
}
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE, indexedPropType);
continue;
}
// (4)!AB!CD
if (normalGetter != null && normalSetter == null
&& indexedGetter != null && indexedSetter == null) {
if (indexedGetter.getName().startsWith(PREFIX_GET)) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE,
normalPropType);
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE,
indexedPropType);
} else {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE,
normalPropType);
}
continue;
}
// (5)A!B!CD
if (normalGetter == null && normalSetter != null
&& indexedGetter != null && indexedSetter == null) {
if (indexedGetter.getName().startsWith(PREFIX_GET)) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE,
normalPropType);
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE,
indexedPropType);
} else {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE,
normalPropType);
}
continue;
}
// (6)!ABC!D
if (normalGetter != null && normalSetter == null
&& indexedGetter == null && indexedSetter != null) {
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE, indexedPropType);
continue;
}
// (7)A!BC!D
if (normalGetter == null && normalSetter != null
&& indexedGetter == null && indexedSetter != null) {
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE, indexedPropType);
continue;
}
}
// RULE4
// no normal normal getter / setter.
// Only indexed getter and/or setter is given
// no normal setters / getters defined
if (normalSetter == null && normalGetter == null
&& (indexedGetter != null || indexedSetter != null)) {
if (indexedGetter != null
&& indexedGetter.getName().startsWith(PREFIX_IS)) {
continue;
}
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE, indexedPropType);
continue;
}
// RULE5
// Both indexed getter and setter methods are defined
// no normal getter/setter *PAIR* of the other type defined
if ((normalSetter != null || normalGetter != null)
&& indexedGetter != null && indexedSetter != null) {
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_GET, indexedGetter);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE, indexedPropType);
continue;
}
// default rule - invalid property
table.put(STR_NORMAL, STR_INVALID);
table.put(STR_INDEXED, STR_INVALID);
}
}
/**
* Introspects the supplied Bean class and returns a list of the Events of
* the class
*
* @return the events
* @throws IntrospectionException
*/
@SuppressWarnings("unchecked")
private EventSetDescriptor[] introspectEvents() throws IntrospectionException {
// Get descriptors for the public methods
// FIXME: performance
MethodDescriptor[] theMethods = introspectMethods();
if (theMethods == null)
return null;
HashMap<String, HashMap> eventTable = new HashMap<String, HashMap>(
theMethods.length);
// Search for methods that add an Event Listener
for (int i = 0; i < theMethods.length; i++) {
introspectListenerMethods(PREFIX_ADD, theMethods[i].getMethod(),
eventTable);
introspectListenerMethods(PREFIX_REMOVE, theMethods[i].getMethod(),
eventTable);
introspectGetListenerMethods(theMethods[i].getMethod(), eventTable);
}
ArrayList<EventSetDescriptor> eventList = new ArrayList<EventSetDescriptor>();
for (Map.Entry<String, HashMap> entry : eventTable.entrySet()) {
HashMap table = entry.getValue();
Method add = (Method) table.get(PREFIX_ADD);
Method remove = (Method) table.get(PREFIX_REMOVE);
if ((add == null) || (remove == null)) {
continue;
}
Method get = (Method) table.get(PREFIX_GET);
Class<?> listenerType = (Class) table.get("listenerType"); //$NON-NLS-1$
Method[] listenerMethods = (Method[]) table.get("listenerMethods"); //$NON-NLS-1$
EventSetDescriptor eventSetDescriptor = new EventSetDescriptor(
decapitalize(entry.getKey()), listenerType, listenerMethods, add,
remove, get);
eventSetDescriptor.setUnicast(table.get("isUnicast") != null); //$NON-NLS-1$
eventList.add(eventSetDescriptor);
}
EventSetDescriptor[] theEvents = new EventSetDescriptor[eventList
.size()];
eventList.toArray(theEvents);
return theEvents;
}
/*
* find the add, remove listener method
*/
@SuppressWarnings("unchecked")
private static void introspectListenerMethods(String type,
Method theMethod, HashMap<String, HashMap> methodsTable) {
String methodName = theMethod.getName();
if (methodName == null) {
return;
}
if (!((methodName.startsWith(type)) && (methodName
.endsWith(SUFFIX_LISTEN)))) {
return;
}
String listenerName = methodName.substring(type.length());
String eventName = listenerName.substring(0, listenerName
.lastIndexOf(SUFFIX_LISTEN));
if ((eventName == null) || (eventName.length() == 0)) {
return;
}
Class[] paramTypes = theMethod.getParameterTypes();
if ((paramTypes == null) || (paramTypes.length != 1)) {
return;
}
Class<?> listenerType = paramTypes[0];
if (!EventListener.class.isAssignableFrom(listenerType)) {
return;
}
if (!listenerType.getName().endsWith(listenerName)) {
return;
}
HashMap table = methodsTable.get(eventName);
if (table == null) {
table = new HashMap();
}
// put listener type
if (table.get("listenerType") == null) { //$NON-NLS-1$
table.put("listenerType", listenerType); //$NON-NLS-1$
table.put("listenerMethods", //$NON-NLS-1$
introspectListenerMethods(listenerType));
}
// put add / remove
table.put(type, theMethod);
// determine isUnicast()
if (type.equals(PREFIX_ADD)) {
Class[] exceptionTypes = theMethod.getExceptionTypes();
if (exceptionTypes != null) {
for (int i = 0; i < exceptionTypes.length; i++) {
if (exceptionTypes[i].getName().equals(
TooManyListenersException.class.getName())) {
table.put("isUnicast", "true"); //$NON-NLS-1$//$NON-NLS-2$
break;
}
}
}
}
methodsTable.put(eventName, table);
}
private static Method[] introspectListenerMethods(Class<?> listenerType) {
Method[] methods = listenerType.getDeclaredMethods();
ArrayList<Method> list = new ArrayList<Method>();
for (int i = 0; i < methods.length; i++) {
Class[] paramTypes = methods[i].getParameterTypes();
if (paramTypes.length != 1) {
continue;
}
if (EventObject.class.isAssignableFrom(paramTypes[0])) {
list.add(methods[i]);
}
}
Method[] matchedMethods = new Method[list.size()];
list.toArray(matchedMethods);
return matchedMethods;
}
@SuppressWarnings("unchecked")
private static void introspectGetListenerMethods(Method theMethod,
HashMap<String, HashMap> methodsTable) {
String type = PREFIX_GET;
String methodName = theMethod.getName();
if (methodName == null) {
return;
}
if (!((methodName.startsWith(type)) && (methodName
.endsWith(SUFFIX_LISTEN + "s")))) { //$NON-NLS-1$
return;
}
String listenerName = methodName.substring(type.length(), methodName
.length() - 1);
String eventName = listenerName.substring(0, listenerName
.lastIndexOf(SUFFIX_LISTEN));
if ((eventName == null) || (eventName.length() == 0)) {
return;
}
Class[] paramTypes = theMethod.getParameterTypes();
if ((paramTypes == null) || (paramTypes.length != 0)) {
return;
}
Class returnType = theMethod.getReturnType();
if ((returnType.getComponentType() == null)
|| (!returnType.getComponentType().getName().endsWith(
listenerName))) {
return;
}
HashMap table = methodsTable.get(eventName);
if (table == null) {
table = new HashMap();
}
// put add / remove
table.put(type, theMethod);
methodsTable.put(eventName, table);
}
private static boolean isValidProperty(String propertyName) {
return (propertyName != null) && (propertyName.length() != 0);
}
private static class PropertyComparator implements
Comparator<PropertyDescriptor> {
public int compare(PropertyDescriptor object1,
PropertyDescriptor object2) {
return object1.getName().compareTo(object2.getName());
}
}
// TODO
void init() {
if (this.events == null) {
events = new EventSetDescriptor[0];
}
if (this.properties == null) {
this.properties = new PropertyDescriptor[0];
}
if (properties != null) {
String defaultPropertyName = (defaultPropertyIndex != -1 ? properties[defaultPropertyIndex]
.getName()
: null);
Arrays.sort(properties, comparator);
if (null != defaultPropertyName) {
for (int i = 0; i < properties.length; i++) {
if (defaultPropertyName.equals(properties[i].getName())) {
defaultPropertyIndex = i;
break;
}
}
}
}
}
}