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