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}