/*************************************************************************** * Copyright 2009-2012 by Christian Ihle * * kontakt@usikkert.net * * * * This file is part of KouInject. * * * * KouInject is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 3 of * * the License, or (at your option) any later version. * * * * KouInject is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with KouInject. * * If not, see <http://www.gnu.org/licenses/>. * ***************************************************************************/ package net.usikkert.kouinject.util; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.inject.Provider; import net.usikkert.kouinject.AnnotationBasedQualifierHandler; import net.usikkert.kouinject.CollectionProvider; import net.usikkert.kouinject.beandata.BeanKey; import net.usikkert.kouinject.beandata.CollectionBeanKey; import net.usikkert.kouinject.beandata.CollectionProviderBeanKey; import net.usikkert.kouinject.beandata.ProviderBeanKey; import net.usikkert.kouinject.generics.GenericsHelper; import net.usikkert.kouinject.generics.InputBasedTypeLiteral; import net.usikkert.kouinject.generics.TypeLiteral; import net.usikkert.kouinject.generics.TypeMap; import org.apache.commons.lang.Validate; /** * Helper methods for extracting meta-data from beans, using reflection. * * @author Christian Ihle */ public class BeanHelper { private final AnnotationBasedQualifierHandler qualifierHandler = new AnnotationBasedQualifierHandler(); /** * Finds the return type of the factory method. The return type includes the qualifier on the method. * * @param factoryMethod The factory method to find the return type of. * @param typeMap The type map to use to resolve actual type parameters when encountering type variables. * @return The return type, as a {@link BeanKey}. */ public BeanKey findFactoryReturnType(final Method factoryMethod, final TypeMap typeMap) { Validate.notNull(factoryMethod, "Factory method can not be null"); Validate.notNull(typeMap, "Type map can not be null"); final Type genericReturnType = factoryMethod.getGenericReturnType(); if (genericReturnType.equals(Void.TYPE)) { throw new UnsupportedOperationException("Can't return void from a factory method: " + factoryMethod); } final Type wrappedType = GenericsHelper.wrapTypeAndReplaceTypeVariables(genericReturnType, typeMap); final Annotation[] annotations = factoryMethod.getAnnotations(); final String qualifier = qualifierHandler.getQualifier(factoryMethod, annotations); final TypeLiteral<?> beanType = new InputBasedTypeLiteral(wrappedType); return new BeanKey(beanType, qualifier); } /** * Gets all the parameters of a method, with associated qualifiers, as a list of bean keys. * * @param method The method to find the parameters of. * @param typeMap The type map to use to resolve actual type parameters when encountering type variables. * @return The methods parameters as bean keys. */ public List<BeanKey> findParameterKeys(final Method method, final TypeMap typeMap) { Validate.notNull(method, "Method can not be null"); Validate.notNull(typeMap, "Type map can not be null"); final Class<?>[] parameterTypes = method.getParameterTypes(); final Type[] genericParameterTypes = method.getGenericParameterTypes(); final Annotation[][] parameterAnnotations = method.getParameterAnnotations(); return findParameterKeys(method, parameterTypes, genericParameterTypes, parameterAnnotations, typeMap); } /** * Gets all the parameters of a constructor, with associated qualifiers, as a list of bean keys. * * @param constructor The constructor to find the parameters of. * @param typeMap The type map to use to resolve actual type parameters when encountering type variables. * @return The constructor parameters as bean keys. */ public List<BeanKey> findParameterKeys(final Constructor<?> constructor, final TypeMap typeMap) { Validate.notNull(constructor, "Constructor can not be null"); Validate.notNull(typeMap, "Type map can not be null"); final Class<?>[] parameterTypes = constructor.getParameterTypes(); final Type[] genericParameterTypes = constructor.getGenericParameterTypes(); final Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); return findParameterKeys(constructor, parameterTypes, genericParameterTypes, parameterAnnotations, typeMap); } /** * Gets the type from the field, with associated qualifier, as a bean key. * * @param field The field to find the type of. * @param typeMap The type map to use to resolve actual type parameters when encountering type variables. * @return The field as a bean key. */ public BeanKey findFieldKey(final Field field, final TypeMap typeMap) { Validate.notNull(field, "Field can not be null"); Validate.notNull(typeMap, "Type map can not be null"); final Class<?> type = field.getType(); final Type genericType = field.getGenericType(); final Type wrappedType = GenericsHelper.wrapTypeAndReplaceTypeVariables(genericType, typeMap); final Annotation[] annotations = field.getAnnotations(); return findParameterKey(field, type, wrappedType, annotations); } private List<BeanKey> findParameterKeys(final Object parameterOwner, final Class<?>[] parameterTypes, final Type[] genericParameterTypes, final Annotation[][] annotations, final TypeMap typeMap) { final List<BeanKey> parameters = new ArrayList<BeanKey>(); for (int i = 0; i < parameterTypes.length; i++) { final Class<?> parameterClass = parameterTypes[i]; final Type parameterType = genericParameterTypes[i]; final Type wrappedType = GenericsHelper.wrapTypeAndReplaceTypeVariables(parameterType, typeMap); final BeanKey parameter = findParameterKey(parameterOwner, parameterClass, wrappedType, annotations[i]); parameters.add(parameter); } return parameters; } private BeanKey findParameterKey(final Object parameterOwner, final Class<?> parameterClass, final Type parameterType, final Annotation[] annotations) { final String qualifier = qualifierHandler.getQualifier(parameterOwner, annotations); if (isProvider(parameterClass)) { final TypeLiteral<?> beanTypeFromProvider = getBeanTypeFromGenericType(parameterOwner, parameterType); return new ProviderBeanKey(beanTypeFromProvider, qualifier); } else if (isCollection(parameterClass)) { final TypeLiteral<?> actualBeanType = new InputBasedTypeLiteral(parameterType); final TypeLiteral<?> beanTypeFromCollection = getBeanTypeFromGenericType(parameterOwner, parameterType); return new CollectionBeanKey(actualBeanType, beanTypeFromCollection, qualifier); } else if (isCollectionProvider(parameterClass)) { final TypeLiteral<?> beanTypeFromCollectionProvider = getBeanTypeFromGenericType(parameterOwner, parameterType); return new CollectionProviderBeanKey(beanTypeFromCollectionProvider, qualifier); } else { final TypeLiteral<?> beanType = new InputBasedTypeLiteral(parameterType); return new BeanKey(beanType, qualifier); } } private TypeLiteral<?> getBeanTypeFromGenericType(final Object parameterOwner, final Type genericParameterType) { if (GenericsHelper.isParameterizedType(genericParameterType)) { final Type genericArgumentAsType = GenericsHelper.getGenericArgumentAsType(genericParameterType); return new InputBasedTypeLiteral(genericArgumentAsType); } throw new IllegalArgumentException("Generic class used without type argument: " + parameterOwner); } private boolean isProvider(final Class<?> parameterType) { return Provider.class.equals(parameterType); } private boolean isCollection(final Class<?> parameterType) { return Collection.class.equals(parameterType); } private boolean isCollectionProvider(final Class<?> parameterType) { return CollectionProvider.class.equals(parameterType); } }