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