/*
* Copyright 2010, 2011 Chris Pheby
*
* 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.jadira.bindings.core.provider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jadira.bindings.core.annotation.BindingScope;
import org.jadira.bindings.core.annotation.DefaultBinding;
import org.jadira.bindings.core.binder.ConverterKey;
import org.jadira.bindings.core.spi.ConverterProvider;
import org.jadira.bindings.core.utils.reflection.TypeHelper;
/**
* Used to implement a binding provider instance that uses annotations to match methods
* @param <T> The annotation used to identify a to method
* @param <F> The annotation used to identify a from method
*/
public class AbstractAnnotationMatchingConverterProvider<T extends Annotation, F extends Annotation> implements ConverterProvider {
public <I,O> Map<ConverterKey<?, ?>, Method> matchToMethods(Class<?> cls) {
Map<ConverterKey<?, ?>, Method> matchedMethods = new HashMap<ConverterKey<?, ?>, Method>();
@SuppressWarnings("unchecked")
Class<T> toAnnotation = (Class<T>) TypeHelper.getTypeArguments(
AbstractAnnotationMatchingConverterProvider.class,
this.getClass()).get(0);
Class<?> loopCls = cls;
while (loopCls != Object.class) {
Method[] methods = loopCls.getDeclaredMethods();
for (Method method : methods) {
if (signatureIndicatesToMethodCandidate(method)) {
T toMethodAnnotation = method.getAnnotation(toAnnotation);
if (toMethodAnnotation != null) {
List<Class<? extends Annotation>> qualifiers = determineQualifiers(toMethodAnnotation, method.getAnnotations());
for (Class<? extends Annotation> nextQualifier : qualifiers) {
@SuppressWarnings("unchecked")
Class<O> returnType = (Class<O>)method.getReturnType();
if (Modifier.isStatic(method.getModifiers())) {
@SuppressWarnings("unchecked")
Class<I> inputClass = (Class<I>) method.getParameterTypes()[0];
matchedMethods.put(new ConverterKey<I,O>(inputClass, returnType, nextQualifier), method);
} else {
@SuppressWarnings("unchecked")
Class<I> inputClass = (Class<I>) cls;
matchedMethods.put(new ConverterKey<I,O>(inputClass, returnType, nextQualifier), method);
}
}
}
}
}
loopCls = loopCls.getSuperclass();
}
return matchedMethods;
}
private boolean signatureIndicatesToMethodCandidate(Method method) {
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (method.getReturnType().equals(Void.TYPE)) {
return false;
}
if (!isToMatch(method)) {
return false;
}
if ((!(Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 1))
&&
(!(!Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 0))) {
return false;
}
return true;
}
public <I,O> Map<ConverterKey<?,?>, Constructor<O>> matchFromConstructors(Class<O> cls) {
Map<ConverterKey<?, ?>, Constructor<O>> matchedConstructors = new HashMap<ConverterKey<?, ?>, Constructor<O>>();
@SuppressWarnings("unchecked")
Class<F> fromAnnotation = (Class<F>) TypeHelper.getTypeArguments(
AbstractAnnotationMatchingConverterProvider.class,
this.getClass()).get(1);
Class<?> loopCls = cls;
while (loopCls != Object.class) {
@SuppressWarnings("unchecked")
Constructor<O>[] constructors = (Constructor<O>[])loopCls.getDeclaredConstructors();
for (Constructor<O> constructor : constructors) {
if (signatureIndicatesFromConstructorCandidate(constructor)) {
F fromConstructorAnnotation = constructor.getAnnotation(fromAnnotation);
if (fromConstructorAnnotation != null) {
List<Class<? extends Annotation>> qualifiers = determineQualifiers(fromConstructorAnnotation, constructor.getAnnotations());
for (Class<? extends Annotation> nextQualifier : qualifiers) {
@SuppressWarnings("unchecked")
Class<I> paramType = (Class<I>)constructor.getParameterTypes()[0];
matchedConstructors.put(new ConverterKey<I,O>((Class<I>)paramType, cls, nextQualifier), constructor);
}
}
}
}
loopCls = loopCls.getSuperclass();
}
return matchedConstructors;
}
private boolean signatureIndicatesFromConstructorCandidate(Constructor<?> constructor) {
if (!Modifier.isPublic(constructor.getModifiers())) {
return false;
}
if (!(constructor.getParameterTypes().length == 1)) {
return false;
}
if (!isFromMatch(constructor)) {
return false;
}
return true;
}
public <I,O> Map<ConverterKey<?,?>, Method> matchFromMethods(Class<?> cls) {
Map<ConverterKey<?, ?>, Method> matchedMethods = new HashMap<ConverterKey<?, ?>, Method>();
@SuppressWarnings("unchecked")
Class<F> fromAnnotation = (Class<F>) TypeHelper.getTypeArguments(
AbstractAnnotationMatchingConverterProvider.class,
this.getClass()).get(1);
Class<?> loopCls = cls;
while (loopCls != Object.class) {
Method[] methods = loopCls.getDeclaredMethods();
for (Method method : methods) {
if (signatureIndicatesFromMethodCandidate(method)) {
F fromMethodAnnotation = method.getAnnotation(fromAnnotation);
if (fromMethodAnnotation != null) {
List<Class<? extends Annotation>> qualifiers = determineQualifiers(fromMethodAnnotation, method.getAnnotations());
for (Class<? extends Annotation> nextQualifier : qualifiers) {
@SuppressWarnings("unchecked")
Class<I> paramType = (Class<I>)method.getParameterTypes()[0];
@SuppressWarnings("unchecked")
Class<O> outputClass = (Class<O>)cls;
matchedMethods.put(new ConverterKey<I,O>(paramType, outputClass, nextQualifier), method);
}
}
}
}
loopCls = loopCls.getSuperclass();
}
return matchedMethods;
}
private boolean signatureIndicatesFromMethodCandidate(Method method) {
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (method.getReturnType().equals(Void.TYPE)) {
return false;
}
if (!(Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 1)) {
return false;
}
if (!isFromMatch(method)) {
return false;
}
return true;
}
/**
* Subclasses can override this template method with their own matching strategy
* @param method The method to be determined
* @return True if match
*/
protected boolean isToMatch(Method method) {
return true;
}
/**
* Subclasses can override this template method with their own matching strategy
* @param constructor The constructor to be determined
* @return True if match
*/
protected boolean isFromMatch(Constructor<?> constructor) {
return true;
}
/**
* Subclasses can override this template method with their own matching strategy
* @param method The method to be determined
* @return True if match
*/
protected boolean isFromMatch(Method method) {
return true;
}
/**
* Returns the qualifiers for this method, setting the Default qualifier if none are found
* Qualifiers can be either explicitly applied to the method, or implicit on account of being found
* in the annotation itself (for example @Plus would have a default binding scope of @PlusOperator)
* @param bindingAnnotation The Annotation for the binding to check if it specifies a binding scope.
* @param allAnnotations Array of Annotations on the target method to check if they specify a scope
* @return True if method can be matched for the given scope
*/
protected List<Class<? extends Annotation>> determineQualifiers(Annotation bindingAnnotation, Annotation... allAnnotations) {
List<Class<? extends Annotation>> result = new ArrayList<Class<? extends Annotation>>();
// The binding annotation itself is marked with @BindingScope
Class<? extends Annotation> bindingAnnotationType = bindingAnnotation.annotationType();
if (bindingAnnotationType.getAnnotation(BindingScope.class) != null) {
result.add(bindingAnnotationType);
}
// The binding annotation is annotated with annotations marked with @BindingScope
for (Annotation next : bindingAnnotation.annotationType().getAnnotations()) {
Class<? extends Annotation> nextType = next.annotationType();
if (nextType.getAnnotation(BindingScope.class) != null) {
result.add(nextType);
}
}
// The method used to attach the annotation is annotated with annotations marked with @BindingScope
for (Annotation next : allAnnotations) {
Class<? extends Annotation> nextType = next.annotationType();
if (nextType.getAnnotation(BindingScope.class) != null) {
result.add(nextType);
}
}
if (result.isEmpty()) {
result.add(DefaultBinding.class);
}
return result;
}
}