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}