blob: 8164ec7e6e377e5324383d1d7d12b4075079f716 [file] [log] [blame]
// 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.
String name = pd.getName();
if (adapters.containsKey(name))
{
continue;
}
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(name));
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(name), 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, name, 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;
}
}