/*
* NOTICE: THE FILE HAS BEEN MODIFIED TO SUIT THE NEEDS OF THE PROJECT.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.robobinding.internal.java_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.HashMap;
import java.util.Map;
@SuppressWarnings({ "rawtypes", "unchecked" })
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$
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 explicitProperties = false;
private BeanInfo explicitBeanInfo = null;
private MethodDescriptor[] methods = null;
private PropertyDescriptor[] properties = null;
private BeanDescriptor beanDescriptor = null;
BeanInfo[] additionalBeanInfo = null;
private Class<?> beanClass;
private int defaultPropertyIndex = -1;
private static PropertyComparator comparator = new PropertyComparator();
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;
properties = explicitBeanInfo.getPropertyDescriptors();
additionalBeanInfo = explicitBeanInfo.getAdditionalBeanInfo();
if (properties != null)
explicitProperties = true;
}
if (methods == null) {
methods = introspectMethods();
}
if (properties == null) {
properties = introspectProperties(stopClass);
}
}
@Override
public BeanInfo[] getAdditionalBeanInfo() {
return null;
}
@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;
}
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();
}
}
}
}
/*
* merge the PropertyDescriptor with superclass
*/
private PropertyDescriptor[] mergeProps(PropertyDescriptor[] superDescs, int superDefaultIndex) throws IntrospectionException {
// 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) {
// 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;
}
}
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;
}
/**
* 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
*/
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]);
}
}
// 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.
// 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;
}
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 = 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<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);
}
private static void introspectSet(Method theMethod, HashMap<String, HashMap> propertyTable) {
String methodName = theMethod.getName();
if (methodName == null) {
return;
}
String propertyName;
Class[] paramTypes;
// setter method should never return type other than void
/**
* Comment out by Cheng
*/
/*
Class returnType;
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<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
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);
}
}
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());
}
}
void init() {
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;
}
}
}
}
}
}