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