org.nextframework.controller.ExtendedBeanWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.nextframework.controller.ExtendedBeanWrapper.java

Source

/*
 * Next Framework http://www.nextframework.org
 * Copyright (C) 2009 the original author or authors.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * You may obtain a copy of the license at
 * 
 *     http://www.gnu.org/copyleft/lesser.html
 * 
 */
package org.nextframework.controller;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nextframework.bean.editors.CalendarEditor;
import org.nextframework.bean.editors.CepPropertyEditor;
import org.nextframework.bean.editors.CnpjPropertyEditor;
import org.nextframework.bean.editors.CpfPropertyEditor;
import org.nextframework.bean.editors.CustomDateEditor;
import org.nextframework.bean.editors.CustomNumberEditor;
import org.nextframework.bean.editors.CustomSqlDateEditor;
import org.nextframework.bean.editors.SimpleTimePropertyEditor;
import org.nextframework.bean.editors.InscricaoEstadualPropertyEditor;
import org.nextframework.bean.editors.MoneyPropertyEditor;
import org.nextframework.bean.editors.PhoneBrazilPropertyEditor;
import org.nextframework.bean.editors.PhonePropertyEditor;
import org.nextframework.bean.editors.TimePropertyEditor;
import org.nextframework.bean.editors.TimestampPropertyEditor;
import org.nextframework.exception.NextException;
import org.nextframework.exception.NotParameterizedTypeException;
import org.nextframework.types.Cep;
import org.nextframework.types.Cnpj;
import org.nextframework.types.Cpf;
import org.nextframework.types.SimpleTime;
import org.nextframework.types.InscricaoEstadual;
import org.nextframework.types.ListSet;
import org.nextframework.types.Money;
import org.nextframework.types.Phone;
import org.nextframework.types.PhoneBrazil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.MethodInvocationException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotReadablePropertyException;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.NullValueInNestedPathException;
import org.springframework.beans.PropertyAccessException;
import org.springframework.beans.PropertyBatchUpdateException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.propertyeditors.ByteArrayPropertyEditor;
import org.springframework.beans.propertyeditors.CharacterEditor;
import org.springframework.beans.propertyeditors.ClassEditor;
import org.springframework.beans.propertyeditors.CustomBooleanEditor;
import org.springframework.beans.propertyeditors.CustomCollectionEditor;
import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.beans.propertyeditors.InputStreamEditor;
import org.springframework.beans.propertyeditors.LocaleEditor;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
import org.springframework.beans.propertyeditors.URLEditor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceArrayPropertyEditor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * @author rogelgarcia
 * @since 31/01/2006
 * @version 1.1
 */
@SuppressWarnings("all")
public class ExtendedBeanWrapper implements BeanWrapper {
    /**
     * We'll create a lot of these objects, so we don't want a new logger every time
     */
    private static final Log logger = LogFactory.getLog(ExtendedBeanWrapper.class);

    //---------------------------------------------------------------------
    // Instance data
    //---------------------------------------------------------------------

    /**
     * The wrapped object
     */
    private Object object;

    private String nestedPath = "";

    private Object rootObject;

    private boolean extractOldValueForEditor = false;

    private final Map defaultEditors;

    private Map customEditors;

    /**
     * Cached introspections results for this object, to prevent encountering
     * the cost of JavaBeans introspection every time.
     */
    private CachedIntrospectionResults cachedIntrospectionResults;

    /**
     * Map with cached nested BeanWrappers: nested path -> BeanWrapper instance.
     */
    private Map nestedBeanWrappers;

    //---------------------------------------------------------------------
    // Constructors
    //---------------------------------------------------------------------

    /**
     * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
     * Registers default editors.
     * @see #setWrappedInstance
     */
    public ExtendedBeanWrapper() {
        this(true);
    }

    /**
     * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
     * @param registerDefaultEditors whether to register default editors
     * (can be suppressed if the BeanWrapper won't need any type conversion)
     * @see #setWrappedInstance
     */
    public ExtendedBeanWrapper(boolean registerDefaultEditors) {
        if (registerDefaultEditors) {
            this.defaultEditors = new HashMap(32);
            registerDefaultEditors();
        } else {
            this.defaultEditors = Collections.EMPTY_MAP;
        }
    }

    /**
     * Create new BeanWrapperImpl for the given object.
     * @param object object wrapped by this BeanWrapper
     */
    public ExtendedBeanWrapper(Object object) {
        this();
        setWrappedInstance(object);
    }

    /**
     * Create new BeanWrapperImpl, wrapping a new instance of the specified class.
     * @param clazz class to instantiate and wrap
     */
    public ExtendedBeanWrapper(Class clazz) {
        this();
        setWrappedInstance(BeanUtils.instantiateClass(clazz));
    }

    /**
     * Create new BeanWrapperImpl for the given object,
     * registering a nested path that the object is in.
     * @param object object wrapped by this BeanWrapper.
     * @param nestedPath the nested path of the object
     * @param rootObject the root object at the top of the path
     */
    public ExtendedBeanWrapper(Object object, String nestedPath, Object rootObject) {
        this();
        setWrappedInstance(object, nestedPath, rootObject);
    }

    /**
     * Create new BeanWrapperImpl for the given object,
     * registering a nested path that the object is in.
     * @param object object wrapped by this BeanWrapper.
     * @param nestedPath the nested path of the object
     * @param superBw the containing BeanWrapper (must not be <code>null</code>)
     */
    private ExtendedBeanWrapper(Object object, String nestedPath, ExtendedBeanWrapper superBw) {
        this.defaultEditors = superBw.defaultEditors;
        setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
    }

    static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
    {
        simpleDateFormat.setLenient(false);
    }

    /**
     * Register default editors in this class, for restricted environments.
     * We're not using the JRE's PropertyEditorManager to avoid potential
     * SecurityExceptions when running in a SecurityManager.
     * <p>Registers a <code>CustomNumberEditor</code> for all primitive number types,
     * their corresponding wrapper types, <code>BigInteger</code> and <code>BigDecimal</code>.
     */
    protected void registerDefaultEditors() {
        // Simple editors, without parameterization capabilities.
        // The JDK does not contain a default editor for any of these target types.
        this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
        this.defaultEditors.put(Class.class, new ClassEditor());
        this.defaultEditors.put(File.class, new FileEditor());
        this.defaultEditors.put(InputStream.class, new InputStreamEditor());
        this.defaultEditors.put(Locale.class, new LocaleEditor());
        this.defaultEditors.put(Properties.class, new PropertiesEditor());
        this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
        this.defaultEditors.put(String[].class, new StringArrayPropertyEditor());
        this.defaultEditors.put(URL.class, new URLEditor());

        // Default instances of collection editors.
        // Can be overridden by registering custom instances of those as custom editors.
        this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
        this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
        this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
        this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));

        // Default instances of character and boolean editors.
        // Can be overridden by registering custom instances of those as custom editors.
        PropertyEditor characterEditor = new CharacterEditor(true);
        PropertyEditor booleanEditor = new CustomBooleanEditor(true);

        // The JDK does not contain a default editor for char!
        this.defaultEditors.put(char.class, characterEditor);
        this.defaultEditors.put(Character.class, characterEditor);

        // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
        this.defaultEditors.put(boolean.class, booleanEditor);
        this.defaultEditors.put(Boolean.class, booleanEditor);

        // The JDK does not contain default editors for number wrapper types!
        // Override JDK primitive number editors with our own CustomNumberEditor.
        PropertyEditor byteEditor = new CustomNumberEditor(Byte.class, true);
        PropertyEditor shortEditor = new CustomNumberEditor(Short.class, true);
        PropertyEditor integerEditor = new CustomNumberEditor(Integer.class, true);
        PropertyEditor longEditor = new CustomNumberEditor(Long.class, true);
        PropertyEditor floatEditor = new CustomNumberEditor(Float.class, true);
        PropertyEditor doubleEditor = new CustomNumberEditor(Double.class, true);

        this.defaultEditors.put(byte.class, byteEditor);
        this.defaultEditors.put(Byte.class, byteEditor);

        this.defaultEditors.put(short.class, shortEditor);
        this.defaultEditors.put(Short.class, shortEditor);

        this.defaultEditors.put(int.class, integerEditor);
        this.defaultEditors.put(Integer.class, integerEditor);

        this.defaultEditors.put(long.class, longEditor);
        this.defaultEditors.put(Long.class, longEditor);

        this.defaultEditors.put(float.class, floatEditor);
        this.defaultEditors.put(Float.class, floatEditor);

        this.defaultEditors.put(double.class, doubleEditor);
        this.defaultEditors.put(Double.class, doubleEditor);

        this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, false));
        this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, false));

        //============================================================================================

        // Date, Time, Hora e Timestamp.

        boolean allowEmpty = true;

        registerCustomEditor(Calendar.class, new CalendarEditor(simpleDateFormat, allowEmpty));
        registerCustomEditor(Date.class, new CustomDateEditor(simpleDateFormat, allowEmpty));
        registerCustomEditor(java.sql.Date.class, new CustomSqlDateEditor(simpleDateFormat, allowEmpty));
        registerCustomEditor(Time.class, new TimePropertyEditor());
        registerCustomEditor(SimpleTime.class, new SimpleTimePropertyEditor());
        registerCustomEditor(Timestamp.class, new TimestampPropertyEditor());

        // Tipos personalizados.
        registerCustomEditor(Cpf.class, new CpfPropertyEditor());
        registerCustomEditor(Cnpj.class, new CnpjPropertyEditor());
        registerCustomEditor(InscricaoEstadual.class, new InscricaoEstadualPropertyEditor());
        registerCustomEditor(Money.class, new MoneyPropertyEditor());
        registerCustomEditor(Cep.class, new CepPropertyEditor());
        registerCustomEditor(PhoneBrazil.class, new PhoneBrazilPropertyEditor());
        registerCustomEditor(Phone.class, new PhonePropertyEditor());
    }

    //---------------------------------------------------------------------
    // Implementation of BeanWrapper
    //---------------------------------------------------------------------

    /**
     * Switch the target object, replacing the cached introspection results only
     * if the class of the new object is different to that of the replaced object.
     * @param object new target
     */
    public void setWrappedInstance(Object object) {
        setWrappedInstance(object, "", null);
    }

    /**
     * Switch the target object, replacing the cached introspection results only
     * if the class of the new object is different to that of the replaced object.
     * @param object new target
     * @param nestedPath the nested path of the object
     * @param rootObject the root object at the top of the path
     */
    public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
        if (object == null) {
            throw new IllegalArgumentException("Cannot set BeanWrapperImpl target to a null object");
        }
        this.object = object;
        this.nestedPath = (nestedPath != null ? nestedPath : "");
        this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
        this.nestedBeanWrappers = null;
        setIntrospectionClass(object.getClass());
    }

    public Object getWrappedInstance() {
        return this.object;
    }

    public Class getWrappedClass() {
        return this.object.getClass();
    }

    /**
     * Return the nested path of the object wrapped by this BeanWrapper.
     */
    public String getNestedPath() {
        return this.nestedPath;
    }

    /**
     * Return the root object at the top of the path of this BeanWrapper.
     * @see #getNestedPath
     */
    public Object getRootInstance() {
        return this.rootObject;
    }

    /**
     * Return the class of the root object at the top of the path of this BeanWrapper.
     * @see #getNestedPath
     */
    public Class getRootClass() {
        return (this.rootObject != null ? this.rootObject.getClass() : null);
    }

    /**
     * Set the class to introspect.
     * Needs to be called when the target object changes.
     * @param clazz the class to introspect
     */
    protected void setIntrospectionClass(Class clazz) {
        if (this.cachedIntrospectionResults == null
                || !this.cachedIntrospectionResults.getBeanClass().equals(clazz)) {
            this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(clazz);
        }
    }

    public void setExtractOldValueForEditor(boolean extractOldValueForEditor) {
        this.extractOldValueForEditor = extractOldValueForEditor;
    }

    @Override
    public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
        registerCustomEditor(requiredType, null, propertyEditor);
    }

    @Override
    public void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor) {
        if (requiredType == null && propertyPath == null) {
            throw new IllegalArgumentException("Either requiredType or propertyPath is required");
        }
        if (this.customEditors == null) {
            this.customEditors = new LinkedHashMap();
        }
        if (propertyPath != null) {
            this.customEditors.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType));
        } else {
            this.customEditors.put(requiredType, propertyEditor);
        }
    }

    @Override
    public PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath) {
        if (this.customEditors == null) {
            return null;
        }
        if (propertyPath != null) {
            // Check property-specific editor first.
            PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
            if (editor == null) {
                List strippedPaths = new LinkedList();
                addStrippedPropertyPaths(strippedPaths, "", propertyPath);
                for (Iterator it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
                    String strippedPath = (String) it.next();
                    editor = getCustomEditor(strippedPath, requiredType);
                }
            }
            if (editor != null) {
                return editor;
            } else if (requiredType == null) {
                requiredType = getPropertyType(propertyPath);
            }
        }
        // No property-specific editor -> check type-specific editor.
        return getCustomEditor(requiredType);
    }

    /**
     * Get custom editor that has been registered for the given property.
     * @return the custom editor, or <code>null</code> if none specific for this property
     */
    private PropertyEditor getCustomEditor(String propertyName, Class requiredType) {
        CustomEditorHolder holder = (CustomEditorHolder) this.customEditors.get(propertyName);
        return (holder != null ? holder.getPropertyEditor(requiredType) : null);
    }

    /**
     * Get custom editor for the given type. If no direct match found,
     * try custom editor for superclass (which will in any case be able
     * to render a value as String via <code>getAsText</code>).
     * @return the custom editor, or <code>null</code> if none found for this type
     * @see java.beans.PropertyEditor#getAsText
     */
    private PropertyEditor getCustomEditor(Class requiredType) {
        if (requiredType != null) {
            PropertyEditor editor = (PropertyEditor) this.customEditors.get(requiredType);
            if (editor == null) {
                for (Iterator it = this.customEditors.keySet().iterator(); it.hasNext();) {
                    Object key = it.next();
                    if (key instanceof Class && ((Class) key).isAssignableFrom(requiredType)) {
                        editor = (PropertyEditor) this.customEditors.get(key);
                    }
                }
            }
            return editor;
        }
        return null;
    }

    /**
     * Add property paths with all variations of stripped keys and/or indexes.
     * Invokes itself recursively with nested paths
     * @param strippedPaths the result list to add to
     * @param nestedPath the current nested path
     * @param propertyPath the property path to check for keys/indexes to strip
     */
    private void addStrippedPropertyPaths(List strippedPaths, String nestedPath, String propertyPath) {
        int startIndex = propertyPath.indexOf(PROPERTY_KEY_PREFIX_CHAR);
        if (startIndex != -1) {
            int endIndex = propertyPath.indexOf(PROPERTY_KEY_SUFFIX_CHAR);
            if (endIndex != -1) {
                String prefix = propertyPath.substring(0, startIndex);
                String key = propertyPath.substring(startIndex, endIndex + 1);
                String suffix = propertyPath.substring(endIndex + 1, propertyPath.length());
                // strip the first key
                strippedPaths.add(nestedPath + prefix + suffix);
                // search for further keys to strip, with the first key stripped
                addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
                // search for further keys to strip, with the first key not stripped
                addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
            }
        }
    }

    /**
     * Determine the first (or last) nested property separator in the
     * given property path, ignoring dots in keys (like "map[my.key]").
     * @param propertyPath the property path to check
     * @param last whether to return the last separator rather than the first
     * @return the index of the nested property separator, or -1 if none
     */
    private int getNestedPropertySeparatorIndex(String propertyPath, boolean last) {
        boolean inKey = false;
        int i = (last ? propertyPath.length() - 1 : 0);
        while ((last && i >= 0) || i < propertyPath.length()) {
            switch (propertyPath.charAt(i)) {
            case PROPERTY_KEY_PREFIX_CHAR:
            case PROPERTY_KEY_SUFFIX_CHAR:
                inKey = !inKey;
                break;
            case NESTED_PROPERTY_SEPARATOR_CHAR:
                if (!inKey) {
                    return i;
                }
            }
            if (last)
                i--;
            else
                i++;
        }
        return -1;
    }

    /**
     * Get the last component of the path. Also works if not nested.
     * @param bw BeanWrapper to work on
     * @param nestedPath property path we know is nested
     * @return last component of the path (the property on the target bean)
     */
    private String getFinalPath(BeanWrapper bw, String nestedPath) {
        if (bw == this) {
            return nestedPath;
        }
        return nestedPath.substring(getNestedPropertySeparatorIndex(nestedPath, true) + 1);
    }

    /**
     * Recursively navigate to return a BeanWrapper for the nested property path.
     * @param propertyPath property property path, which may be nested
     * @return a BeanWrapper for the target bean
     */
    protected ExtendedBeanWrapper getBeanWrapperForPropertyPath(String propertyPath) throws BeansException {
        int pos = getNestedPropertySeparatorIndex(propertyPath, false);
        // handle nested properties recursively
        if (pos > -1) {
            String nestedProperty = propertyPath.substring(0, pos);
            String nestedPath = propertyPath.substring(pos + 1);
            ExtendedBeanWrapper nestedBw = getNestedBeanWrapper(nestedProperty);
            return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
        } else {
            return this;
        }
    }

    /**
     * Retrieve a BeanWrapper for the given nested property.
     * Create a new one if not found in the cache.
     * <p>Note: Caching nested BeanWrappers is necessary now,
     * to keep registered custom editors for nested properties.
     * @param nestedProperty property to create the BeanWrapper for
     * @return the BeanWrapper instance, either cached or newly created
     */
    private ExtendedBeanWrapper getNestedBeanWrapper(String nestedProperty) throws BeansException {
        if (this.nestedBeanWrappers == null) {
            this.nestedBeanWrappers = new HashMap();
        }
        // get value of bean property
        PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
        Object propertyValue = getPropertyValue(tokens);
        String canonicalName = tokens.canonicalName;
        String propertyName = tokens.actualName;
        if (propertyValue == null) {
            //tentar instanciar o objeto
            String errorMsg = "";
            try {
                Type returnType = getPropertyType(tokens);
                Class clazz = null;
                if (returnType instanceof Class) {
                    clazz = (Class) returnType;
                } else if (returnType instanceof ParameterizedType) {
                    clazz = (Class) ((ParameterizedType) returnType).getRawType();
                }
                if (clazz != null) {
                    errorMsg = "O erro pode ser evitado se a classe " + clazz.getName()
                            + " possuir um construtor pblico sem argumentos";
                    propertyValue = clazz.newInstance();
                    setPropertyValue(nestedProperty, propertyValue);
                }
            } catch (Exception e) {
                throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName, errorMsg);
            }

        }

        // lookup cached sub-BeanWrapper, create new one if not found
        ExtendedBeanWrapper nestedBw = (ExtendedBeanWrapper) this.nestedBeanWrappers.get(canonicalName);
        if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) {
            if (logger.isDebugEnabled()) {
                logger.debug("Creating new nested BeanWrapper for property '" + canonicalName + "'");
            }
            nestedBw = new ExtendedBeanWrapper(propertyValue,
                    this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR, this);
            // inherit all type-specific PropertyEditors
            if (this.customEditors != null) {
                for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) {
                    Map.Entry entry = (Map.Entry) it.next();
                    if (entry.getKey() instanceof Class) {
                        Class requiredType = (Class) entry.getKey();
                        PropertyEditor editor = (PropertyEditor) entry.getValue();
                        nestedBw.registerCustomEditor(requiredType, editor);
                    } else if (entry.getKey() instanceof String) {
                        String editorPath = (String) entry.getKey();
                        int pos = getNestedPropertySeparatorIndex(editorPath, false);
                        if (pos != -1) {
                            String editorNestedProperty = editorPath.substring(0, pos);
                            String editorNestedPath = editorPath.substring(pos + 1);
                            if (editorNestedProperty.equals(canonicalName)
                                    || editorNestedProperty.equals(propertyName)) {
                                CustomEditorHolder editorHolder = (CustomEditorHolder) entry.getValue();
                                nestedBw.registerCustomEditor(editorHolder.getRegisteredType(), editorNestedPath,
                                        editorHolder.getPropertyEditor());
                            }
                        }
                    }
                }
            }
            this.nestedBeanWrappers.put(canonicalName, nestedBw);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Using cached nested BeanWrapper for property '" + canonicalName + "'");
            }
        }
        return nestedBw;
    }

    private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
        PropertyTokenHolder tokens = new PropertyTokenHolder();
        String actualName = null;
        List<Object> keys = new ArrayList<Object>(2);
        int searchIndex = 0;
        while (searchIndex != -1) {
            int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
            searchIndex = -1;
            if (keyStart != -1) {
                int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
                if (keyEnd != -1) {
                    if (actualName == null) {
                        actualName = propertyName.substring(0, keyStart);
                    }
                    String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
                    if (key.startsWith("'") && key.endsWith("'")) {
                        key = key.substring(1, key.length() - 1);
                    } else if (key.startsWith("\"") && key.endsWith("\"")) {
                        key = key.substring(1, key.length() - 1);
                    }
                    keys.add(key);
                    searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
                }
            }
        }
        tokens.actualName = (actualName != null ? actualName : propertyName);
        tokens.canonicalName = tokens.actualName;
        if (!keys.isEmpty()) {
            tokens.canonicalName += PROPERTY_KEY_PREFIX
                    + StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX)
                    + PROPERTY_KEY_SUFFIX;
            tokens.keys = (String[]) keys.toArray(new String[keys.size()]);
        }
        return tokens;
    }

    public Object getPropertyValue(String propertyName) throws BeansException {
        ExtendedBeanWrapper nestedBw = getBeanWrapperForPropertyPath(propertyName);
        PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
        return nestedBw.getPropertyValue(tokens);
    }

    protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
        String propertyName = tokens.canonicalName;
        String actualName = tokens.actualName;
        PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName);
        if (pd == null || pd.getReadMethod() == null) {
            throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
        }
        if (logger.isDebugEnabled())
            logger.debug("About to invoke read method [" + pd.getReadMethod() + "] on object of class ["
                    + this.object.getClass().getName() + "]");
        try {
            Object value = pd.getReadMethod().invoke(this.object, (Object[]) null);
            Type genericReturnType = pd.getReadMethod().getGenericReturnType();
            Class rawReturnType = pd.getReadMethod().getReturnType();
            if (tokens.keys != null) {
                // apply indexes and map keys
                for (int i = 0; i < tokens.keys.length; i++) {
                    String key = tokens.keys[i];

                    //cria listas sob demanda.. no  mais necessrio utilizar o ListSet no pojo
                    Class originalClass = null;
                    if (value != null) {
                        originalClass = value.getClass();
                    }
                    if (value == null && rawReturnType != null && genericReturnType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                        if (Map.class.isAssignableFrom(rawReturnType)) {
                            value = new LinkedHashMap();
                            pd.getWriteMethod().invoke(this.object, value);
                        } else if (List.class.isAssignableFrom(rawReturnType)
                                || Set.class.isAssignableFrom(rawReturnType)) {
                            Type type = parameterizedType.getActualTypeArguments()[0];
                            value = new ListSet(type instanceof Class ? (Class) type
                                    : (Class) ((ParameterizedType) type).getRawType());
                            pd.getWriteMethod().invoke(this.object, value);
                        }
                    }
                    //fim da criacao sob demanda

                    if (value == null) {
                        throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
                                "Cannot access indexed value of property referenced in indexed " + "property path '"
                                        + propertyName + "': returned null");
                    } else if (value.getClass().isArray()) {
                        value = Array.get(value, Integer.parseInt(key));
                    } else if (value instanceof List) {
                        List list = (List) value;
                        try {
                            value = list.get(Integer.parseInt(key));
                        } catch (IndexOutOfBoundsException e) {
                            //tentar instanciar um bean da lista
                            String extraMessage = "";
                            if (genericReturnType instanceof ParameterizedType) {
                                ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                                Type type = parameterizedType.getActualTypeArguments()[0];
                                if (type instanceof Class) {
                                    Class clazz = (Class) type;
                                    extraMessage = "A classe " + clazz.getName()
                                            + " no possui um construtor publico sem argumentos";
                                    try {
                                        value = clazz.newInstance();
                                        int index = Integer.parseInt(key);
                                        int insertNulls = index - list.size();
                                        while (insertNulls > 0) { // 11/06/2012
                                            list.add(null);
                                            insertNulls--;
                                        }

                                        list.add(index, value); // CDIGO 15/01/2007
                                    } catch (InstantiationException e1) {
                                        throw new RuntimeException(
                                                "Aconteceu um erro ao acessar um elemento da classe "
                                                        + originalClass.getName() + " propriedade " + propertyName
                                                        + "  No foi possvel instanciar um bean para preencher a lista. "
                                                        + extraMessage,
                                                e);
                                    }
                                }
                            } else if (originalClass != null) {
                                throw new RuntimeException("Aconteceu um erro ao acessar um elemento da classe "
                                        + originalClass.getName() + " propriedade " + propertyName
                                        + "  Sugesto: Utilize uma lista que cresa quando for necessrio como o ListSet ou no instancie nenhum objeto para essa propriedade",
                                        e);
                            } else {
                                throw e;
                            }
                        }
                    } else if (value instanceof Set) {
                        // apply index to Iterator in case of a Set
                        //TODO CRIAR AUTOMATICAMENTE O BEAN DO SET
                        Set set = (Set) value;
                        int index = Integer.parseInt(key);
                        if (index < 0 || index >= set.size()) {
                            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                                    "Cannot get element with index " + index + " from Set of size " + set.size()
                                            + ", accessed using property path '" + propertyName + "'"
                                            + "  Sugesto: Utilize o ListSet ou no instancie nenhum objeto");
                        }
                        Iterator it = set.iterator();
                        for (int j = 0; it.hasNext(); j++) {
                            Object elem = it.next();
                            if (j == index) {
                                value = elem;
                                break;
                            }
                        }
                    } else if (value instanceof Map) {
                        if (!(genericReturnType instanceof ParameterizedType)) {
                            throw new NotParameterizedTypeException(
                                    "Path direciona a um Map no parameterizado com generics. " + " Propriedade '"
                                            + this.nestedPath + propertyName + "' da classe ["
                                            + this.rootObject.getClass().getName() + "]");
                        }
                        ParameterizedType parameterizedType = ((ParameterizedType) genericReturnType);
                        Type mapKeyType = parameterizedType.getActualTypeArguments()[0];
                        Type mapValueType = parameterizedType.getActualTypeArguments()[1];
                        Class rawKeyType = mapKeyType instanceof Class ? (Class) mapKeyType
                                : (Class) ((ParameterizedType) mapKeyType).getRawType();
                        Class rawValueType = mapValueType instanceof Class ? (Class) mapValueType
                                : (Class) ((ParameterizedType) mapValueType).getRawType();
                        Object objectKey = doTypeConversionIfNecessary(key, rawKeyType);
                        Map map = (Map) value;
                        value = map.get(objectKey);
                        if (value == null && List.class.isAssignableFrom(rawValueType)) {
                            List mapValue;
                            try {
                                Type listType = ((ParameterizedType) mapValueType).getActualTypeArguments()[0];
                                mapValue = new ListSet(listType instanceof Class ? (Class) listType
                                        : (Class) ((ParameterizedType) listType).getRawType());
                            } catch (ClassCastException e) {
                                throw new RuntimeException(
                                        "Na path " + propertyName + " um mapa contm uma lista no parametrizada");
                            }
                            map.put(objectKey, mapValue);
                            value = mapValue;
                        }
                    } else {
                        throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                                "Property referenced in indexed property path '" + propertyName
                                        + "' is neither an array nor a List nor a Set nor a Map; returned value was ["
                                        + value + "]");
                    }
                }
            }
            return value;
        } catch (InvocationTargetException ex) {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Getter for property '" + actualName + "' threw exception", ex);
        } catch (IllegalAccessException ex) {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Illegal attempt to get property '" + actualName + "' threw exception", ex);
        } catch (IndexOutOfBoundsException ex) {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Index of out of bounds in property path '" + propertyName + "'", ex);
        } catch (NumberFormatException ex) {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Invalid index in property path '" + propertyName + "'", ex);
        }
    }

    public void setPropertyValue(String propertyName, Object value) throws BeansException {
        ExtendedBeanWrapper nestedBw = null;
        try {
            nestedBw = getBeanWrapperForPropertyPath(propertyName);
        } catch (NotReadablePropertyException ex) {
            throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Nested property in path '" + propertyName + "' does not exist", ex);
        }
        PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
        nestedBw.setPropertyValue(tokens, value);
    }

    protected void setPropertyValue(PropertyTokenHolder tokens, Object newValue) throws BeansException {
        String propertyName = tokens.canonicalName;

        if (tokens.keys != null) {
            // apply indexes and map keys: fetch value for all keys but the last one
            PropertyTokenHolder getterTokens = new PropertyTokenHolder();
            getterTokens.canonicalName = tokens.canonicalName;
            getterTokens.actualName = tokens.actualName;
            getterTokens.keys = new String[tokens.keys.length - 1];
            System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
            Object propValue = null;
            Type type;
            try {
                propValue = getPropertyValue(getterTokens);
                type = getPropertyType(getterTokens);
                if (propValue == null) {
                    Class rawType = null;
                    if (type instanceof ParameterizedType) {
                        if (((ParameterizedType) type).getRawType() instanceof Class) {
                            rawType = (Class) ((ParameterizedType) type).getRawType();
                        } else if (type instanceof Class) {
                            rawType = (Class) type;
                        }
                    }
                    if (rawType != null && List.class.isAssignableFrom(rawType)) {
                        PropertyTokenHolder propertyTokenHolder = new PropertyTokenHolder();
                        propertyTokenHolder.actualName = getterTokens.actualName;
                        propertyTokenHolder.canonicalName = getterTokens.canonicalName;
                        setPropertyValue(propertyTokenHolder, new ArrayList());
                    }
                }
            } catch (NotReadablePropertyException ex) {
                throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                        "Cannot access indexed value in property referenced " + "in indexed property path '"
                                + propertyName + "'",
                        ex);
            }
            // set value for last key
            String key = tokens.keys[tokens.keys.length - 1];
            if (propValue == null) {
                throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
                        "Cannot access indexed value in property referenced " + "in indexed property path '"
                                + propertyName + "': returned null");
            } else if (propValue.getClass().isArray()) {
                Class requiredType = propValue.getClass().getComponentType();
                int arrayIndex = Integer.parseInt(key);
                Object oldValue = null;
                try {
                    if (this.extractOldValueForEditor) {
                        oldValue = Array.get(propValue, arrayIndex);
                    }
                    Object convertedValue = doTypeConversionIfNecessary(propertyName, propertyName, oldValue,
                            newValue, requiredType);
                    Array.set(propValue, Integer.parseInt(key), convertedValue);
                } catch (IllegalArgumentException ex) {
                    PropertyChangeEvent pce = new PropertyChangeEvent(this.rootObject,
                            this.nestedPath + propertyName, oldValue, newValue);
                    throw new TypeMismatchException(pce, requiredType, ex);
                } catch (IndexOutOfBoundsException ex) {
                    throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                            "Invalid array index in property path '" + propertyName + "'", ex);
                }
            } else if (propValue instanceof List) {
                List list = (List) propValue;
                int index = Integer.parseInt(key);
                Object oldValue = null;
                if (this.extractOldValueForEditor && index < list.size()) {
                    oldValue = list.get(index);
                }
                Object convertedValue = doTypeConversionIfNecessary(propertyName, propertyName, oldValue, newValue,
                        null);
                if (index < list.size()) {
                    list.set(index, convertedValue);
                } else if (index >= list.size()) {
                    for (int i = list.size(); i < index; i++) {
                        try {
                            list.add(null);
                        } catch (NullPointerException ex) {
                            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                                    "Cannot set element with index " + index + " in List of size " + list.size()
                                            + ", accessed using property path '" + propertyName
                                            + "': List does not support filling up gaps with null elements");
                        }
                    }
                    list.add(convertedValue);
                }
            } else if (propValue instanceof Map) {
                Map map = (Map) propValue;
                propValue.getClass().getGenericSuperclass();
                Object oldValue = null;
                if (this.extractOldValueForEditor) {
                    oldValue = map.get(key);
                }
                Type type2 = ((ParameterizedType) type).getActualTypeArguments()[1];
                Type type3 = ((ParameterizedType) type).getActualTypeArguments()[0];
                Class reqClass = null;
                Class keyReqClass = null;
                if (type2 instanceof Class) {
                    reqClass = (Class) type2;
                } else if (type2 instanceof ParameterizedType) {
                    reqClass = (Class) ((ParameterizedType) type2).getRawType();
                }

                if (type3 instanceof Class) {
                    keyReqClass = (Class) type3;
                } else if (type3 instanceof ParameterizedType) {
                    keyReqClass = (Class) ((ParameterizedType) type3).getRawType();
                }
                Object convertedValue = doTypeConversionIfNecessary(propertyName, propertyName, oldValue, newValue,
                        reqClass);
                Object convertedKey = doTypeConversionIfNecessary(key, keyReqClass);
                map.put(convertedKey, convertedValue);
            } else {
                throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                        "Property referenced in indexed property path '" + propertyName
                                + "' is neither an array nor a List nor a Map; returned value was [" + newValue
                                + "]");
            }
        }

        else {
            PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
            if (pd == null || pd.getWriteMethod() == null) {
                throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName);
            }

            Method readMethod = pd.getReadMethod();
            Method writeMethod = pd.getWriteMethod();
            Object oldValue = null;

            if (this.extractOldValueForEditor && readMethod != null) {
                try {
                    oldValue = readMethod.invoke(this.object, new Object[0]);
                } catch (Exception ex) {
                    logger.debug("Could not read previous value of property '" + this.nestedPath + propertyName,
                            ex);
                }
            }

            try {
                Object convertedValue = doTypeConversionIfNecessary(propertyName, propertyName, oldValue, newValue,
                        pd.getPropertyType());

                if (pd.getPropertyType().isPrimitive() && (convertedValue == null || "".equals(convertedValue))) {
                    throw new IllegalArgumentException("Invalid value [" + newValue + "] for property '"
                            + pd.getName() + "' of primitive type [" + pd.getPropertyType() + "]");
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("About to invoke write method [" + writeMethod + "] on object of class ["
                            + this.object.getClass().getName() + "]");
                }
                writeMethod.invoke(this.object, new Object[] { convertedValue });
                if (logger.isDebugEnabled()) {
                    logger.debug("Invoked write method [" + writeMethod + "] with value of type ["
                            + pd.getPropertyType().getName() + "]");
                }
            } catch (InvocationTargetException ex) {
                PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(this.rootObject,
                        this.nestedPath + propertyName, oldValue, newValue);
                if (ex.getTargetException() instanceof ClassCastException) {
                    throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(),
                            ex.getTargetException());
                } else {
                    throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException());
                }
            } catch (IllegalArgumentException ex) {
                PropertyChangeEvent pce = new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName,
                        oldValue, newValue);
                throw new TypeMismatchException(pce, pd.getPropertyType(), ex);
            } catch (IllegalAccessException ex) {
                PropertyChangeEvent pce = new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName,
                        oldValue, newValue);
                throw new MethodInvocationException(pce, ex);
            }
        }
    }

    private Type getPropertyType(PropertyTokenHolder tokens) {
        String propertyName = tokens.canonicalName;
        PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName);
        if (pd == null || pd.getReadMethod() == null) {
            throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
        }
        if (logger.isDebugEnabled())
            logger.debug("About to invoke read method [" + pd.getReadMethod() + "] on object of class ["
                    + this.object.getClass().getName() + "]");
        try {
            Type type = pd.getReadMethod().getGenericReturnType();
            if (tokens.keys != null) {
                // apply indexes and map keys
                for (int i = 0; i < tokens.keys.length; i++) {
                    if (type == null) {
                        throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
                                "Cannot access indexed value of property referenced in indexed " + "property path '"
                                        + propertyName + "': returned null");
                    } else if (type instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) type;
                        Class clazz = (Class) parameterizedType.getRawType();
                        if (Map.class.isAssignableFrom(clazz)) {
                            type = parameterizedType.getActualTypeArguments()[1];
                        } else if (Collection.class.isAssignableFrom(clazz)) {
                            type = parameterizedType.getActualTypeArguments()[0];
                        } else {
                            throw new RuntimeException("Tipo desconhecido " + parameterizedType);
                        }
                    } else if (type instanceof Class && ((Class) type).isArray()) {
                        return type;
                    } else {
                        throw new RuntimeException("Implementar converso!!!");
                    }
                }
            }
            return type;
        } catch (IndexOutOfBoundsException ex) {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Index of out of bounds in property path '" + propertyName + "'", ex);
        } catch (NumberFormatException ex) {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "Invalid index in property path '" + propertyName + "'", ex);
        }
    }

    public void setPropertyValue(PropertyValue pv) throws BeansException {
        setPropertyValue(pv.getName(), pv.getValue());
    }

    /**
     * Bulk update from a Map.
     * Bulk updates from PropertyValues are more powerful: this method is
     * provided for convenience.
     * @param map map containing properties to set, as name-value pairs.
     * The map may include nested properties.
     * @throws BeansException if there's a fatal, low-level exception
     */
    @Override
    public void setPropertyValues(Map<?, ?> map) throws BeansException {
        setPropertyValues(new MutablePropertyValues(map));
    }

    public void setPropertyValues(PropertyValues pvs) throws BeansException {
        setPropertyValues(pvs, false);
    }

    public void setPropertyValues(PropertyValues propertyValues, boolean ignoreUnknown) throws BeansException {
        List propertyAccessExceptions = new ArrayList();
        PropertyValue[] pvs = propertyValues.getPropertyValues();
        for (int i = 0; i < pvs.length; i++) {
            try {
                // This method may throw any BeansException, which won't be caught
                // here, if there is a critical failure such as no matching field.
                // We can attempt to deal only with less serious exceptions.
                setPropertyValue(pvs[i]);
            } catch (NotWritablePropertyException ex) {
                if (!ignoreUnknown) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            } catch (PropertyAccessException ex) {
                propertyAccessExceptions.add(ex);
            }
        }

        // If we encountered individual exceptions, throw the composite exception.
        if (!propertyAccessExceptions.isEmpty()) {
            Object[] paeArray = propertyAccessExceptions
                    .toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
            throw new PropertyBatchUpdateException((PropertyAccessException[]) paeArray);
        }
    }

    private PropertyChangeEvent createPropertyChangeEvent(String propertyName, Object oldValue, Object newValue) {
        return new PropertyChangeEvent((this.rootObject != null ? this.rootObject : "constructor"),
                (propertyName != null ? this.nestedPath + propertyName : null), oldValue, newValue);
    }

    /**
     * Convert the value to the required type (if necessary from a String).
     * <p>Conversions from String to any type use the <code>setAsText</code> method
     * of the PropertyEditor class. Note that a PropertyEditor must be registered
     * for the given class for this to work; this is a standard JavaBeans API.
     * A number of PropertyEditors are automatically registered by BeanWrapperImpl.
     * @param newValue proposed change value
     * @param requiredType the type we must convert to
     * @return the new value, possibly the result of type conversion
     * @throws TypeMismatchException if type conversion failed
     * @see java.beans.PropertyEditor#setAsText(String)
     * @see java.beans.PropertyEditor#getValue()
     */
    public Object doTypeConversionIfNecessary(Object newValue, Class requiredType) throws TypeMismatchException {
        return doTypeConversionIfNecessary(null, null, null, newValue, requiredType);
    }

    /**
     * Convert the value to the required type (if necessary from a String),
     * for the specified property.
     * @param propertyName name of the property
     * @param oldValue previous value, if available (may be <code>null</code>)
     * @param newValue proposed change value
     * @param requiredType the type we must convert to
     * (or <code>null</code> if not known, for example in case of a collection element)
     * @return the new value, possibly the result of type conversion
     * @throws TypeMismatchException if type conversion failed
     */
    protected Object doTypeConversionIfNecessary(String propertyName, String fullPropertyName, Object oldValue,
            Object newValue, Class requiredType) throws TypeMismatchException {

        Object convertedValue = newValue;
        if (convertedValue != null) {

            // Custom editor for this type?
            PropertyEditor pe = findCustomEditor(requiredType, fullPropertyName);

            // Value not of required type?
            if (pe != null || (requiredType != null
                    && (requiredType.isArray() || !requiredType.isAssignableFrom(convertedValue.getClass())))) {

                if (requiredType != null) {
                    if (pe == null) {
                        // No custom editor -> check BeanWrapperImpl's default editors.
                        pe = (PropertyEditor) this.defaultEditors.get(requiredType);
                        if (pe == null) {
                            // No BeanWrapper default editor -> check standard JavaBean editors.
                            pe = PropertyEditorManager.findEditor(requiredType);
                        }
                    }
                }

                if (pe != null && !(convertedValue instanceof String)) {
                    // Not a String -> use PropertyEditor's setValue.
                    // With standard PropertyEditors, this will return the very same object;
                    // we just want to allow special PropertyEditors to override setValue
                    // for type conversion from non-String values to the required type.
                    try {
                        pe.setValue(convertedValue);
                        Object newConvertedValue = pe.getValue();
                        if (newConvertedValue != convertedValue) {
                            convertedValue = newConvertedValue;
                            // Reset PropertyEditor: It already did a proper conversion.
                            // Don't use it again for a setAsText call.
                            pe = null;
                        }
                    } catch (IllegalArgumentException ex) {
                        throw new TypeMismatchException(
                                createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType, ex);
                    }
                }

                if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
                    // Convert String array to a comma-separated String.
                    // Only applies if no PropertyEditor converted the String array before.
                    // The CSV String will be passed into a PropertyEditor's setAsText method, if any.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Converting String array to comma-delimited String [" + convertedValue + "]");
                    }
                    convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
                }

                if (pe != null && convertedValue instanceof String) {
                    // Use PropertyEditor's setAsText in case of a String value.
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "Converting String to [" + requiredType + "] using property editor [" + pe + "]");
                    }
                    try {
                        pe.setValue(oldValue);
                        pe.setAsText((String) convertedValue);
                        convertedValue = pe.getValue();
                    } catch (IllegalArgumentException ex) {
                        throw new TypeMismatchException(
                                createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType, ex);
                    }
                }

                if (requiredType != null) {
                    // Array required -> apply appropriate conversion of elements.
                    if (requiredType.isArray()) {
                        Class componentType = requiredType.getComponentType();
                        if (convertedValue instanceof Collection) {
                            // Convert Collection elements to array elements.
                            Collection coll = (Collection) convertedValue;
                            Object result = Array.newInstance(componentType, coll.size());
                            int i = 0;
                            for (Iterator it = coll.iterator(); it.hasNext(); i++) {
                                Object value = doTypeConversionIfNecessary(propertyName,
                                        propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX, null,
                                        it.next(), componentType);
                                Array.set(result, i, value);
                            }
                            return result;
                        } else if (convertedValue != null && convertedValue.getClass().isArray()) {
                            // Convert Collection elements to array elements.
                            int arrayLength = Array.getLength(convertedValue);
                            Object result = Array.newInstance(componentType, arrayLength);
                            for (int i = 0; i < arrayLength; i++) {
                                Object value = doTypeConversionIfNecessary(propertyName,
                                        propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX, null,
                                        Array.get(convertedValue, i), componentType);
                                Array.set(result, i, value);
                            }
                            return result;
                        } else {
                            // A plain value: convert it to an array with a single component.
                            Object result = Array.newInstance(componentType, 1);
                            Object value = doTypeConversionIfNecessary(propertyName,
                                    propertyName + PROPERTY_KEY_PREFIX + 0 + PROPERTY_KEY_SUFFIX, null,
                                    convertedValue, componentType);
                            Array.set(result, 0, value);
                            return result;
                        }
                    }

                    // If the resulting value definitely doesn't match the required type,
                    // try field lookup as fallback. If no matching field found,
                    // throw explicit TypeMismatchException with full context information.
                    if (convertedValue != null && !requiredType.isPrimitive()
                            && !requiredType.isAssignableFrom(convertedValue.getClass())) {

                        // In case of String value, try to find matching field (for JDK 1.5
                        // enum or custom enum with values defined as static fields).
                        if (convertedValue instanceof String) {
                            try {
                                Field enumField = requiredType.getField((String) convertedValue);
                                return enumField.get(null);
                            } catch (Exception ex) {
                                logger.debug("Field [" + convertedValue + "] isn't an enum value", ex);
                            }
                        }

                        // Definitely doesn't match: throw TypeMismatchException.
                        throw new TypeMismatchException(
                                createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType);
                    }
                }
            }
        }

        if (requiredType != null && List.class.isAssignableFrom(requiredType)) { //treat conventions of enum lists
            Type genericReturnType = getPropertyDescriptorInternal(fullPropertyName).getReadMethod()
                    .getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType) {
                Type actualType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
                if (actualType instanceof Class && convertedValue != null) {
                    List list = (List) convertedValue;
                    for (int i = 0; i < list.size(); i++) {
                        Object o = list.remove(i);
                        o = doTypeConversionIfNecessary(o, (Class) actualType);
                        list.add(i, o);
                    }
                }
            }
        }

        return convertedValue;
    }

    public PropertyDescriptor[] getPropertyDescriptors() {
        return this.cachedIntrospectionResults.getBeanInfo().getPropertyDescriptors();
    }

    public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException {
        if (propertyName == null) {
            throw new IllegalArgumentException("Can't find property descriptor for <code>null</code> property");
        }
        PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
        if (pd != null) {
            return pd;
        } else {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
                    "No property '" + propertyName + "' found");
        }
    }

    /**
     * Internal version of getPropertyDescriptor:
     * Returns null if not found rather than throwing an exception.
     */
    protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException {
        Assert.state(this.object != null, "BeanWrapper does not hold a bean instance");
        ExtendedBeanWrapper nestedBw = getBeanWrapperForPropertyPath(propertyName);
        return nestedBw.cachedIntrospectionResults.getPropertyDescriptor(getFinalPath(nestedBw, propertyName));
    }

    public Class getPropertyType(String propertyName) throws BeansException {
        try {
            PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
            if (pd != null) {
                return pd.getPropertyType();
            } else {
                // Maybe an indexed/mapped property...
                Object value = getPropertyValue(propertyName);
                if (value != null) {
                    return value.getClass();
                }
                // Check to see if there is a custom editor,
                // which might give an indication on the desired target type.
                if (this.customEditors != null) {
                    CustomEditorHolder editorHolder = (CustomEditorHolder) this.customEditors.get(propertyName);
                    if (editorHolder == null) {
                        List strippedPaths = new LinkedList();
                        addStrippedPropertyPaths(strippedPaths, "", propertyName);
                        for (Iterator it = strippedPaths.iterator(); it.hasNext() && editorHolder == null;) {
                            String strippedName = (String) it.next();
                            editorHolder = (CustomEditorHolder) this.customEditors.get(strippedName);
                        }
                    }
                    if (editorHolder != null) {
                        return editorHolder.getRegisteredType();
                    }
                }
            }
        } catch (InvalidPropertyException ex) {
            // Consider as not determinable.
        }
        return null;
    }

    public boolean isReadableProperty(String propertyName) {
        // This is a programming error, although asking for a property
        // that doesn't exist is not.
        if (propertyName == null) {
            throw new IllegalArgumentException("Can't find readability status for <code>null</code> property");
        }
        try {
            PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
            if (pd != null) {
                if (pd.getReadMethod() != null) {
                    return true;
                }
            } else {
                // maybe an indexed/mapped property
                getPropertyValue(propertyName);
                return true;
            }
        } catch (InvalidPropertyException ex) {
            // cannot be evaluated, so can't be readable
        }
        return false;
    }

    public boolean isWritableProperty(String propertyName) {
        // This is a programming error, although asking for a property
        // that doesn't exist is not.
        if (propertyName == null) {
            throw new IllegalArgumentException("Can't find writability status for <code>null</code> property");
        }
        try {
            PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
            if (pd != null) {
                if (pd.getWriteMethod() != null) {
                    return true;
                }
            } else {
                // maybe an indexed/mapped property
                getPropertyValue(propertyName);
                return true;
            }
        } catch (InvalidPropertyException ex) {
            // cannot be evaluated, so can't be writable
        }
        return false;
    }

    //---------------------------------------------------------------------
    // Diagnostics
    //---------------------------------------------------------------------

    public String toString() {
        StringBuffer sb = new StringBuffer("BeanWrapperImpl: wrapping class [");
        sb.append(getWrappedClass().getName()).append("]");
        return sb.toString();
    }

    /**
     * Holder for a registered custom editor with property name.
     * Keeps the PropertyEditor itself plus the type it was registered for.
     */
    private static class CustomEditorHolder {

        private final PropertyEditor propertyEditor;

        private final Class registeredType;

        private CustomEditorHolder(PropertyEditor propertyEditor, Class registeredType) {
            this.propertyEditor = propertyEditor;
            this.registeredType = registeredType;
        }

        private PropertyEditor getPropertyEditor() {
            return propertyEditor;
        }

        private Class getRegisteredType() {
            return registeredType;
        }

        private PropertyEditor getPropertyEditor(Class requiredType) {
            // Special case: If no required type specified, which usually only happens for
            // Collection elements, or required type is not assignable to registered type,
            // which usually only happens for generic properties of type Object -
            // then return PropertyEditor if not registered for Collection or array type.
            // (If not registered for Collection or array, it is assumed to be intended
            // for elements.)
            /*
            if (this.registeredType == null ||
                  (requiredType != null &&
                  (BeanUtils.isAssignable(this.registeredType, requiredType) ||
                  BeanUtils.isAssignable(requiredType, this.registeredType))) ||
                  (requiredType == null &&
                  (!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) {
               return this.propertyEditor;
            }
            else {
               return null;
            }
            */
            return null;
        }
    }

    private static class PropertyTokenHolder {

        private String canonicalName;

        private String actualName;

        private String[] keys;
    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam)
            throws TypeMismatchException {
        return (T) doTypeConversionIfNecessary(value, requiredType);
    }

    public boolean isExtractOldValueForEditor() {
        return false;
    }

    public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
            throws BeansException {
        setPropertyValues(pvs, ignoreUnknown);
    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException {
        return (T) doTypeConversionIfNecessary(value, requiredType);
    }

    public boolean isAutoGrowNestedPaths() {
        return true;
    }

    public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
        //o extended bean wrapper sempre utiliza o autoGrowNestedPaths
    }

    public ConversionService getConversionService() {
        return null;
    }

    public void setConversionService(ConversionService conversionService) {
        throw new NextException("O ExtendedBeanWrapper no suporta ConversionService");
    }

    public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException {
        throw new NextException("Not supported");
    }

    @Override
    public int getAutoGrowCollectionLimit() {
        return Integer.MAX_VALUE;
    }

    @Override
    public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {

    }

    @Override
    public <T> T convertIfNecessary(Object value, Class<T> requiredType, Field field) throws TypeMismatchException {
        return doConvert(value, requiredType, null, field);
    }

    private <T> T doConvert(Object value, Class<T> requiredType, MethodParameter methodParam, Field field)
            throws TypeMismatchException {
        try {
            if (field != null) {
                return (T) doTypeConversionIfNecessary(value, requiredType);
            } else {
                return (T) doTypeConversionIfNecessary(value, requiredType);
            }
        } catch (ConverterNotFoundException ex) {
            throw new ConversionNotSupportedException(value, requiredType, ex);
        } catch (ConversionException ex) {
            throw new TypeMismatchException(value, requiredType, ex);
        } catch (IllegalStateException ex) {
            throw new ConversionNotSupportedException(value, requiredType, ex);
        } catch (IllegalArgumentException ex) {
            throw new TypeMismatchException(value, requiredType, ex);
        }
    }

}