001// Copyright 2006, 2007, 2008, 2010, 2011, 2012 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 static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap; 018 019import java.beans.PropertyDescriptor; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Field; 022import java.lang.reflect.Method; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 027import org.apache.tapestry5.ioc.internal.util.GenericsUtils; 028import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; 029import org.apache.tapestry5.ioc.services.ClassPropertyAdapter; 030import org.apache.tapestry5.ioc.services.PropertyAdapter; 031 032public class ClassPropertyAdapterImpl implements ClassPropertyAdapter 033{ 034 private final Map<String, PropertyAdapter> adapters = newCaseInsensitiveMap(); 035 036 private final Class beanType; 037 038 public ClassPropertyAdapterImpl(Class beanType, List<PropertyDescriptor> descriptors) 039 { 040 this.beanType = beanType; 041 042 // lazy init 043 Map<String, List<Method>> nonBridgeMethods = null; 044 045 for (PropertyDescriptor pd : descriptors) 046 { 047 // Indexed properties will have a null propertyType (and a non-null 048 // indexedPropertyType). We ignore indexed properties. 049 050 String name = pd.getName(); 051 052 if (adapters.containsKey(name)) 053 { 054 continue; 055 } 056 057 final Class<?> thisPropertyType = pd.getPropertyType(); 058 if (thisPropertyType == null) 059 continue; 060 061 Method readMethod = pd.getReadMethod(); 062 Method writeMethod = pd.getWriteMethod(); 063 064 // TAP5-1493 065 if (readMethod != null && readMethod.isBridge()) 066 { 067 if (nonBridgeMethods == null) 068 { 069 nonBridgeMethods = groupNonBridgeMethodsByName(beanType); 070 } 071 readMethod = findMethodWithSameNameAndParamCount(readMethod, nonBridgeMethods); 072 } 073 074 // TAP5-1548, TAP5-1885: trying to find a getter which Introspector missed 075 if (readMethod == null) { 076 final String prefix = thisPropertyType != boolean.class ? "get" : "is"; 077 try 078 { 079 Method method = beanType.getMethod(prefix + capitalize(name)); 080 final Class<?> returnType = method.getReturnType(); 081 if (returnType.equals(thisPropertyType) || returnType.isInstance(thisPropertyType)) { 082 readMethod = method; 083 } 084 } 085 catch (SecurityException e) { 086 // getter not usable. 087 } 088 catch (NoSuchMethodException e) 089 { 090 // getter doesn't exist. 091 } 092 } 093 094 if (writeMethod != null && writeMethod.isBridge()) 095 { 096 if (nonBridgeMethods == null) 097 { 098 nonBridgeMethods = groupNonBridgeMethodsByName(beanType); 099 } 100 writeMethod = findMethodWithSameNameAndParamCount(writeMethod, nonBridgeMethods); 101 } 102 103 // TAP5-1548, TAP5-1885: trying to find a setter which Introspector missed 104 if (writeMethod == null) { 105 try 106 { 107 Method method = beanType.getMethod("set" + capitalize(name), pd.getPropertyType()); 108 final Class<?> returnType = method.getReturnType(); 109 if (returnType.equals(void.class)) { 110 writeMethod = method; 111 } 112 } 113 catch (SecurityException e) { 114 // setter not usable. 115 } 116 catch (NoSuchMethodException e) 117 { 118 // setter doesn't exist. 119 } 120 } 121 122 Class propertyType = readMethod == null ? thisPropertyType : GenericsUtils.extractGenericReturnType( 123 beanType, readMethod); 124 125 PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, readMethod, writeMethod); 126 127 adapters.put(pa.getName(), pa); 128 } 129 130 // Now, add any public fields (even if static) that do not conflict 131 132 for (Field f : beanType.getFields()) 133 { 134 String name = f.getName(); 135 136 if (!adapters.containsKey(name)) 137 { 138 Class propertyType = GenericsUtils.extractGenericFieldType(beanType, f); 139 PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, f); 140 141 adapters.put(name, pa); 142 } 143 } 144 } 145 146 private static String capitalize(String name) 147 { 148 return Character.toUpperCase(name.charAt(0)) + name.substring(1); 149 } 150 151 /** 152 * Find a replacement for the method (if one exists) 153 * @param method A method 154 * @param groupedMethods Methods mapped by name 155 * @return A method from groupedMethods with the same name / param count 156 * (default to providedmethod if none found) 157 */ 158 private Method findMethodWithSameNameAndParamCount(Method method, Map<String, List<Method>> groupedMethods) { 159 List<Method> methodGroup = groupedMethods.get(method.getName()); 160 if (methodGroup != null) 161 { 162 for (Method nonBridgeMethod : methodGroup) 163 { 164 if (nonBridgeMethod.getParameterTypes().length == method.getParameterTypes().length) 165 { 166 // return the non-bridge method with the same name / argument count 167 return nonBridgeMethod; 168 } 169 } 170 } 171 172 // default to the provided method 173 return method; 174 } 175 176 /** 177 * Find all of the public methods that are not bridge methods and 178 * group them by method name 179 * 180 * {@see Method#isBridge()} 181 * @param type Bean type 182 * @return 183 */ 184 private Map<String, List<Method>> groupNonBridgeMethodsByName(Class type) 185 { 186 Map<String, List<Method>> methodGroupsByName = CollectionFactory.newMap(); 187 for (Method method : type.getMethods()) 188 { 189 if (!method.isBridge()) 190 { 191 List<Method> methodGroup = methodGroupsByName.get(method.getName()); 192 if (methodGroup == null) 193 { 194 methodGroup = CollectionFactory.newList(); 195 methodGroupsByName.put(method.getName(), methodGroup); 196 } 197 methodGroup.add(method); 198 } 199 } 200 return methodGroupsByName; 201 } 202 203 @Override 204 public Class getBeanType() 205 { 206 return beanType; 207 } 208 209 @Override 210 public String toString() 211 { 212 String names = InternalCommonsUtils.joinSorted(adapters.keySet()); 213 214 return String.format("<ClassPropertyAdaptor %s: %s>", beanType.getName(), names); 215 } 216 217 @Override 218 public List<String> getPropertyNames() 219 { 220 return InternalCommonsUtils.sortedKeys(adapters); 221 } 222 223 @Override 224 public PropertyAdapter getPropertyAdapter(String name) 225 { 226 return adapters.get(name); 227 } 228 229 @Override 230 public Object get(Object instance, String propertyName) 231 { 232 return adaptorFor(propertyName).get(instance); 233 } 234 235 @Override 236 public void set(Object instance, String propertyName, Object value) 237 { 238 adaptorFor(propertyName).set(instance, value); 239 } 240 241 @Override 242 public Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass) { 243 return adaptorFor(propertyName).getAnnotation(annotationClass); 244 } 245 246 private PropertyAdapter adaptorFor(String name) 247 { 248 PropertyAdapter pa = adapters.get(name); 249 250 if (pa == null) 251 throw new IllegalArgumentException(ServiceMessages.noSuchProperty(beanType, name)); 252 253 return pa; 254 } 255 256}