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