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