    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            String name = pd.getName();
    051
    052            if (adapters.containsKey(name))
    053            {
    054                continue;
    055            }
    056
    057            final Class<?> thisPropertyType = pd.getPropertyType();
    058            if (thisPropertyType == null)
    059                continue;
    060
    061            Method readMethod = pd.getReadMethod();
    062            Method writeMethod = pd.getWriteMethod();
    063
    064            // TAP5-1493
    065            if (readMethod != null && readMethod.isBridge())
    066            {
    067                if (nonBridgeMethods == null)
    068                {
    069                    nonBridgeMethods = groupNonBridgeMethodsByName(beanType);
    070                }
    071                readMethod = findMethodWithSameNameAndParamCount(readMethod, nonBridgeMethods);
    072            }
    073
    074            // TAP5-1548, TAP5-1885: trying to find a getter which Introspector missed
    075            if (readMethod == null) {
    076                final String prefix = thisPropertyType != boolean.class ? "get" : "is";
    077                try
    078                {
    079                    Method method = beanType.getMethod(prefix + capitalize(name));
    080                    final Class<?> returnType = method.getReturnType();
    081                    if (returnType.equals(thisPropertyType) || returnType.isInstance(thisPropertyType)) {
    082                        readMethod = method;
    083                    }
    084                }
    085                catch (SecurityException e) {
    086                    // getter not usable.
    087                }
    088                catch (NoSuchMethodException e)
    089                {
    090                    // getter doesn't exist.
    091                }
    092            }
    093
    094            if (writeMethod != null && writeMethod.isBridge())
    095            {
    096                if (nonBridgeMethods == null)
    097                {
    098                    nonBridgeMethods = groupNonBridgeMethodsByName(beanType);
    099                }
    100                writeMethod = findMethodWithSameNameAndParamCount(writeMethod, nonBridgeMethods);
    101            }
    102
    103            // TAP5-1548, TAP5-1885: trying to find a setter which Introspector missed
    104            if (writeMethod == null) {
    105                try
    106                {
    107                    Method method = beanType.getMethod("set" + capitalize(name), pd.getPropertyType());
    108                    final Class<?> returnType = method.getReturnType();
    109                    if (returnType.equals(void.class)) {
    110                        writeMethod = method;
    111                    }
    112                }
    113                catch (SecurityException e) {
    114                    // setter not usable.
    115                }
    116                catch (NoSuchMethodException e)
    117                {
    118                    // setter doesn't exist.
    119                }
    120            }
    121
    122            Class propertyType = readMethod == null ? thisPropertyType : GenericsUtils.extractGenericReturnType(
    123                    beanType, readMethod);
    124
    125            PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, readMethod, writeMethod);
    126
    127            adapters.put(pa.getName(), pa);
    128        }
    129
    130        // Now, add any public fields (even if static) that do not conflict
    131
    132        for (Field f : beanType.getFields())
    133        {
    134            String name = f.getName();
    135
    136            if (!adapters.containsKey(name))
    137            {
    138                Class propertyType = GenericsUtils.extractGenericFieldType(beanType, f);
    139                PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, f);
    140
    141                adapters.put(name, pa);
    142            }
    143        }
    144    }
    145
    146    private static String capitalize(String name)
    147    {
    148        return Character.toUpperCase(name.charAt(0)) + name.substring(1);
    149    }
    150
    151    /**
    152     * Find a replacement for the method (if one exists)
    153     * @param method A method
    154     * @param groupedMethods Methods mapped by name
    155     * @return A method from groupedMethods with the same name / param count
    156     *         (default to providedmethod if none found)
    157     */
    158    private Method findMethodWithSameNameAndParamCount(Method method, Map<String, List<Method>> groupedMethods) {
    159        List<Method> methodGroup = groupedMethods.get(method.getName());
    160        if (methodGroup != null)
    161        {
    162            for (Method nonBridgeMethod : methodGroup)
    163            {
    164                if (nonBridgeMethod.getParameterTypes().length == method.getParameterTypes().length)
    165                {
    166                    // return the non-bridge method with the same name / argument count
    167                    return nonBridgeMethod;
    168                }
    169            }
    170        }
    171
    172        // default to the provided method
    173        return method;
    174    }
    175
    176    /**
    177     * Find all of the public methods that are not bridge methods and
    178     * group them by method name
    179     *
    180     * {@see Method#isBridge()}
    181     * @param type Bean type
    182     * @return
    183     */
    184    private Map<String, List<Method>> groupNonBridgeMethodsByName(Class type)
    185    {
    186        Map<String, List<Method>> methodGroupsByName = CollectionFactory.newMap();
    187        for (Method method : type.getMethods())
    188        {
    189            if (!method.isBridge())
    190            {
    191                List<Method> methodGroup = methodGroupsByName.get(method.getName());
    192                if (methodGroup == null)
    193                {
    194                    methodGroup = CollectionFactory.newList();
    195                    methodGroupsByName.put(method.getName(), methodGroup);
    196                }
    197                methodGroup.add(method);
    198            }
    199        }
    200        return methodGroupsByName;
    201    }
    202
    203    @Override
    204    public Class getBeanType()
    205    {
    206        return beanType;
    207    }
    208
    209    @Override
    210    public String toString()
    211    {
    212        String names = InternalCommonsUtils.joinSorted(adapters.keySet());
    213
    214        return String.format("<ClassPropertyAdaptor %s: %s>", beanType.getName(), names);
    215    }
    216
    217    @Override
    218    public List<String> getPropertyNames()
    219    {
    220        return InternalCommonsUtils.sortedKeys(adapters);
    221    }
    222
    223    @Override
    224    public PropertyAdapter getPropertyAdapter(String name)
    225    {
    226        return adapters.get(name);
    227    }
    228
    229    @Override
    230    public Object get(Object instance, String propertyName)
    231    {
    232        return adaptorFor(propertyName).get(instance);
    233    }
    234
    235    @Override
    236    public void set(Object instance, String propertyName, Object value)
    237    {
    238        adaptorFor(propertyName).set(instance, value);
    239    }
    240
    241    @Override
    242    public Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass) {
    243    return adaptorFor(propertyName).getAnnotation(annotationClass);
    244    }
    245
    246    private PropertyAdapter adaptorFor(String name)
    247    {
    248        PropertyAdapter pa = adapters.get(name);
    249
    250        if (pa == null)
    251            throw new IllegalArgumentException(ServiceMessages.noSuchProperty(beanType, name));
    252
    253        return pa;
    254    }
    255
    256}