lite.beans.StandardBeanInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of litebeans Show documentation
Show all versions of litebeans Show documentation
Libraries for Elasticsearch
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 lite.beans;
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;
private BeanDescriptor beanDescriptor = 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();
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 (beanDescriptor == null) {
if (explicitBeanInfo != null) {
beanDescriptor = explicitBeanInfo.getBeanDescriptor();
}
if (beanDescriptor == null) {
beanDescriptor = new BeanDescriptor(beanClass);
}
}
return beanDescriptor;
}
@Override
public int getDefaultEventIndex() {
return this.defaultEventIndex;
}
@Override
public int getDefaultPropertyIndex() {
return this.defaultPropertyIndex;
}
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 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 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 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 internalAsMap(
PropertyDescriptor[] propertyDescs) {
HashMap map = new HashMap();
for (int i = 0; i < propertyDescs.length; i++) {
map.put(propertyDescs[i].getName(), propertyDescs[i]);
}
return map;
}
private static HashMap internalAsMap(
MethodDescriptor[] theDescs) {
HashMap map = new HashMap();
for (int i = 0; i < theDescs.length; i++) {
String qualifiedName = getQualifiedName(theDescs[i].getMethod());
map.put(qualifiedName, theDescs[i]);
}
return map;
}
private static HashMap internalAsMap(
EventSetDescriptor[] theDescs) {
HashMap map = new HashMap();
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 methodList = new ArrayList(
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 methodList = new ArrayList();
// 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 propertyTable = new 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 tempMethods = new ArrayList();
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 propertyList = new ArrayList();
for (Map.Entry 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 propertyTable) {
String methodName = theMethod.getName();
int prefixLength = 0;
String propertyName;
Class propertyType;
Class[] paramTypes;
HashMap table;
ArrayList 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 = Introspector.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) table.get(STR_GETTERS);
if (getters == null) {
getters = new ArrayList();
table.put(STR_GETTERS, getters);
}
// add current method as a valid getter
getters.add(theMethod);
}
@SuppressWarnings("unchecked")
private static void introspectSet(Method theMethod,
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 = Introspector.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 setters = (ArrayList) table.get(STR_SETTERS);
if (setters == null) {
setters = new ArrayList();
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 propertyTable)
throws IntrospectionException {
if (propertyTable == null) {
return;
}
for (Map.Entry entry : propertyTable.entrySet()) {
HashMap table = entry.getValue();
ArrayList getters = (ArrayList) table
.get(STR_GETTERS);
ArrayList setters = (ArrayList) 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();
}
if (setters == null) {
setters = new ArrayList();
}
// retrieve getters
Class>[] paramTypes = null;
String methodName = null;
for (Method getter : getters) {
paramTypes = getter.getParameterTypes();
methodName = getter.getName();
// checks if it's a normal getter
if (paramTypes == null || paramTypes.length == 0) {
// normal getter found
if (normalGetter == null
|| methodName.startsWith(PREFIX_IS)) {
normalGetter = getter;
}
}
// checks if it's an indexed getter
if (paramTypes != null && paramTypes.length == 1
&& paramTypes[0] == int.class) {
// indexed getter found
if (indexedGetter == null
|| methodName.startsWith(PREFIX_GET)
|| (methodName.startsWith(PREFIX_IS) && !indexedGetter
.getName().startsWith(PREFIX_GET))) {
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)) {
if (indexedSetter != null) {
table.put(STR_INDEXED, STR_VALID);
table.put(STR_INDEXED + PREFIX_SET, indexedSetter);
table.put(STR_INDEXED + STR_PROPERTY_TYPE,
indexedPropType);
}
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 eventTable = new 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 eventList = new ArrayList();
for (Map.Entry 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(
Introspector.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 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 list = new ArrayList();
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 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 {
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;
}
}
}
}
}
}