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 }