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 public String getName() 082 { 083 return name; 084 } 085 086 public Method getReadMethod() 087 { 088 return readMethod; 089 } 090 091 public Class getType() 092 { 093 return type; 094 } 095 096 public Method getWriteMethod() 097 { 098 return writeMethod; 099 } 100 101 public boolean isRead() 102 { 103 return field != null || readMethod != null; 104 } 105 106 public boolean isUpdate() 107 { 108 return writeMethod != null || (field != null && !isFinal(field)); 109 } 110 111 private boolean isFinal(Member member) 112 { 113 return Modifier.isFinal(member.getModifiers()); 114 } 115 116 public Object get(Object instance) 117 { 118 if (field == null && readMethod == null) 119 { 120 throw new UnsupportedOperationException(String.format("Class %s does not provide an accessor ('getter') method for property '%s'.", toClassName(instance), name)); 121 } 122 123 Throwable fail; 124 125 try 126 { 127 if (field == null) 128 return readMethod.invoke(instance); 129 else 130 return field.get(instance); 131 } catch (InvocationTargetException ex) 132 { 133 fail = ex.getTargetException(); 134 } catch (Exception ex) 135 { 136 fail = ex; 137 } 138 139 throw new RuntimeException(ServiceMessages.readFailure(name, instance, fail), fail); 140 } 141 142 public void set(Object instance, Object value) 143 { 144 if (field == null && writeMethod == null) 145 { 146 throw new UnsupportedOperationException(String.format("Class %s does not provide a mutator ('setter') method for property '%s'.", 147 toClassName(instance), 148 name 149 )); 150 } 151 152 Throwable fail; 153 154 try 155 { 156 if (field == null) 157 writeMethod.invoke(instance, value); 158 else 159 field.set(instance, value); 160 161 return; 162 } catch (InvocationTargetException ex) 163 { 164 fail = ex.getTargetException(); 165 } catch (Exception ex) 166 { 167 fail = ex; 168 } 169 170 throw new RuntimeException(String.format("Error updating property '%s' of %s: %s", 171 name, toClassName(instance), 172 ExceptionUtils.toMessage(fail)), fail); 173 } 174 175 private String toClassName(Object instance) 176 { 177 return instance == null ? "<null>" : instance.getClass().getName(); 178 } 179 180 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 181 { 182 return getAnnnotationProvider().getAnnotation(annotationClass); 183 } 184 185 /** 186 * Creates (as needed) the annotation provider for this property. 187 */ 188 private synchronized AnnotationProvider getAnnnotationProvider() 189 { 190 if (annotationProvider == null) 191 { 192 List<AnnotationProvider> providers = CollectionFactory.newList(); 193 194 if (readMethod != null) 195 providers.add(new AccessableObjectAnnotationProvider(readMethod)); 196 197 if (writeMethod != null) 198 providers.add(new AccessableObjectAnnotationProvider(writeMethod)); 199 200 // There's an assumption here, that the fields match the property name (we ignore case 201 // which leads to a manageable ambiguity) and that the field and the getter/setter 202 // are in the same class (i.e., that we don't have a getter exposing a protected field inherted 203 // from a base class, or some other oddity). 204 205 Class cursor = getBeanType(); 206 207 out: 208 while (cursor != null) 209 { 210 for (Field f : cursor.getDeclaredFields()) 211 { 212 if (f.getName().equalsIgnoreCase(name)) 213 { 214 providers.add(new AccessableObjectAnnotationProvider(f)); 215 216 break out; 217 } 218 } 219 220 cursor = cursor.getSuperclass(); 221 } 222 223 annotationProvider = AnnotationProviderChain.create(providers); 224 } 225 226 return annotationProvider; 227 } 228 229 public boolean isCastRequired() 230 { 231 return castRequired; 232 } 233 234 public ClassPropertyAdapter getClassAdapter() 235 { 236 return classAdapter; 237 } 238 239 public Class getBeanType() 240 { 241 return classAdapter.getBeanType(); 242 } 243 244 public boolean isField() 245 { 246 return field != null; 247 } 248 249 public Field getField() 250 { 251 return field; 252 } 253 254 public Class getDeclaringClass() 255 { 256 return declaringClass; 257 } 258}