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    
015    package org.apache.tapestry5.ioc.internal.services;
016    
017    import org.apache.tapestry5.ioc.AnnotationProvider;
018    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019    import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
020    import org.apache.tapestry5.ioc.services.PropertyAdapter;
021    
022    import java.lang.annotation.Annotation;
023    import java.lang.reflect.*;
024    import java.util.List;
025    
026    public 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    }