/*
 *  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.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 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;
    }

    // java.awt.Image not supported by j2objc.
//    @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
            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<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;
                    }
                }
            }
        }
    }
}
