    001// Copyright 2006-2013 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.beanmodel.internal.services;
    016
    017import java.lang.annotation.Annotation;
    018import java.lang.reflect.*;
    019import java.util.List;
    020
    021import org.apache.tapestry5.commons.AnnotationProvider;
    022import org.apache.tapestry5.commons.internal.services.AccessableObjectAnnotationProvider;
    023import org.apache.tapestry5.commons.internal.services.AnnotationProviderChain;
    024import org.apache.tapestry5.commons.internal.services.ServiceMessages;
    025import org.apache.tapestry5.commons.services.ClassPropertyAdapter;
    026import org.apache.tapestry5.commons.services.PropertyAdapter;
    027import org.apache.tapestry5.commons.util.CollectionFactory;
    028import org.apache.tapestry5.commons.util.ExceptionUtils;
    029
    030public class PropertyAdapterImpl implements PropertyAdapter
    031{
    032    private final ClassPropertyAdapter classAdapter;
    033
    034    private final String name;
    035
    036    private final Method readMethod;
    037
    038    private final Method writeMethod;
    039
    040    private final Class type;
    041
    042    private final boolean castRequired;
    043
    044    // Synchronized by this; lazily initialized
    045    private AnnotationProvider annotationProvider;
    046
    047    private final Field field;
    048
    049    private final Class declaringClass;
    050
    051    PropertyAdapterImpl(ClassPropertyAdapter classAdapter, String name, Class type, Method readMethod,
    052                        Method writeMethod)
    053    {
    054        this.classAdapter = classAdapter;
    055        this.name = name;
    056        this.type = type;
    057
    058        this.readMethod = readMethod;
    059        this.writeMethod = writeMethod;
    060
    061        declaringClass = readMethod != null ? readMethod.getDeclaringClass() : writeMethod.getDeclaringClass();
    062
    063        castRequired = readMethod != null && readMethod.getReturnType() != type;
    064
    065        field = null;
    066    }
    067
    068    PropertyAdapterImpl(ClassPropertyAdapter classAdapter, String name, Class type, Field field)
    069    {
    070        this.classAdapter = classAdapter;
    071        this.name = name;
    072        this.type = type;
    073
    074        this.field = field;
    075
    076        declaringClass = field.getDeclaringClass();
    077
    078        castRequired = field.getType() != type;
    079
    080        readMethod = null;
    081        writeMethod = null;
    082    }
    083
    084    @Override
    085    public String getName()
    086    {
    087        return name;
    088    }
    089
    090    @Override
    091    public Method getReadMethod()
    092    {
    093        return readMethod;
    094    }
    095
    096    @Override
    097    public Class getType()
    098    {
    099        return type;
    100    }
    101
    102    @Override
    103    public Method getWriteMethod()
    104    {
    105        return writeMethod;
    106    }
    107
    108    @Override
    109    public boolean isRead()
    110    {
    111        return field != null || readMethod != null;
    112    }
    113
    114    @Override
    115    public boolean isUpdate()
    116    {
    117        return writeMethod != null || (field != null && !isFinal(field));
    118    }
    119
    120    private boolean isFinal(Member member)
    121    {
    122        return Modifier.isFinal(member.getModifiers());
    123    }
    124
    125    @Override
    126    public Object get(Object instance)
    127    {
    128        if (field == null && readMethod == null)
    129        {
    130            throw new UnsupportedOperationException(String.format("Class %s does not provide an accessor ('getter') method for property '%s'.", toClassName(instance), name));
    131        }
    132
    133        Throwable fail;
    134
    135        try
    136        {
    137            if (field == null)
    138                return readMethod.invoke(instance);
    139            else
    140                return field.get(instance);
    141        } catch (InvocationTargetException ex)
    142        {
    143            fail = ex.getTargetException();
    144        } catch (Exception ex)
    145        {
    146            fail = ex;
    147        }
    148
    149        throw new RuntimeException(ServiceMessages.readFailure(name, instance, fail), fail);
    150    }
    151
    152    @Override
    153    public void set(Object instance, Object value)
    154    {
    155        if (field == null && writeMethod == null)
    156        {
    157            throw new UnsupportedOperationException(String.format("Class %s does not provide a mutator ('setter') method for property '%s'.",
    158                    toClassName(instance),
    159                    name
    160            ));
    161        }
    162
    163        Throwable fail;
    164
    165        try
    166        {
    167            if (field == null)
    168                writeMethod.invoke(instance, value);
    169            else
    170                field.set(instance, value);
    171
    172            return;
    173        } catch (InvocationTargetException ex)
    174        {
    175            fail = ex.getTargetException();
    176        } catch (Exception ex)
    177        {
    178            fail = ex;
    179        }
    180
    181        throw new RuntimeException(String.format("Error updating property '%s' of %s: %s",
    182                name, toClassName(instance),
    183                ExceptionUtils.toMessage(fail)), fail);
    184    }
    185
    186    private String toClassName(Object instance)
    187    {
    188        return instance == null ? "<null>" : instance.getClass().getName();
    189    }
    190
    191    @Override
    192    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    193    {
    194        return getAnnnotationProvider().getAnnotation(annotationClass);
    195    }
    196
    197    /**
    198     * Creates (as needed) the annotation provider for this property.
    199     */
    200    private synchronized AnnotationProvider getAnnnotationProvider()
    201    {
    202        if (annotationProvider == null)
    203        {
    204            List<AnnotationProvider> providers = CollectionFactory.newList();
    205
    206            if (readMethod != null)
    207                providers.add(new AccessableObjectAnnotationProvider(readMethod));
    208
    209            if (writeMethod != null)
    210                providers.add(new AccessableObjectAnnotationProvider(writeMethod));
    211
    212            // There's an assumption here, that the fields match the property name (we ignore case
    213            // which leads to a manageable ambiguity) and that the field and the getter/setter
    214            // are in the same class (i.e., that we don't have a getter exposing a protected field inherted
    215            // from a base class, or some other oddity).
    216
    217            Class cursor = getBeanType();
    218
    219            out:
    220            while (cursor != null)
    221            {
    222                for (Field f : cursor.getDeclaredFields())
    223                {
    224                    if (f.getName().equalsIgnoreCase(name))
    225                    {
    226                        providers.add(new AccessableObjectAnnotationProvider(f));
    227
    228                        break out;
    229                    }
    230                }
    231
    232                cursor = cursor.getSuperclass();
    233            }
    234
    235            annotationProvider = AnnotationProviderChain.create(providers);
    236        }
    237
    238        return annotationProvider;
    239    }
    240
    241    @Override
    242    public boolean isCastRequired()
    243    {
    244        return castRequired;
    245    }
    246
    247    @Override
    248    public ClassPropertyAdapter getClassAdapter()
    249    {
    250        return classAdapter;
    251    }
    252
    253    @Override
    254    public Class getBeanType()
    255    {
    256        return classAdapter.getBeanType();
    257    }
    258
    259    @Override
    260    public boolean isField()
    261    {
    262        return field != null;
    263    }
    264
    265    @Override
    266    public Field getField()
    267    {
    268        return field;
    269    }
    270
    271    @Override
    272    public Class getDeclaringClass()
    273    {
    274        return declaringClass;
    275    }
    276}