// Copyright 2006, 2007, 2008, 2010, 2011, 2012 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.apache.tapestry5.ioc.internal.services; import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.internal.util.GenericsUtils; import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; import org.apache.tapestry5.ioc.services.ClassPropertyAdapter; import org.apache.tapestry5.ioc.services.PropertyAdapter; public class ClassPropertyAdapterImpl implements ClassPropertyAdapter { private final Map<String, PropertyAdapter> adapters = newCaseInsensitiveMap(); private final Class beanType; public ClassPropertyAdapterImpl(Class beanType, List<PropertyDescriptor> descriptors) { this.beanType = beanType; // lazy init Map<String, List<Method>> nonBridgeMethods = null; for (PropertyDescriptor pd : descriptors) { // Indexed properties will have a null propertyType (and a non-null // indexedPropertyType). We ignore indexed properties. final Class<?> thisPropertyType = pd.getPropertyType(); if (thisPropertyType == null) continue; Method readMethod = pd.getReadMethod(); Method writeMethod = pd.getWriteMethod(); // TAP5-1493 if (readMethod != null && readMethod.isBridge()) { if (nonBridgeMethods == null) { nonBridgeMethods = groupNonBridgeMethodsByName(beanType); } readMethod = findMethodWithSameNameAndParamCount(readMethod, nonBridgeMethods); } // TAP5-1548, TAP5-1885: trying to find a getter which Introspector missed if (readMethod == null) { final String prefix = thisPropertyType != boolean.class ? "get" : "is"; try { Method method = beanType.getMethod(prefix + capitalize(pd.getName())); final Class<?> returnType = method.getReturnType(); if (returnType.equals(thisPropertyType) || returnType.isInstance(thisPropertyType)) { readMethod = method; } } catch (SecurityException e) { // getter not usable. } catch (NoSuchMethodException e) { // getter doesn't exist. } } if (writeMethod != null && writeMethod.isBridge()) { if (nonBridgeMethods == null) { nonBridgeMethods = groupNonBridgeMethodsByName(beanType); } writeMethod = findMethodWithSameNameAndParamCount(writeMethod, nonBridgeMethods); } // TAP5-1548, TAP5-1885: trying to find a setter which Introspector missed if (writeMethod == null) { try { Method method = beanType.getMethod("set" + capitalize(pd.getName()), pd.getPropertyType()); final Class<?> returnType = method.getReturnType(); if (returnType.equals(void.class)) { writeMethod = method; } } catch (SecurityException e) { // setter not usable. } catch (NoSuchMethodException e) { // setter doesn't exist. } } Class propertyType = readMethod == null ? thisPropertyType : GenericsUtils.extractGenericReturnType( beanType, readMethod); PropertyAdapter pa = new PropertyAdapterImpl(this, pd.getName(), propertyType, readMethod, writeMethod); adapters.put(pa.getName(), pa); } // Now, add any public fields (even if static) that do not conflict for (Field f : beanType.getFields()) { String name = f.getName(); if (!adapters.containsKey(name)) { Class propertyType = GenericsUtils.extractGenericFieldType(beanType, f); PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, f); adapters.put(name, pa); } } } private static String capitalize(String name) { return Character.toUpperCase(name.charAt(0)) + name.substring(1); } /** * Find a replacement for the method (if one exists) * @param method A method * @param groupedMethods Methods mapped by name * @return A method from groupedMethods with the same name / param count * (default to providedmethod if none found) */ private Method findMethodWithSameNameAndParamCount(Method method, Map<String, List<Method>> groupedMethods) { List<Method> methodGroup = groupedMethods.get(method.getName()); if (methodGroup != null) { for (Method nonBridgeMethod : methodGroup) { if (nonBridgeMethod.getParameterTypes().length == method.getParameterTypes().length) { // return the non-bridge method with the same name / argument count return nonBridgeMethod; } } } // default to the provided method return method; } /** * Find all of the public methods that are not bridge methods and * group them by method name * * {@see Method#isBridge()} * @param type Bean type * @return */ private Map<String, List<Method>> groupNonBridgeMethodsByName(Class type) { Map<String, List<Method>> methodGroupsByName = CollectionFactory.newMap(); for (Method method : type.getMethods()) { if (!method.isBridge()) { List<Method> methodGroup = methodGroupsByName.get(method.getName()); if (methodGroup == null) { methodGroup = CollectionFactory.newList(); methodGroupsByName.put(method.getName(), methodGroup); } methodGroup.add(method); } } return methodGroupsByName; } @Override public Class getBeanType() { return beanType; } @Override public String toString() { String names = InternalCommonsUtils.joinSorted(adapters.keySet()); return String.format("<ClassPropertyAdaptor %s: %s>", beanType.getName(), names); } @Override public List<String> getPropertyNames() { return InternalCommonsUtils.sortedKeys(adapters); } @Override public PropertyAdapter getPropertyAdapter(String name) { return adapters.get(name); } @Override public Object get(Object instance, String propertyName) { return adaptorFor(propertyName).get(instance); } @Override public void set(Object instance, String propertyName, Object value) { adaptorFor(propertyName).set(instance, value); } @Override public Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass) { return adaptorFor(propertyName).getAnnotation(annotationClass); } private PropertyAdapter adaptorFor(String name) { PropertyAdapter pa = adapters.get(name); if (pa == null) throw new IllegalArgumentException(ServiceMessages.noSuchProperty(beanType, name)); return pa; } }