    001// Copyright 2006, 2007, 2008, 2010, 2011, 2012 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.ioc.internal.services;
    016
    017import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
    018
    019import java.beans.PropertyDescriptor;
    020import java.lang.annotation.Annotation;
    021import java.lang.reflect.Field;
    022import java.lang.reflect.Method;
    023import java.util.List;
    024import java.util.Map;
    025
    026import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    027import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
    028import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils;
    029import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
    030import org.apache.tapestry5.ioc.services.PropertyAdapter;
    031
    032public class ClassPropertyAdapterImpl implements ClassPropertyAdapter
    033{
    034    private final Map<String, PropertyAdapter> adapters = newCaseInsensitiveMap();
    035
    036    private final Class beanType;
    037
    038    public ClassPropertyAdapterImpl(Class beanType, List<PropertyDescriptor> descriptors)
    039    {
    040        this.beanType = beanType;
    041
    042        // lazy init
    043        Map<String, List<Method>> nonBridgeMethods = null;
    044
    045        for (PropertyDescriptor pd : descriptors)
    046        {
    047            // Indexed properties will have a null propertyType (and a non-null
    048            // indexedPropertyType). We ignore indexed properties.
    049
    050            final Class<?> thisPropertyType = pd.getPropertyType();
    051            if (thisPropertyType == null)
    052                continue;
    053
    054            Method readMethod = pd.getReadMethod();
    055            Method writeMethod = pd.getWriteMethod();
    056
    057            // TAP5-1493
    058            if (readMethod != null && readMethod.isBridge())
    059            {
    060                if (nonBridgeMethods == null)
    061                {
    062                    nonBridgeMethods = groupNonBridgeMethodsByName(beanType);
    063                }
    064                readMethod = findMethodWithSameNameAndParamCount(readMethod, nonBridgeMethods);
    065            }
    066
    067            // TAP5-1548, TAP5-1885: trying to find a getter which Introspector missed
    068            if (readMethod == null) {
    069                final String prefix = thisPropertyType != boolean.class ? "get" : "is";
    070                try
    071                {
    072                    Method method = beanType.getMethod(prefix + capitalize(pd.getName()));
    073                    final Class<?> returnType = method.getReturnType();
    074                    if (returnType.equals(thisPropertyType) || returnType.isInstance(thisPropertyType)) {
    075                        readMethod = method;
    076                    }
    077                }
    078                catch (SecurityException e) {
    079                    // getter not usable.
    080                }
    081                catch (NoSuchMethodException e)
    082                {
    083                    // getter doesn't exist.
    084                }
    085            }
    086
    087            if (writeMethod != null && writeMethod.isBridge())
    088            {
    089                if (nonBridgeMethods == null)
    090                {
    091                    nonBridgeMethods = groupNonBridgeMethodsByName(beanType);
    092                }
    093                writeMethod = findMethodWithSameNameAndParamCount(writeMethod, nonBridgeMethods);
    094            }
    095
    096            // TAP5-1548, TAP5-1885: trying to find a setter which Introspector missed
    097            if (writeMethod == null) {
    098                try
    099                {
    100                    Method method = beanType.getMethod("set" + capitalize(pd.getName()), pd.getPropertyType());
    101                    final Class<?> returnType = method.getReturnType();
    102                    if (returnType.equals(void.class)) {
    103                        writeMethod = method;
    104                    }
    105                }
    106                catch (SecurityException e) {
    107                    // setter not usable.
    108                }
    109                catch (NoSuchMethodException e)
    110                {
    111                    // setter doesn't exist.
    112                }
    113            }
    114
    115            Class propertyType = readMethod == null ? thisPropertyType : GenericsUtils.extractGenericReturnType(
    116                    beanType, readMethod);
    117
    118            PropertyAdapter pa = new PropertyAdapterImpl(this, pd.getName(), propertyType, readMethod, writeMethod);
    119
    120            adapters.put(pa.getName(), pa);
    121        }
    122
    123        // Now, add any public fields (even if static) that do not conflict
    124
    125        for (Field f : beanType.getFields())
    126        {
    127            String name = f.getName();
    128
    129            if (!adapters.containsKey(name))
    130            {
    131                Class propertyType = GenericsUtils.extractGenericFieldType(beanType, f);
    132                PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, f);
    133
    134                adapters.put(name, pa);
    135            }
    136        }
    137    }
    138
    139    private static String capitalize(String name)
    140    {
    141        return Character.toUpperCase(name.charAt(0)) + name.substring(1);
    142    }
    143
    144    /**
    145     * Find a replacement for the method (if one exists)
    146     * @param method A method
    147     * @param groupedMethods Methods mapped by name
    148     * @return A method from groupedMethods with the same name / param count
    149     *         (default to providedmethod if none found)
    150     */
    151    private Method findMethodWithSameNameAndParamCount(Method method, Map<String, List<Method>> groupedMethods) {
    152        List<Method> methodGroup = groupedMethods.get(method.getName());
    153        if (methodGroup != null)
    154        {
    155            for (Method nonBridgeMethod : methodGroup)
    156            {
    157                if (nonBridgeMethod.getParameterTypes().length == method.getParameterTypes().length)
    158                {
    159                    // return the non-bridge method with the same name / argument count
    160                    return nonBridgeMethod;
    161                }
    162            }
    163        }
    164
    165        // default to the provided method
    166        return method;
    167    }
    168
    169    /**
    170     * Find all of the public methods that are not bridge methods and
    171     * group them by method name
    172     *
    173     * {@see Method#isBridge()}
    174     * @param type Bean type
    175     * @return
    176     */
    177    private Map<String, List<Method>> groupNonBridgeMethodsByName(Class type)
    178    {
    179        Map<String, List<Method>> methodGroupsByName = CollectionFactory.newMap();
    180        for (Method method : type.getMethods())
    181        {
    182            if (!method.isBridge())
    183            {
    184                List<Method> methodGroup = methodGroupsByName.get(method.getName());
    185                if (methodGroup == null)
    186                {
    187                    methodGroup = CollectionFactory.newList();
    188                    methodGroupsByName.put(method.getName(), methodGroup);
    189                }
    190                methodGroup.add(method);
    191            }
    192        }
    193        return methodGroupsByName;
    194    }
    195
    196    @Override
    197    public Class getBeanType()
    198    {
    199        return beanType;
    200    }
    201
    202    @Override
    203    public String toString()
    204    {
    205        String names = InternalCommonsUtils.joinSorted(adapters.keySet());
    206
    207        return String.format("<ClassPropertyAdaptor %s: %s>", beanType.getName(), names);
    208    }
    209
    210    @Override
    211    public List<String> getPropertyNames()
    212    {
    213        return InternalCommonsUtils.sortedKeys(adapters);
    214    }
    215
    216    @Override
    217    public PropertyAdapter getPropertyAdapter(String name)
    218    {
    219        return adapters.get(name);
    220    }
    221
    222    @Override
    223    public Object get(Object instance, String propertyName)
    224    {
    225        return adaptorFor(propertyName).get(instance);
    226    }
    227
    228    @Override
    229    public void set(Object instance, String propertyName, Object value)
    230    {
    231        adaptorFor(propertyName).set(instance, value);
    232    }
    233
    234    @Override
    235    public Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass) {
    236    return adaptorFor(propertyName).getAnnotation(annotationClass);
    237    }
    238
    239    private PropertyAdapter adaptorFor(String name)
    240    {
    241        PropertyAdapter pa = adapters.get(name);
    242
    243        if (pa == null)
    244            throw new IllegalArgumentException(ServiceMessages.noSuchProperty(beanType, name));
    245
    246        return pa;
    247    }
    248
    249}