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