001package org.junit.experimental.theories;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.Constructor;
005import java.lang.reflect.Method;
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.Collections;
009import java.util.HashMap;
010import java.util.List;
011import java.util.Map;
012
013public class ParameterSignature {
014    
015    private static final Map<Class<?>, Class<?>> CONVERTABLE_TYPES_MAP = buildConvertableTypesMap();
016    
017    private static Map<Class<?>, Class<?>> buildConvertableTypesMap() {
018        Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();
019
020        putSymmetrically(map, boolean.class, Boolean.class);
021        putSymmetrically(map, byte.class, Byte.class);
022        putSymmetrically(map, short.class, Short.class);
023        putSymmetrically(map, char.class, Character.class);
024        putSymmetrically(map, int.class, Integer.class);
025        putSymmetrically(map, long.class, Long.class);
026        putSymmetrically(map, float.class, Float.class);
027        putSymmetrically(map, double.class, Double.class);
028
029        return Collections.unmodifiableMap(map);
030    }
031    
032    private static <T> void putSymmetrically(Map<T, T> map, T a, T b) {
033        map.put(a, b);
034        map.put(b, a);
035    }
036    
037    public static ArrayList<ParameterSignature> signatures(Method method) {
038        return signatures(method.getParameterTypes(), method
039                .getParameterAnnotations());
040    }
041
042    public static List<ParameterSignature> signatures(Constructor<?> constructor) {
043        return signatures(constructor.getParameterTypes(), constructor
044                .getParameterAnnotations());
045    }
046
047    private static ArrayList<ParameterSignature> signatures(
048            Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
049        ArrayList<ParameterSignature> sigs = new ArrayList<ParameterSignature>();
050        for (int i = 0; i < parameterTypes.length; i++) {
051            sigs.add(new ParameterSignature(parameterTypes[i],
052                    parameterAnnotations[i]));
053        }
054        return sigs;
055    }
056
057    private final Class<?> type;
058
059    private final Annotation[] annotations;
060
061    private ParameterSignature(Class<?> type, Annotation[] annotations) {
062        this.type = type;
063        this.annotations = annotations;
064    }
065
066    public boolean canAcceptValue(Object candidate) {
067        return (candidate == null) ? !type.isPrimitive() : canAcceptType(candidate.getClass());
068    }
069
070    public boolean canAcceptType(Class<?> candidate) {
071        return type.isAssignableFrom(candidate) ||
072                isAssignableViaTypeConversion(type, candidate);
073    }
074    
075    public boolean canPotentiallyAcceptType(Class<?> candidate) {
076        return candidate.isAssignableFrom(type) ||
077                isAssignableViaTypeConversion(candidate, type) ||
078                canAcceptType(candidate);
079    }
080
081    private boolean isAssignableViaTypeConversion(Class<?> targetType, Class<?> candidate) {
082        if (CONVERTABLE_TYPES_MAP.containsKey(candidate)) {
083            Class<?> wrapperClass = CONVERTABLE_TYPES_MAP.get(candidate);
084            return targetType.isAssignableFrom(wrapperClass);
085        } else {
086            return false;
087        }
088    }
089
090    public Class<?> getType() {
091        return type;
092    }
093
094    public List<Annotation> getAnnotations() {
095        return Arrays.asList(annotations);
096    }
097
098    public boolean hasAnnotation(Class<? extends Annotation> type) {
099        return getAnnotation(type) != null;
100    }
101
102    public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) {
103        Annotation[] annotations2 = annotations;
104        return findDeepAnnotation(annotations2, annotationType, 3);
105    }
106
107    private <T extends Annotation> T findDeepAnnotation(
108            Annotation[] annotations, Class<T> annotationType, int depth) {
109        if (depth == 0) {
110            return null;
111        }
112        for (Annotation each : annotations) {
113            if (annotationType.isInstance(each)) {
114                return annotationType.cast(each);
115            }
116            Annotation candidate = findDeepAnnotation(each.annotationType()
117                    .getAnnotations(), annotationType, depth - 1);
118            if (candidate != null) {
119                return annotationType.cast(candidate);
120            }
121        }
122
123        return null;
124    }
125
126    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
127        for (Annotation each : getAnnotations()) {
128            if (annotationType.isInstance(each)) {
129                return annotationType.cast(each);
130            }
131        }
132        return null;
133    }
134}