Search This Blog

Loading...

Thursday, December 3, 2009

Advanced type compliance framework

Table of contents




All source code packaged as a Maven project may be downloaded here

It may be convenient to be able to check if one type may be used in place another. That may seem to be a simple task, e.g. it's really easy to see if, say, Long may be used in place of Number but we can go further and check, for example, if '? extends List<Long>' may be used in place of 'Collection<? extends Number>'. Still not scary? Ok, what about '? super List<? extends Number[]>[]' vs '? super Collection<Integer[]>[]'? Hope that shows that the task may be really not trivial. Ok, but what about rationaly behind it - when do we want to use such checks?

One answer to use it within dependency injection containers like spring - it's possible to define property as a collection of the target type and ask container to inject all registered beans that match to the target type. I wrote that spring looks at the raw type of collection argument, i.e. if you ask spring to inject all beans that are Comparable<MyGenericInterface<Integer>> it injects either Comparable<MyGenericInterface<Integer>> or Comparable<MyGenericInterface<String>> beans. It would be really nice if it correctly filter the beans by available generic type information.

So, I created a mini-framework that allows to answer if one type may be used in place of another. My indirect task was to provide decoupled easy maintainable code, so, I decided to group checking rules by one argument type ('base' type). The contract is defined by the following interface:

TypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.Type;

/**
* Defines general contract for checking if particular type may be used in place of another type.
* <p/>
* Implementations of this interface are assumed to be thread-safe.
*
* @author Denis Zhdanov
* @since Nov 3, 2009
* @param <T> target <code>'base'</code> type
*/

public interface TypeComplianceMatcher<T extends Type> {

/**
* Allows to check if <code>'candidate'</code> type may be used in place of <code>'base'</code> type.
* <p/>
* This method is essentially the same as {@link #match(Type, Type, boolean)} called with <code>'false'</code>
* as last argument
*
* @param base base type
* @param candidate candidate type
* @return <code>true</code> if given <code>'candidate'</code> type may be used in place
* of <code>'base'</code> type; <code>false</code> otherwise
*/

boolean match(T base, Type candidate);

/**
* Allows to check if <code>'candidate'</code> type may be used in place of <code>'base'</code> type.
*
* @param base base type
* @param candidate candidate type
* @param strict flag that shows if this is a 'strict' check, e.g. if we compare <code>Integer</code>
* to <code>Number</code> as a part of MyClass<Integer>
* to MyClass<? extends Number> comparison, the check should be non-strict but
* check of MyClass<Integer> to MyClass<Number> should be strict
* @return <code>true</code> if given <code>'candidate'</code> type may be used in place
* of <code>'base'</code> type; <code>false</code> otherwise
*/

boolean match(T base, Type candidate, boolean strict);
}


Next step was to move all processing logic common for all types processing to the single place. Note that I reused type dispatchig facilities introduced here:

AbstractTypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import org.springcontrib.util.generic.TypeDispatcher;
import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.resolver.TypeArgumentResolver;
import org.springcontrib.util.generic.resolver.DefaultTypeArgumentResolver;

import java.lang.reflect.Type;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicReference;

/**
* {@link TypeComplianceMatcher} implementation that is based on GoF <code>'Template Method'</code> pattern.
* <p/>
* I.e. this class defines general algorithm, offers useful facilities for subclasses and requires them to implement
* particular functionality.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since Nov 3, 2009
* @param <T> target <code>'base'</code> type
* @see #match(Type, Type, boolean)
*/

public abstract class AbstractTypeComplianceMatcher<T extends Type> implements TypeComplianceMatcher<T> {

/**
* Holds stack of flags that indicate if <code>'strict'</code> check is performed
* (check {@link #match(Type, Type, boolean)}) contract for more details.
* <p/>
* We use static variable here in order to be able to keep track of <code>'strict'</code> value across
* multiple instances of underlying classes.
*/

private static final ThreadLocal<Stack<Boolean>> STRICT = new ThreadLocal<Stack<Boolean>>();
static {
Stack<Boolean> stack = new Stack<Boolean>();
stack.push(false);
STRICT.set(stack);
}

/**
* Stores <code>'base'</code> type used in comparison. That type is available to actual implementations
* via {@link #getBaseType()} method.
* <p/>
* We use stack of values here in order to be able to handle the situation when the same matcher implementation
* is used more than one during the same type comparison. Example of such a situation is comparison of
* {@code Comparable<Collection<Comparable<? extends Number>>>} vs
* {@code Comparable<Collection<Comparable<Long>>>}. Matcher that works with {@link ParameterizedType} is used
* for different types here ({@link Comparable} and {@link Collection}), so, we need to keep track of base
* type between those comparisons.
*/

private final ThreadLocal<Stack<T>> baseType = new ThreadLocal<Stack<T>>() {
@Override
protected Stack<T> initialValue() {
return new Stack<T>();
}
};
private final ThreadLocal<Boolean> matched = new ThreadLocal<Boolean>();
private final AtomicReference<TypeArgumentResolver> typeArgumentResolver = new AtomicReference<TypeArgumentResolver>();
private final TypeDispatcher typeDispatcher = new TypeDispatcher();

protected AbstractTypeComplianceMatcher() {
matched.set(false);
setTypeArgumentResolver(DefaultTypeArgumentResolver.INSTANCE);
}

/** {@inheritDoc} */
@Override
public boolean match(T base, Type candidate) throws IllegalArgumentException {
return match(base, candidate, false);
}

/**
* Template method that defines basic match algorithm:
* <ol>
* <li>
* given <code>'base'</code> type is remembered at thread-local variable and is available
* to subclasses via {@link #getBaseType()};
* </li>
* <li>given <code>'strict'</code> parameter value is exposed to subclasses via {@link #isStrict()} method;</li>
* <li>
* subclass is asked for {@link TypeVisitor} implementation that contains all evaluation
* logic ({@link #getVisitor()}). That logic is assumed to store its processing result
* via {@link #setMatched(boolean)} method. If that method is not called it's assumed that
* result is <code>false</code>;
* </li>
* </ol>
*
* @param base base type
* @param candidate candidate type
* @param strict flag that shows if this is a 'strict' check, e.g. if we compare <code>Integer</code>
* to <code>Number</code> as a part of MyClass<Integer>
* to MyClass<? extends Number> comparison, the check should be non-strict but
* check of MyClass<Integer> to MyClass<Number> should be strict
* @return <code>true</code> if given <code>'candidate'</code> type may be used in place
* of <code>'base'</code> type; <code>false</code> otherwise
*/

@Override
public boolean match(T base, Type candidate, boolean strict) {
if (base == null) {
throw new IllegalArgumentException("Can't process AbstractTypeComplianceMatcher.match(). Reason: given "
+ "'base' argument is null");
}
if (candidate == null) {
throw new IllegalArgumentException("Can't process AbstractTypeComplianceMatcher.match(). Reason: given "
+ "'candidate' argument is null");
}
baseType.get().push(base);
STRICT.get().push(strict);
try {
typeDispatcher.dispatch(candidate, getVisitor());
return isMatched();
} finally {
baseType.get().pop();
matched.set(null);
STRICT.get().pop();
if (STRICT.get().size() == 1) { // We use stack size as an indicator if top-level comparison is done.
cleanup();
}
}
}

/**
* Allows to get type argument resolver to use.
*
* @return type argument resolver to use
*/

public TypeArgumentResolver getTypeArgumentResolver() {
return typeArgumentResolver.get();
}

/**
* Allows to define custom {@link TypeArgumentResolver} to use; {@link DefaultTypeArgumentResolver#INSTANCE}
* is used by default.
*
* @param typeArgumentResolver custom type argument resolver to use
*/

public void setTypeArgumentResolver(TypeArgumentResolver typeArgumentResolver) {
this.typeArgumentResolver.set(typeArgumentResolver);
}

/**
* Assumed to be implemented at subclass and contain actual comparison logic.
* <p/>
* Check {@link #match(Type, Type, boolean)} contract for more details about how the visitor should
* use various processing parameters and store processing result.
*
* @return visitor that contains target comparison logic
*/

protected abstract TypeVisitor getVisitor();

/**
* Allows to retrieve <code>'base'</code> type given to {@link #match(Type, Type, boolean)}
* ({@link #match(Type, Type)}).
*
* @return <code>'base'</code> type given to {@link #match(Type, Type, boolean)} ({@link #match(Type, Type)})
* if this method is called during <code>'match()'</code> method call; <code>null</code> if this
* method is called before or after <code>'match()'</code> call
*/

protected T getBaseType() {
return baseType.get().peek();
}

/**
* Allows to define matching result.
*
* @param matched flag that shows if types are matched
*/

protected void setMatched(boolean matched) {
this.matched.set(matched);
}

/**
* @return <code>'strict'</code> parameter given to {@link #match(Type, Type, boolean)} method
*/

protected boolean isStrict() {
return STRICT.get().peek();
}

/**
* Allows to dispatch given type against given visitor.
* <p/>
* Follows {@link TypeDispatcher#dispatch(Type, TypeVisitor)} contract.
*
* @param type type to dispatch
* @param visitor visitor to use during the dispatching
*/

protected void dispatch(Type type, TypeVisitor visitor) {
typeDispatcher.dispatch(type, visitor);
}

/**
* Callback that can be used at subclasses in order to process the event of initial comparison finish.
* <p/>
* Default implementation (provided by this class) does nothing.
*/

protected void cleanup() {
}

private boolean isMatched() {
Boolean matched = this.matched.get();
return matched == null ? false : matched;
}
}


There is a necessity to be able to delegate the compliance checking task to the target processor. E.g. if we compare Comparable<Integer> to Comparable<Number> we start with the TypeComplianceMatcher that works with the instances of ParameterizedType and delegate the processing to the Class matcher. So, following class is introduced:

AbstractDelegatingTypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.*;

/**
* Expands {@link AbstractTypeComplianceMatcher} in order to add <code>'matcher delegate'</code> concept.
* <p/>
* <code>'Matcher delegate'</code> here means {@link TypeComplianceMatcher} implementation that allows to handle
* compliance rules that are not handled by current <code>AbstractDelegatingTypeComplianceMatcher</code> subclass.
* <p/>
* E.g. suppose that current subclass contains logic that allows to check compliance rules when basic type
* is {@link ParameterizedType}. It's called when we need to check if {@code Comparable<Integer>} can be used
* in place of {@code Comparable<? extends Number>}. So, we check that raw types of given parameterized types
* are consistent and need to check {@code ? extends Number} wildcard type to {@code Integer} class. That logic
* is not contained at current subclass (remember, it contains comparison logic for the cases when base type is
* {@link ParameterizedType} not {@link WildcardType}), so, target type argument values are derived and the processing
* is delegated to another {@link TypeComplianceMatcher} implementation.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since Nov 3, 2009
*/

public abstract class AbstractDelegatingTypeComplianceMatcher<T extends Type> extends AbstractTypeComplianceMatcher<T> {

private final TypeComplianceMatcher<Type> delegate;

/**
* Creates new <code>AbstractDelegatingTypeComplianceMatcher</code> object with default
* delegate ({@link CompositeTypeComplianceMatcher#INSTANCE}).
*/

protected AbstractDelegatingTypeComplianceMatcher() {
this(new CompositeTypeComplianceMatcher());
}

/**
* Creates new <code>AbstractDelegatingTypeComplianceMatcher</code> object with given delegate.
*
* @param delegate delegate to expose via {@link #getDelegate()} method
*/

protected AbstractDelegatingTypeComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
this.delegate = delegate;
}

/**
* Allows to retrieve type compliance matcher delegate to use.
*
* @return type compliance matcher delegate to use
*/

public TypeComplianceMatcher<Type> getDelegate() {
return delegate;
}
}


Uff, the infrastructure is done. Let's build the implementation:

ClassComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.TypeVisitorAdapter;

import java.lang.reflect.*;

/**
* Allows to check if particular class may be matched to particular type.
* <p/>
* Provides actual functionality described at the contract of {@link GenericsHelper#match(Type, Class)} method.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since Oct 28, 2009
*/

public class ClassComplianceMatcher extends AbstractDelegatingTypeComplianceMatcher<Class<?>> {

private final TypeVisitor visitor = new TypeVisitorAdapter() {
@Override
public void visitParameterizedType(ParameterizedType type) {
setMatched(getDelegate().match(getBaseType(), type.getRawType()));
}

@Override
public void visitWildcardType(WildcardType type) {
for (Type upperBoundType : type.getUpperBounds()) {
if (!getDelegate().match(getBaseType(), upperBoundType, true)) {
return;
}
}

setMatched(type.getLowerBounds().length <= 0 || getBaseType() == Object.class);
}

@Override
public void visitGenericArrayType(GenericArrayType type) {
if (!getBaseType().isArray()) {
setMatched(false);
return;
}

setMatched(getDelegate().match(getBaseType().getComponentType(), type.getGenericComponentType(), true));
}

@Override
public void visitTypeVariable(TypeVariable<? extends GenericDeclaration> type) {
// We know that java.lang.Object is returned if no upper bound is defined explicitly.
for (Type upperBoundType : type.getBounds()) {
if (!getDelegate().match(getBaseType(), upperBoundType)) {
return;
}
}
setMatched(true);
}

@Override
public void visitClass(Class<?> clazz) {
boolean matched = isStrict() ? getBaseType() == clazz : getBaseType().isAssignableFrom(clazz);
setMatched(matched);
}
};

public ClassComplianceMatcher() {
}

public ClassComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
super(delegate);
}

/** {@inheritDoc} */
@Override
protected TypeVisitor getVisitor() {
return visitor;
}
}


ClassComplianceMatcherTest.java


package org.springcontrib.util.generic.compliance;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Comparator;

/**
* @author Denis Zhdanov
* @since 10/28/2009
*/

@SuppressWarnings({"UnusedDeclaration"})
public class ClassComplianceMatcherTest {

private ClassComplianceMatcher matcher;

@Before
@SuppressWarnings("unchecked")
public void setUp() throws Exception {
matcher = new ClassComplianceMatcher();
}

@Test
public void toClass() {
assertTrue(matcher.match(Number.class, Integer.class));
assertTrue(matcher.match(Number.class, Number.class));
assertFalse(matcher.match(Number.class, Integer.class, true));
assertFalse(matcher.match(Integer.class, Number.class));
assertFalse(matcher.match(String.class, Number.class));
}

@Test
public void toParameterizedInterfaceType() {
class TestClass implements Comparable<String> {
@Override
public int compareTo(String o) {
return 0;
}
}
Type type = TestClass.class.getGenericInterfaces()[0];
assertTrue(matcher.match(Comparable.class, type));
assertFalse(matcher.match(Serializable.class, type));
}

@Test
public void toUpperBound() throws NoSuchFieldException {
class Test {
public Comparator<? extends Number> field;
}

Type intType = ((ParameterizedType)Test.class.getField("field").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(Integer.class, intType));
assertTrue(matcher.match(Number.class, intType));
assertFalse(matcher.match(Long.class, intType));
assertFalse(matcher.match(String.class, intType));
assertFalse(matcher.match(Object.class, intType));
}

@Test
public void toLowerBound() throws NoSuchFieldException {
class Test {
public Comparator<? super Integer> field1;
public Comparator<? super String> field2;
}

Type intType = ((ParameterizedType)Test.class.getField("field1").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(Integer.class, intType));
assertFalse(matcher.match(Number.class, intType));
assertTrue(matcher.match(Object.class, intType));
assertFalse(matcher.match(Long.class, intType));

Type stringType = ((ParameterizedType)Test.class.getField("field2").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(Integer.class, stringType));
assertFalse(matcher.match(Number.class, stringType));
assertFalse(matcher.match(String.class, stringType));
assertTrue(matcher.match(Object.class, stringType));
}

@Test
public void toBoundTypeVariable() {
class SingleBoundClass<T extends Number> {}
class MultipleBoundClass<T extends Number & CharSequence> {}

Type numberType = SingleBoundClass.class.getTypeParameters()[0];
assertFalse(matcher.match(Integer.class, numberType));
assertTrue(matcher.match(Number.class, numberType));
assertFalse(matcher.match(String.class, numberType));

Type compoundType = MultipleBoundClass.class.getTypeParameters()[0];
assertFalse(matcher.match(Integer.class, compoundType));
assertFalse(matcher.match(Number.class, compoundType));
assertFalse(matcher.match(String.class, compoundType));
}

@Test
public void toUnboundTypeVariable() {
class TestClass<T> {}
Type type = TestClass.class.getTypeParameters()[0];
assertFalse(matcher.match(Integer.class, type));
assertFalse(matcher.match(Number.class, type));
assertFalse(matcher.match(String.class, type));
}

@Test
public void toUnboundGenericArray() {
class TestClass<T> implements Comparable<T[]> {
@Override
public int compareTo(T[] o) {
return 0;
}
}
Type type = ((ParameterizedType)TestClass.class.getGenericInterfaces()[0]).getActualTypeArguments()[0];

assertFalse(matcher.match(Integer.class, type));
assertFalse(matcher.match(Number.class, type));
assertFalse(matcher.match(String.class, type));
assertFalse(matcher.match(Integer[].class, type));
assertFalse(matcher.match(Number[].class, type));
assertFalse(matcher.match(String[].class, type));
}

@Test
public void toBoundGenericArray() {
class TestClass implements Comparable<Number[]> {
@Override
public int compareTo(Number[] o) {
return 0;
}
}
Type type = ((ParameterizedType)TestClass.class.getGenericInterfaces()[0]).getActualTypeArguments()[0];
assertFalse(matcher.match(Integer.class, type));
assertFalse(matcher.match(Number.class, type));
assertFalse(matcher.match(Integer[].class, type));
assertTrue(matcher.match(Number[].class, type));
}
}


GenericArrayTypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.*;

import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.TypeVisitorAdapter;

/**
* @author Denis Zhdanov
* @since Nov 10, 2009
*/

public class GenericArrayTypeComplianceMatcher extends AbstractDelegatingTypeComplianceMatcher<GenericArrayType> {

private final TypeVisitor visitor = new TypeVisitorAdapter() {
@Override
public void visitGenericArrayType(GenericArrayType type) {
setMatched(getDelegate().match(
getBaseType().getGenericComponentType(), type.getGenericComponentType(), isStrict()
));
}
};

public GenericArrayTypeComplianceMatcher() {
}

public GenericArrayTypeComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
super(delegate);
}

/** {@inheritDoc} */
@Override
protected TypeVisitor getVisitor() {
return visitor;
}
}


GenericArrayTypeComplianceMatcherTest.java


package org.springcontrib.util.generic.compliance;

import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.junit.Before;
import org.junit.Test;
import org.springcontrib.util.generic.resolver.TypeArgumentResolver;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

/**
* @author Denis Zhdanov
* @since 11/20/2009
*/

@SuppressWarnings({"UnusedDeclaration"})
public class GenericArrayTypeComplianceMatcherTest {

private GenericArrayTypeComplianceMatcher matcher;
private GenericArrayType numberArrayType = (GenericArrayType)
((ParameterizedType) NumberArrayClass.class.getGenericInterfaces()[0]).getActualTypeArguments()[0];
private GenericArrayType objectArrayType = (GenericArrayType)
((ParameterizedType) ObjectArrayClass.class.getGenericInterfaces()[0]).getActualTypeArguments()[0];

@Before
public void setUp() throws Exception {
matcher = new GenericArrayTypeComplianceMatcher();
}

@Test
public void parameterizedType() {
assertFalse(matcher.match(numberArrayType, NumberArrayClass.class.getGenericInterfaces()[0]));
}

@Test
public void wildcardType() throws NoSuchFieldException {
class TestClass {
public Collection<? extends Number> field1;
public Collection<? extends Number[]> field2;
public Collection<? extends Long[]> field3;
public Collection<? super Long[]> field4;
public Collection<? super Number[]> field5;
public Collection<?> field6;
}

Type lowerToNumber
= ((ParameterizedType)TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(numberArrayType, lowerToNumber));
assertFalse(matcher.match(objectArrayType, lowerToNumber));

Type lowerToNumberArray
= ((ParameterizedType)TestClass.class.getField("field2").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(numberArrayType, lowerToNumberArray));
assertFalse(matcher.match(objectArrayType, lowerToNumberArray));

Type lowerToLongArray
= ((ParameterizedType)TestClass.class.getField("field3").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(numberArrayType, lowerToLongArray));
assertFalse(matcher.match(objectArrayType, lowerToLongArray));

Type upperToLongArray
= ((ParameterizedType)TestClass.class.getField("field4").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(numberArrayType, upperToLongArray));
assertFalse(matcher.match(objectArrayType, upperToLongArray));

Type upperToNumberArray
= ((ParameterizedType)TestClass.class.getField("field5").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(numberArrayType, upperToNumberArray));
assertFalse(matcher.match(objectArrayType, upperToNumberArray));

Type wildcard
= ((ParameterizedType)TestClass.class.getField("field6").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(numberArrayType, wildcard));
assertFalse(matcher.match(objectArrayType, wildcard));
}

@Test
public void toGenericArray() throws NoSuchFieldException {
class TestClass<T> {
public Collection<Integer[]> baseField1;
public Collection<Long[]> baseField2;
public Collection<List<? super Long>[]> baseField3;
public Collection<List<? extends Number[]>[]> baseField4;

public Collection<Integer> candidateField1;
public Collection<Object[]> candidateField2;
public Collection<Number[]> candidateField3;
public Collection<List<? super Number>[]> candidateField4;
public Collection<List<? super Integer>[]> candidateField5;
public Collection<List<Integer[]>[]> candidateField6;
}

doTest(TestClass.class, "baseField1", "candidateField1", false);
doTest(TestClass.class, "baseField1", "candidateField2", false);
doTest(TestClass.class, "baseField2", "candidateField2", false);
doTest(TestClass.class, "baseField2", "candidateField3", false);
doTest(TestClass.class, "baseField3", "candidateField4", true);
doTest(TestClass.class, "baseField3", "candidateField5", false);
doTest(TestClass.class, "baseField4", "candidateField3", false);
doTest(TestClass.class, "baseField4", "candidateField5", false);
doTest(TestClass.class, "baseField4", "candidateField6", true);
}

private void doTest(Class<?> clazz, String baseFieldName, String candidateFieldName, boolean expectedMatch)
throws NoSuchFieldException
{
GenericArrayType genericArrayType = (GenericArrayType) getGenericFieldType(clazz, baseFieldName);
assertEquals(expectedMatch, matcher.match(genericArrayType, getGenericFieldType(clazz, candidateFieldName)));
}

private Type getGenericFieldType(Class<?> clazz, String fieldName) throws NoSuchFieldException {
ParameterizedType parameterizedType = (ParameterizedType) clazz.getField(fieldName).getGenericType();
return parameterizedType.getActualTypeArguments()[0];
}

@Test
public void typeVariable() {
class TestClass<T> {}
assertFalse(matcher.match(numberArrayType, TestClass.class.getTypeParameters()[0]));
assertFalse(matcher.match(objectArrayType, TestClass.class.getTypeParameters()[0]));
}

@Test
public void toClass() {
assertFalse(matcher.match(numberArrayType, Number[].class));
assertFalse(matcher.match(objectArrayType, Number[].class));
}

@Test
public void toType() {
assertFalse(matcher.match(numberArrayType, TypeArgumentResolver.RAW_TYPE));
assertFalse(matcher.match(objectArrayType, TypeArgumentResolver.RAW_TYPE));
}

interface TestInterface<A> {}
class NumberArrayClass implements TestInterface<Number[]> {}
class ObjectArrayClass implements TestInterface<Object[]> {}
}


ParameterizedTypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.*;

import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.TypeVisitorAdapter;

/**
* Allows to check if given type may be used in place of base {@link ParameterizedType}.
*
* @author Denis Zhdanov
* @since Nov 3, 2009
*/

public class ParameterizedTypeComplianceMatcher extends AbstractDelegatingTypeComplianceMatcher<ParameterizedType> {

private final TypeVisitor visitor = new TypeVisitorAdapter() {
@Override
public void visitParameterizedType(ParameterizedType candidateType) {
// Return eagerly if raw types don't match.
if (!getDelegate().match(getBaseType().getRawType(), candidateType.getRawType(), isStrict())) {
return;
}

Type[] baseTypeArguments = getBaseType().getActualTypeArguments();
Type[] candidateTypeArguments = new Type[baseTypeArguments.length];

// Resolve actual type argument types.
for (int i = 0; i < baseTypeArguments.length; ++i) {
if (getBaseType().getRawType() == candidateType.getRawType()) {
candidateTypeArguments[i] = candidateType.getActualTypeArguments()[i];
} else {
candidateTypeArguments[i] = getTypeArgumentResolver().resolve(getBaseType(), candidateType, i);
}
}

// Check type arguments conformance.
for (int i = 0; i < baseTypeArguments.length; ++i) {
// Note that we explicitly set 'strict' to 'true' here because there is no covariance
// for type arguments in java.
boolean result = getDelegate().match(baseTypeArguments[i], candidateTypeArguments[i], true);
if (!result) {
return;
}
}

if (getBaseType().getRawType() == candidateType.getRawType()) {
setMatched(true);
return;
}

// Check that pairs of corresponding arguments conform to each other (1st vs 1st, 2nd vs 2nd etc).
// I.e. there is a possible situation that we have two parameterized types and one of them has the
// same type argument repeated at more than one position and another has different arguments
// (MyType<A, A> vs MyType<X, Y>). We want to consider such types to be inconsistent.
if (!checkTypeArgumentsRepetition(baseTypeArguments, candidateTypeArguments)) {
return;
}

setMatched(true);
}

@Override
public void visitWildcardType(WildcardType wildcardType) {
for (Type type : wildcardType.getUpperBounds()) {
if (!getDelegate().match(getBaseType(), type, true)) {
return;
}
}
setMatched(true);
}

@Override
public void visitClass(Class<?> clazz) {
if (!getDelegate().match(getBaseType().getRawType(), clazz, isStrict())) {
return;
}

Type[] baseTypeArguments = getBaseType().getActualTypeArguments();
Type[] candidateTypeArguments = new Type[baseTypeArguments.length];
for (int i = 0; i < baseTypeArguments.length; ++i) {
candidateTypeArguments[i] = getTypeArgumentResolver().resolve(getBaseType(), clazz, i);
if (!getDelegate().match(baseTypeArguments[i], candidateTypeArguments[i], isStrict())) {
return;
}
}

if (!checkTypeArgumentsRepetition(baseTypeArguments, candidateTypeArguments)) {
return;
}

setMatched(true);
}
};

public ParameterizedTypeComplianceMatcher() {
}

public ParameterizedTypeComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
super(delegate);
}

@Override
protected TypeVisitor getVisitor() {
return visitor;
}

/**
* Allows to check is given type arrays hold the same values at the same positions, i.e. if first type
* holds the same arguments at more than one position, candidate type arguments are the same at the same
* positions, i.e. {@code <A, A, B>} matches to {@code <X, X, Z>} but not to {@code <X, Y, Z>}.
*
* @param first first types array to check
* @param second second types array to check
* @return <code>true</code> if examination is successful; <code>false</code> otherwise
*/

private boolean checkTypeArgumentsRepetition(Type[] first, Type[] second) {
for (int i = 0; i < first.length; ++i) {
for (int j = i + 1; j < first.length; ++j) {
if (first[i] == first[j] && second[i] != second[j]) {
return false;
}
}
}
return true;
}
}


ParameterizedTypeComplianceMatcherTest.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.*;
import java.util.Comparator;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;

/**
* @author Denis Zhdanov
* @since 11/03/2009
*/

@SuppressWarnings({"UnusedDeclaration", "SuppressionAnnotation", "RawUseOfParameterizedType"})
public class ParameterizedTypeComplianceMatcherTest {

private ParameterizedTypeComplianceMatcher matcher;

@Before
public void setUp() throws Exception {
matcher = new ParameterizedTypeComplianceMatcher();
}

@Test
public void toClass() {
class TestClass implements Comparable<Integer> {
@Override
public int compareTo(Integer o) {
return 0;
}
}
class RawClass implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}

assertTrue(matcher.match((ParameterizedType) TestClass.class.getGenericInterfaces()[0], Integer.class));
assertFalse(matcher.match((ParameterizedType) TestClass.class.getGenericInterfaces()[0], Long.class));
assertFalse(matcher.match((ParameterizedType) TestClass.class.getGenericInterfaces()[0], StringBuilder.class));
assertFalse(matcher.match((ParameterizedType) TestClass.class.getGenericInterfaces()[0], RawClass.class));
}

@Test
public void toParameterizedType() {
class SimpleBaseClass implements TestInterface<Integer, Long, String> {}
class SimpleMatchedClass extends TestInterfaceImpl<Integer, Long, String> {}
class SimpleUnmatchedClass extends TestInterfaceImpl<Integer, Long, Number> {}

class ComplexBaseClass implements TestInterface<TestInterface<Integer, Long, Number>, String, Long> {}
class ComplexMatchedClass extends TestInterfaceImpl<TestInterface<Integer, Long, Number>, String, Long> {}
class ComplexUnmatchedClass1 extends TestInterfaceImpl<TestInterface<Integer, Long, String>, String, Long> {}
class ComplexUnmatchedClass2 extends TestInterfaceImpl<TestInterface<Integer, Long, Long>, String, Long> {}
class ComplexUnmatchedClass3 extends TestInterfaceImpl<TestInterface<Integer, Long, Integer>, String, Long> {}

ParameterizedType simpleBaseType = (ParameterizedType) SimpleBaseClass.class.getGenericInterfaces()[0];
assertTrue(matcher.match(simpleBaseType, SimpleMatchedClass.class.getGenericSuperclass()));
assertFalse(matcher.match(simpleBaseType, SimpleUnmatchedClass.class.getGenericSuperclass()));

ParameterizedType complexBaseType = (ParameterizedType) ComplexBaseClass.class.getGenericInterfaces()[0];
assertTrue(matcher.match(complexBaseType, ComplexMatchedClass.class.getGenericSuperclass()));
assertFalse(matcher.match(complexBaseType, ComplexUnmatchedClass1.class.getGenericSuperclass()));
assertFalse(matcher.match(complexBaseType, ComplexUnmatchedClass2.class.getGenericSuperclass()));
assertFalse(matcher.match(complexBaseType, ComplexUnmatchedClass3.class.getGenericSuperclass()));
}

@Test
public void toParameterizedTypeWithUnresolvedTypeVariables() {
class TestClass1<T> implements TestInterface<T, T, T> {}
class TestClass2<A> implements TestInterface<A, A, A> {}
class TestClass3<A, B, C> implements TestInterface<A, B, C> {}

assertTrue(matcher.match((ParameterizedType)TestClass1.class.getGenericInterfaces()[0], TestClass2.class));
assertFalse(matcher.match((ParameterizedType)TestClass1.class.getGenericInterfaces()[0], TestClass3.class));
}

@Test
public void toParameterizedTypeWithGenericArrays() {
class BaseClassWithoutBounds implements TestInterface<TestInterface<Integer, Long, String>[], String, Long> {}
class BaseClassWithUpperBounds
implements TestInterface<TestInterface<Integer, Long, ? extends Number>[], String, Long> {}
class BaseClassWithLowerBounds
implements TestInterface<TestInterface<Integer, Long, ? super Number>[], String, Long> {}

class TestClass1 extends TestInterfaceImpl<TestInterface<Integer, Long, String>[], String, Long> {}
class TestClass2 extends TestInterfaceImpl<TestInterface<Integer, Long, Number>[], String, Long> {}
class TestClass3 extends TestInterfaceImpl<TestInterface<Integer, Long, Long>[], String, Long> {}
class TestClass4 extends TestInterfaceImpl<TestInterface<Integer, Long, Object>[], String, Long> {}

ParameterizedType baseTypeWithoutBounds
= (ParameterizedType)BaseClassWithoutBounds.class.getGenericInterfaces()[0];
assertTrue(matcher.match(baseTypeWithoutBounds, TestClass1.class.getGenericSuperclass()));
assertFalse(matcher.match(baseTypeWithoutBounds, TestClass2.class.getGenericSuperclass()));
assertFalse(matcher.match(baseTypeWithoutBounds, TestClass3.class.getGenericSuperclass()));
assertFalse(matcher.match(baseTypeWithoutBounds, TestClass4.class.getGenericSuperclass()));

ParameterizedType baseTypeWithUpperBounds
= (ParameterizedType)BaseClassWithUpperBounds.class.getGenericInterfaces()[0];
assertFalse(matcher.match(baseTypeWithUpperBounds, TestClass1.class.getGenericSuperclass()));
assertTrue(matcher.match(baseTypeWithUpperBounds, TestClass2.class.getGenericSuperclass()));
assertTrue(matcher.match(baseTypeWithUpperBounds, TestClass3.class.getGenericSuperclass()));
assertFalse(matcher.match(baseTypeWithUpperBounds, TestClass4.class.getGenericSuperclass()));

ParameterizedType baseTypeWithLowerBounds
= (ParameterizedType)BaseClassWithLowerBounds.class.getGenericInterfaces()[0];
assertFalse(matcher.match(baseTypeWithLowerBounds, TestClass1.class.getGenericSuperclass()));
assertTrue(matcher.match(baseTypeWithLowerBounds, TestClass2.class.getGenericSuperclass()));
assertFalse(matcher.match(baseTypeWithLowerBounds, TestClass3.class.getGenericSuperclass()));
assertTrue(matcher.match(baseTypeWithLowerBounds, TestClass4.class.getGenericSuperclass()));
}

@Test
public void toWildcard() throws NoSuchFieldException {
class Base<T> {}
class TestClass1<T extends Base<? extends Number>> {}
class TestClass2<T extends Base<? super Number>> {}
class TestClass3<T extends Base<Number>> {}
class Test {
public Comparator<? extends Base<CharSequence>> field1;
public Comparator<? extends Base<Long>> field2;
public Comparator<? extends Base<Object>> field3;
public Comparator<? extends Base<Number>> field4;
}

Type candidateCharSequence
= ((ParameterizedType)Test.class.getField("field1").getGenericType()).getActualTypeArguments()[0];
Type candidateLong
= ((ParameterizedType)Test.class.getField("field2").getGenericType()).getActualTypeArguments()[0];
Type candidateObject
= ((ParameterizedType)Test.class.getField("field3").getGenericType()).getActualTypeArguments()[0];
Type candidateNumber
= ((ParameterizedType)Test.class.getField("field4").getGenericType()).getActualTypeArguments()[0];

ParameterizedType baseWithUpperBounds
= (ParameterizedType) TestClass1.class.getTypeParameters()[0].getBounds()[0];
assertTrue(matcher.match(baseWithUpperBounds, candidateLong));
assertTrue(matcher.match(baseWithUpperBounds, candidateNumber));
assertFalse(matcher.match(baseWithUpperBounds, candidateCharSequence));
assertFalse(matcher.match(baseWithUpperBounds, candidateObject));

ParameterizedType baseWithLowerBounds
= (ParameterizedType) TestClass2.class.getTypeParameters()[0].getBounds()[0];
assertTrue(matcher.match(baseWithLowerBounds, candidateNumber));
assertTrue(matcher.match(baseWithLowerBounds, candidateObject));
assertFalse(matcher.match(baseWithLowerBounds, candidateLong));
assertFalse(matcher.match(baseWithLowerBounds, candidateCharSequence));

ParameterizedType baseWithoutBounds
= (ParameterizedType) TestClass3.class.getTypeParameters()[0].getBounds()[0];
assertTrue(matcher.match(baseWithoutBounds, candidateNumber));
assertFalse(matcher.match(baseWithoutBounds, candidateLong));
assertFalse(matcher.match(baseWithoutBounds, candidateCharSequence));
assertFalse(matcher.match(baseWithoutBounds, candidateObject));
}

@Test
public void toGenericArrayType() {
class BaseClass implements TestInterface<Integer[], String, Long> {}
class SubClass extends TestInterfaceImpl<Integer[], String, Long> {}

ParameterizedType parameterizedBaseType = (ParameterizedType)BaseClass.class.getGenericInterfaces()[0];
ParameterizedType parameterizedSubType = (ParameterizedType)SubClass.class.getGenericSuperclass();
GenericArrayType genericArrayType = (GenericArrayType)parameterizedSubType.getActualTypeArguments()[0];
assertFalse(matcher.match(parameterizedBaseType, genericArrayType));
}

interface TestInterface<A, B, C> {}
private class TestInterfaceImpl<A, B, C> implements TestInterface<A, B, C> {}
}


TopLevelTypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.resolver.TypeArgumentResolver;

import java.lang.reflect.*;

/**
* @author Denis Zhdanov
* @since Nov 20, 2009
*/

public class TopLevelTypeComplianceMatcher extends AbstractDelegatingTypeComplianceMatcher<Type> {

private final TypeVisitor visitor = new TypeVisitor() {
@Override
public void visitParameterizedType(ParameterizedType type) {
setMatched(getBaseType() == TypeArgumentResolver.RAW_TYPE);
}

@Override
public void visitWildcardType(WildcardType type) {
setMatched(getBaseType() == TypeArgumentResolver.RAW_TYPE);
}

@Override
public void visitGenericArrayType(GenericArrayType type) {
setMatched(getBaseType() == TypeArgumentResolver.RAW_TYPE);
}

@Override
public void visitTypeVariable(TypeVariable<? extends GenericDeclaration> type) {
setMatched(getBaseType() == TypeArgumentResolver.RAW_TYPE);
}

@Override
public void visitClass(Class<?> clazz) {
setMatched(getBaseType() == TypeArgumentResolver.RAW_TYPE);
}

@Override
public void visitType(Type type) {
setMatched(getBaseType() == TypeArgumentResolver.RAW_TYPE);
}
};

public TopLevelTypeComplianceMatcher() {
}

public TopLevelTypeComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
super(delegate);
}

/** {@inheritDoc} */
@Override
protected TypeVisitor getVisitor() {
return visitor;
}
}


TopLevelTypeComplianceMatcherTest.java


package org.springcontrib.util.generic.compliance;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springcontrib.util.generic.resolver.TypeArgumentResolver;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

/**
* @author Denis Zhdanov
* @since 11/21/2009
*/

@SuppressWarnings({"UnusedDeclaration"})
public class TopLevelTypeComplianceMatcherTest {

private final Type dummyType = new Type() {};
private TopLevelTypeComplianceMatcher matcher;

@Before
public void setUp() throws Exception {
matcher = new TopLevelTypeComplianceMatcher();
}

@Test
public void toParameterizedType() {
assertTrue(matcher.match(TypeArgumentResolver.RAW_TYPE, TestInterfaceImpl.class.getGenericInterfaces()[0]));
assertFalse(matcher.match(dummyType, TestInterfaceImpl.class.getGenericInterfaces()[0]));
}

@Test
public void toWildcard() throws NoSuchFieldException {
class TestClass {
public Collection<?> field;
}
Type wildcard = ((ParameterizedType)TestClass.class.getField("field").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(TypeArgumentResolver.RAW_TYPE, wildcard));
assertFalse(matcher.match(dummyType, wildcard));
}

@Test
public void toTypeVariable() {
class TestClass<T> {}
assertTrue(matcher.match(TypeArgumentResolver.RAW_TYPE, TestClass.class.getTypeParameters()[0]));
assertFalse(matcher.match(dummyType, TestClass.class.getTypeParameters()[0]));
}

@Test
public void toClass() {
assertTrue(matcher.match(TypeArgumentResolver.RAW_TYPE, Class.class));
assertFalse(matcher.match(dummyType, Class.class));
}

@Test
public void toType() {
assertTrue(matcher.match(TypeArgumentResolver.RAW_TYPE, dummyType));
assertFalse(matcher.match(dummyType, dummyType));
}

interface TestInterface<A> {}
class TestInterfaceImpl<S> implements TestInterface<S> {}
}


TypeVariableComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.*;

import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.TypeVisitorAdapter;

/**
* @author Denis Zhdanov
* @since Nov 10, 2009
*/

public class TypeVariableComplianceMatcher
extends AbstractDelegatingTypeComplianceMatcher<TypeVariable<? extends GenericDeclaration>>
{

private final TypeVisitor visitor = new TypeVisitorAdapter() {
@Override
public void visitParameterizedType(ParameterizedType type) {
checkBounds(type);
}

@Override
public void visitWildcardType(WildcardType type) {
checkBounds(type);
}

@Override
public void visitGenericArrayType(GenericArrayType type) {
checkBounds(type);
}

@Override
public void visitTypeVariable(TypeVariable<? extends GenericDeclaration> type) {
for (Type baseBound : getBaseType().getBounds()) {
if (baseBound == Object.class) {
// java.lang.Object as a type variable bound means that type is actually inbound, so, we just
// skip it here.
continue;
}

// We assume that base type variable bound restriction is satisfied if base type bound is matched
// at least to one candidate type variable bound.
boolean matched = false;
for (Type candidateBound : type.getBounds()) {
if (candidateBound != Object.class && getDelegate().match(baseBound, candidateBound)) {
matched = true;
break;
}
}
if (!matched) {
return;
}
}
setMatched(true);
}

@Override
public void visitClass(Class<?> clazz) {
checkBounds(clazz);
}
};

public TypeVariableComplianceMatcher() {
}

public TypeVariableComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
super(delegate);
}

@Override
protected TypeVisitor getVisitor() {
return visitor;
}

private void checkBounds(Type type) {
for (Type boundType : getBaseType().getBounds()) {
// java.lang.Object as a bound type means that type is actually inbound, so, we just skip it here.
if (boundType != Object.class && !getDelegate().match(boundType, type)) {
return;
}
}
setMatched(true);
}
}


TypeVariableComplianceMatcherTest.java


package org.springcontrib.util.generic.compliance;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;

/**
* @author Denis Zhdanov
* @since 11/21/2009
*/

@SuppressWarnings({"UnusedDeclaration"})
public class TypeVariableComplianceMatcherTest {

private final TypeVariable<? extends GenericDeclaration> unboundTypeVariable = TestClass.class.getTypeParameters()[0];
private final TypeVariable<? extends GenericDeclaration> boundTypeVariable = TestClass.class.getTypeParameters()[1];
private TypeVariableComplianceMatcher matcher;

@Before
public void setUp() throws Exception {
matcher = new TypeVariableComplianceMatcher();
}

@Test
public void toParameterizedType() throws NoSuchFieldException {
class TestClass {
public Collection<Integer> field1;
public Collection<Long> field2;
public Collection<Number> field3;
public Collection<Object> field4;
public Comparable<String> field5;
public Comparable<Comparable<Long>> field6;
public Comparable<Comparable<Number>> field7;
public Comparable<Comparable<String>> field8;
}

Type integerCollection = TestClass.class.getField("field1").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, integerCollection));
assertFalse(matcher.match(boundTypeVariable, integerCollection));

Type longCollection = TestClass.class.getField("field2").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, longCollection));
assertFalse(matcher.match(boundTypeVariable, longCollection));

Type numberCollection = TestClass.class.getField("field3").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, numberCollection));
assertFalse(matcher.match(boundTypeVariable, numberCollection));

Type objectCollection = TestClass.class.getField("field4").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, objectCollection));
assertFalse(matcher.match(boundTypeVariable, objectCollection));

Type stringComparable = TestClass.class.getField("field5").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, stringComparable));
assertFalse(matcher.match(boundTypeVariable, stringComparable));

Type comparableLong = TestClass.class.getField("field6").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, comparableLong));
assertTrue(matcher.match(boundTypeVariable, comparableLong));

Type comparableNumber = TestClass.class.getField("field7").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, comparableNumber));
assertTrue(matcher.match(boundTypeVariable, comparableNumber));

Type comparableString = TestClass.class.getField("field8").getGenericType();
assertTrue(matcher.match(unboundTypeVariable, comparableString));
assertFalse(matcher.match(boundTypeVariable, comparableString));
}

@Test
public void toWildcardType() throws NoSuchFieldException {
class TestClass {
public Collection<Comparable<?>> field1;
public Collection<Comparable<Comparable<?>>> field2;
public Collection<? extends Comparable<Comparable<Integer>>> field3;
public Collection<? extends Comparable<Comparable<? extends Long>>> field4;
public Collection<? super Comparable<Comparable<Integer>>> field5;
public Collection<? extends Comparable<Comparable<? super Long>>> field6;
public Collection<? extends Comparable<Comparable<? super Number>>> field7;
}

Type comparableWildcard
= ((ParameterizedType)TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, comparableWildcard));
assertFalse(matcher.match(boundTypeVariable, comparableWildcard));

Type comparableComparableWildcard
= ((ParameterizedType)TestClass.class.getField("field2").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, comparableComparableWildcard));
assertFalse(matcher.match(boundTypeVariable, comparableComparableWildcard));

Type comparableComparableInteger
= ((ParameterizedType)TestClass.class.getField("field3").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, comparableComparableInteger));
assertTrue(matcher.match(boundTypeVariable, comparableComparableInteger));

Type comparableComparableUpperLong
= ((ParameterizedType)TestClass.class.getField("field4").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, comparableComparableUpperLong));
assertTrue(matcher.match(boundTypeVariable, comparableComparableInteger));

Type superComparableComparableInteger
= ((ParameterizedType)TestClass.class.getField("field5").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, superComparableComparableInteger));
assertFalse(matcher.match(boundTypeVariable, superComparableComparableInteger));

Type comparableComparableSuperLong
= ((ParameterizedType)TestClass.class.getField("field6").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, comparableComparableSuperLong));
assertFalse(matcher.match(boundTypeVariable, comparableComparableSuperLong));

Type comparableComparableSuperNumber
= ((ParameterizedType)TestClass.class.getField("field7").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, comparableComparableSuperNumber));
assertFalse(matcher.match(boundTypeVariable, comparableComparableSuperNumber));
}

@Test
public void toGenericArray() {
class TestClass<T, U extends Comparable<Comparable<Long>>> extends GenericClass<T[], U[], Integer[]> {}

Type unboundTypeVariableArray
= ((ParameterizedType)TestClass.class.getGenericSuperclass()).getActualTypeArguments()[0];
assertTrue(matcher.match(unboundTypeVariable, unboundTypeVariableArray));
assertFalse(matcher.match(boundTypeVariable, unboundTypeVariableArray));

Type boundTypeVariableArray
= ((ParameterizedType)TestClass.class.getGenericSuperclass()).getActualTypeArguments()[1];
assertTrue(matcher.match(unboundTypeVariable, unboundTypeVariableArray));
assertFalse(matcher.match(boundTypeVariable, unboundTypeVariableArray));

Type integerArray
= ((ParameterizedType)TestClass.class.getGenericSuperclass()).getActualTypeArguments()[2];
assertTrue(matcher.match(unboundTypeVariable, unboundTypeVariableArray));
assertFalse(matcher.match(boundTypeVariable, unboundTypeVariableArray));
}

@Test
public void toTypeVariable() {
class TestClass<
A,
B extends Comparable<Comparable<? extends Number>>,
C extends Comparable<Comparable<Long>>,
D extends Comparable<Comparable<String>>
> {}

Type unbound = TestClass.class.getTypeParameters()[0];
assertTrue(matcher.match(unboundTypeVariable, unbound));
assertFalse(matcher.match(boundTypeVariable, unbound));

Type boundToNumber = TestClass.class.getTypeParameters()[1];
assertTrue(matcher.match(unboundTypeVariable, boundToNumber));
assertTrue(matcher.match(boundTypeVariable, boundToNumber));

Type boundToLong = TestClass.class.getTypeParameters()[2];
assertTrue(matcher.match(unboundTypeVariable, boundToLong));
assertTrue(matcher.match(boundTypeVariable, boundToLong));

Type boundToString = TestClass.class.getTypeParameters()[3];
assertTrue(matcher.match(unboundTypeVariable, boundToString));
assertFalse(matcher.match(boundTypeVariable, boundToString));
}

@Test
public void toClass() {
class BoundToNumber implements Comparable<Comparable<? extends Number>> {
@Override
public int compareTo(Comparable<? extends Number> o) {
return 0;
}
}
class BoundToInteger implements Comparable<Comparable<Integer>> {
@Override
public int compareTo(Comparable<Integer> o) {
return 0;
}
}
class BoundToString implements Comparable<Comparable<String>> {
@Override
public int compareTo(Comparable<String> o) {
return 0;
}
}

assertTrue(matcher.match(unboundTypeVariable, BoundToNumber.class));
assertTrue(matcher.match(boundTypeVariable, BoundToNumber.class));

assertTrue(matcher.match(unboundTypeVariable, BoundToInteger.class));
assertTrue(matcher.match(boundTypeVariable, BoundToInteger.class));

assertTrue(matcher.match(unboundTypeVariable, BoundToString.class));
assertFalse(matcher.match(boundTypeVariable, BoundToString.class));
}

class GenericClass<A, B, C> {}
class TestClass<T, U extends Comparable<Comparable<? extends Number>>> {}
}


WildcardTypeComplianceMatcher.java


package org.springcontrib.util.generic.compliance;

import java.lang.reflect.*;

import org.springcontrib.util.generic.TypeVisitor;
import org.springcontrib.util.generic.TypeVisitorAdapter;

/**
* Allows to check if given type may be used in place of base {@link WildcardType}.
*
* @author Denis Zhdanov
* @since Nov 4, 2009
*/

public class WildcardTypeComplianceMatcher extends AbstractDelegatingTypeComplianceMatcher<WildcardType> {

private final TypeVisitor visitor = new TypeVisitorAdapter() {
@Override
public void visitParameterizedType(ParameterizedType type) {
if (isUnboundWildcard()) {
return;
}
for (Type upperBound : getBaseType().getUpperBounds()) {
if (!getDelegate().match(upperBound, type)) {
return;
}
}
setMatched(checkBaseLowerBounds(type));
}

@Override
public void visitWildcardType(WildcardType type) {
if (isUnboundWildcard()) {
return;
}
Type[] baseUpperBounds = getBaseType().getUpperBounds();
Type[] candidateUpperBounds = type.getUpperBounds();

for (Type baseUpperBound : baseUpperBounds) {
boolean matched = false;
for (Type candidateUpperBound : candidateUpperBounds) {
if (getDelegate().match(baseUpperBound, candidateUpperBound)) {
matched = true;
break;
}
}
if (!matched) {
return;
}
}

Type[] baseLowerBounds = getBaseType().getLowerBounds();

// We assume here that the match is always failed if base type has lower bounds and candidate type
// has upper bound.
if (baseLowerBounds.length > 0
&& (candidateUpperBounds.length > 1 || candidateUpperBounds[0] != Object.class))
{
return;
}
setMatched(checkWildcardCandidateLowerBounds(type));
}

@Override
public void visitGenericArrayType(GenericArrayType type) {
if (isUnboundWildcard()) {
return;
}
for (Type upperBound : getBaseType().getUpperBounds()) {
if (!getDelegate().match(upperBound, type)) {
return;
}
}
setMatched(checkBaseLowerBounds(type));
}

@Override
public void visitTypeVariable(TypeVariable<? extends GenericDeclaration> type) {
if (isUnboundWildcard()) {
return;
}

if (getBaseType().getLowerBounds().length > 0) {
return;
}

for (Type upperBound : getBaseType().getUpperBounds()) {
boolean matched = false;
for (Type typeVariableBound : type.getBounds()) {
if (typeVariableBound == Object.class) {
continue;
}
matched = getDelegate().match(upperBound, typeVariableBound);
if (!matched) {
return;
}
}
if (!matched) {
return;
}
}
setMatched(checkBaseLowerBounds(type));
}

@Override
public void visitClass(Class<?> clazz) {
if (isUnboundWildcard()) {
return;
}
for (Type type : getBaseType().getUpperBounds()) {
if (!getDelegate().match(type, clazz)) {
return;
}
}
setMatched(checkBaseLowerBounds(clazz));
}
};

/**
* This visitor checks if dispatched type is {@link ParameterizedType} and stored it at the
* {@link #parameterizedTypeHolder} in the case of success.
*
* @see #checkParameterizedTypeSpecialCase(Type, Type)
*/

private final TypeVisitor parameterizedTypeRetriever = new TypeVisitorAdapter() {
@Override
public void visitParameterizedType(ParameterizedType type) {
parameterizedTypeHolder.set(type);
}
};

/**
* This visitor checks if dispatched type is {@link GenericArrayType} and stored it at the
* {@link #genericArrayHolder} in the case of success.
*
* @see #checkGenericArraySpecialCase(Type, Type)
*/

private final TypeVisitor genericArrayRetriever = new TypeVisitorAdapter() {
@Override
public void visitGenericArrayType(GenericArrayType type) {
genericArrayHolder.set(type);
}
};

private final ThreadLocal<ParameterizedType> parameterizedTypeHolder = new ThreadLocal<ParameterizedType>();
private final ThreadLocal<GenericArrayType> genericArrayHolder = new ThreadLocal<GenericArrayType>();

public WildcardTypeComplianceMatcher() {
}

public WildcardTypeComplianceMatcher(TypeComplianceMatcher<Type> delegate) {
super(delegate);
}

/** {@inheritDoc} */
@Override
protected TypeVisitor getVisitor() {
return visitor;
}

private boolean isUnboundWildcard() {
Type[] lowerBounds = getBaseType().getLowerBounds();
Type[] upperBounds = getBaseType().getUpperBounds();
return lowerBounds.length == 0 && upperBounds.length == 1 && getBaseType().getUpperBounds()[0] == Object.class;
}

/**
* Allows to check if lower bounds (if any) of the {@link #getBaseType() base wildcard type} prevent given
* type to be used in place of it.
* <p/>
* It's assumed that given type is not a wildcard type.
*
* @param type type to check against {@link #getBaseType() base wildcard type} lower bounds
* @return <code>true</code> if lower bounds of the {@link #getBaseType() base wildcard type}
* don't prevent given type to be used in place of it; <code>false</code> otherwise
*/

private boolean checkBaseLowerBounds(Type type) {
for (Type boundType : getBaseType().getLowerBounds()) {
if (!getDelegate().match(type, boundType)) {
return false;
}
}
return true;
}

/**
* This method contains the logic for special case comparison - checking if lower bounds may prevent one
* wildcard type may be used in place of another wildcard type.
* <p/>
* I.e. this method is intended to handle lower-bound comparisons like
* {@code ? super List<? super Set<Intget>>'} vs {@code ? super Collection<? super Collection<Integer>>}
*
* @param type candidate wildcard type
* @return <code>true</code> if wildcard lower bound don't prevent given wildcard type to be used
* in place of the {@link #getBaseType() base wildcard type}; <code>false</code> otherwise
*/

private boolean checkWildcardCandidateLowerBounds(WildcardType type) {
for (Type baseLowerBound : getBaseType().getLowerBounds()) {
for (Type candidateLowerBound : type.getLowerBounds()) {
Boolean specialCaseResult;
try {
specialCaseResult = checkParameterizedTypeSpecialCase(baseLowerBound, candidateLowerBound);
if (specialCaseResult == null) {
specialCaseResult = checkGenericArraySpecialCase(baseLowerBound, candidateLowerBound);
}
} finally {
parameterizedTypeHolder.set(null);
genericArrayHolder.set(null);
}
boolean matched;
if (specialCaseResult == null) {
matched = getDelegate().match(candidateLowerBound, baseLowerBound);
} else {
matched = specialCaseResult;
}
if (!matched) {
return false;
}
}
}
return true;
}

/**
* Follows the contract of {@link #checkWildcardCandidateLowerBounds(WildcardType)} for the special case
* when two lower bounds are {@link ParameterizedType} instances.
* <p/>
* The general idea is to correctly perform checking for the comparisons like {@code ? super List<? super Long>>}
* vs {@code ? super Collection<? super Number>}. We need to check that <code>List IS-A Collection</code>
* and that <code>Long IS-A Number</code> here.
*
* @param baseLowerBound <code>'base'</code> type lower bound
* @param candidateLowerBound <code>'candidate'</code> type lower bound
* @return <code>true</code> if given lower bounds are {@link ParameterizedType} and
* <code>'candidate'</code> lower bound usage doesn't contradict to
* <code>'base'</code> lower bound usage; <code>false</code> if both given
* arguments are {@link ParameterizedType} and <code>'candidate'</code> bound
* contradicts to <code>'base'</code> bound; <code>null</code> if any of the
* given types is not {@link ParameterizedType}
*
*/

private Boolean checkParameterizedTypeSpecialCase(Type baseLowerBound, Type candidateLowerBound) {
dispatch(baseLowerBound, parameterizedTypeRetriever);
ParameterizedType baseType = parameterizedTypeHolder.get();
if (baseType == null) {
return null;
}

parameterizedTypeHolder.set(null);
dispatch(candidateLowerBound, parameterizedTypeRetriever);
ParameterizedType candidateType = parameterizedTypeHolder.get();
if (candidateType == null) {
return null;
}

if (!getDelegate().match(candidateType.getRawType(), baseType.getRawType())) {
return false;
}

Type[] candidateArguments = candidateType.getActualTypeArguments();
for (int i = 0; i < candidateArguments.length; ++i) {
Type baseArgument = getTypeArgumentResolver().resolve(candidateType, baseType, i);
if (!getDelegate().match(baseArgument, candidateArguments[i])) {
return false;
}
}
return true;
}

/**
* Follows the contract of {@link #checkWildcardCandidateLowerBounds(WildcardType)} for the special case
* when two lower bounds are {@link ParameterizedType} instances.
* <p/>
* Just delegates to {@link #checkParameterizedTypeSpecialCase(Type, Type)} for the generic arrays component types.
*
* @param baseLowerBound <code>'base'</code> type lower bound
* @param candidateLowerBound <code>'candidate'</code> type lower bound
* @return <code>true</code> if given lower bounds are {@link GenericArrayType} and
* <code>'candidate'</code> lower bound usage doesn't contradict to
* <code>'base'</code> lower bound usage; <code>false</code> if both given
* arguments are {@link GenericArrayType} and <code>'candidate'</code> bound
* contradicts to <code>'base'</code> bound; <code>null</code> if any of the
* given types is not {@link GenericArrayType}
*/

private Boolean checkGenericArraySpecialCase(Type baseLowerBound, Type candidateLowerBound) {
dispatch(baseLowerBound, genericArrayRetriever);
GenericArrayType baseType = genericArrayHolder.get();
if (baseType == null) {
return null;
}

genericArrayHolder.set(null);
dispatch(candidateLowerBound, genericArrayRetriever);
GenericArrayType candidateType = genericArrayHolder.get();
if (candidateType == null) {
return null;
}

return checkParameterizedTypeSpecialCase(
baseType.getGenericComponentType(), candidateType.getGenericComponentType()
);
}
}


WildcardTypeComplianceMatcherTest.java


package org.springcontrib.util.generic.compliance;

import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.*;

/**
* @author Denis Zhdanov
* @since 11/22/2009
*/

@SuppressWarnings({"UnusedDeclaration", "serial"})
public class WildcardTypeComplianceMatcherTest {

private final WildcardType wildcard;
private final WildcardType boundToNumber;
private final WildcardType boundToTypeVariable;
private final WildcardType boundToLong;
private WildcardTypeComplianceMatcher matcher;

public WildcardTypeComplianceMatcherTest() throws NoSuchFieldException {
wildcard = (WildcardType) ((ParameterizedType)
TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0];
boundToNumber = (WildcardType) ((ParameterizedType)
TestClass.class.getField("field2").getGenericType()).getActualTypeArguments()[0];
boundToTypeVariable = (WildcardType) ((ParameterizedType)
TestClass.class.getField("field3").getGenericType()).getActualTypeArguments()[0];
boundToLong = (WildcardType) ((ParameterizedType)
TestClass.class.getField("field4").getGenericType()).getActualTypeArguments()[0];
}

@Before
public void setUp() throws Exception {
matcher = new WildcardTypeComplianceMatcher();
}

@Test
public void toParameterizedType() throws NoSuchFieldException {
class TestClass {
public List<? extends Integer> field1;
public Collection<Object> field2;
public Collection<Comparable<String>> field3;
public Collection<String> field4;
public List<Object> field5;
}

Type boundToInteger = TestClass.class.getField("field1").getGenericType();
assertFalse(matcher.match(wildcard, boundToInteger));
assertTrue(matcher.match(boundToNumber, boundToInteger));
assertFalse(matcher.match(boundToTypeVariable, boundToInteger));
assertFalse(matcher.match(boundToLong, boundToInteger));

Type boundToObject = TestClass.class.getField("field2").getGenericType();
assertFalse(matcher.match(wildcard, boundToObject));
assertFalse(matcher.match(boundToNumber, boundToObject));
assertFalse(matcher.match(boundToTypeVariable, boundToObject));
assertTrue(matcher.match(boundToLong, boundToObject));

Type comparableString = TestClass.class.getField("field3").getGenericType();
assertFalse(matcher.match(wildcard, comparableString));
assertFalse(matcher.match(boundToNumber, comparableString));
assertTrue(matcher.match(boundToTypeVariable, comparableString));
assertFalse(matcher.match(boundToLong, comparableString));

Type boundToString = TestClass.class.getField("field4").getGenericType();
assertFalse(matcher.match(wildcard, boundToString));
assertFalse(matcher.match(boundToNumber, boundToString));
assertFalse(matcher.match(boundToTypeVariable, boundToString));
assertFalse(matcher.match(boundToLong, boundToString));

Type objectList = TestClass.class.getField("field5").getGenericType();
assertFalse(matcher.match(wildcard, objectList));
assertFalse(matcher.match(boundToNumber, objectList));
assertFalse(matcher.match(boundToTypeVariable, objectList));
assertFalse(matcher.match(boundToLong, objectList));
}

@Test
public void toWildcard() throws NoSuchFieldException {
class TestClass {
public Collection<? extends List<? super Long>> field1;
public Collection<? extends List<? extends Long>> field2;
public Collection<? super Collection<? extends Number>> field3;
public Collection<? super Collection<? super Number>> field4;
public Collection<? super List<? super Number>> field5;
}

Type listSuperLong
= ((ParameterizedType)TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(wildcard, listSuperLong));
assertFalse(matcher.match(boundToNumber, listSuperLong));
assertFalse(matcher.match(boundToTypeVariable, listSuperLong));
assertFalse(matcher.match(boundToLong, listSuperLong));

Type listExtendsLong
= ((ParameterizedType)TestClass.class.getField("field2").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(wildcard, listExtendsLong));
assertTrue(matcher.match(boundToNumber, listExtendsLong));
assertFalse(matcher.match(boundToTypeVariable, listExtendsLong));
assertFalse(matcher.match(boundToLong, listExtendsLong));

Type collectionExtendsNumber
= ((ParameterizedType)TestClass.class.getField("field3").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(wildcard, collectionExtendsNumber));
assertFalse(matcher.match(boundToNumber, collectionExtendsNumber));
assertFalse(matcher.match(boundToTypeVariable, collectionExtendsNumber));
assertFalse(matcher.match(boundToLong, collectionExtendsNumber));

Type collectionSuperNumber
= ((ParameterizedType)TestClass.class.getField("field4").getGenericType()).getActualTypeArguments()[0];
assertFalse(matcher.match(wildcard, collectionSuperNumber));
assertFalse(matcher.match(boundToNumber, collectionSuperNumber));
assertFalse(matcher.match(boundToTypeVariable, collectionSuperNumber));
assertTrue(matcher.match(boundToLong, collectionSuperNumber));

WildcardType superListSuperNumber = (WildcardType) ((ParameterizedType)
TestClass.class.getField("field5").getGenericType()).getActualTypeArguments()[0];
assertTrue(matcher.match(superListSuperNumber, collectionSuperNumber));
}

@Test
public void toWildcardWithArrays() throws NoSuchFieldException {
class TestClass<T> {
public Collection<? extends Integer[]> baseField1;
public Collection<? super Long[]> baseField2;
public Collection<? extends Collection<? super Long[]>[]> baseField3;
public Collection<? super List<? extends Number[]>[]> baseField4;

public Collection<? extends Integer> candidateField1;
public Collection<? extends Object[]> candidateField2;
public Collection<? super Number[]> candidateField3;
public Collection<? extends List<? super Number[]>[]> candidateField4;
public Collection<? extends List<? super Integer>[]> candidateField5;
public Collection<? super Collection<Integer[]>[]> candidateField6;
}

doTest(TestClass.class, "baseField1", "candidateField1", false);
doTest(TestClass.class, "baseField1", "candidateField2", false);
doTest(TestClass.class, "baseField2", "candidateField2", false);
doTest(TestClass.class, "baseField2", "candidateField3", true);
doTest(TestClass.class, "baseField3", "candidateField4", true);
doTest(TestClass.class, "baseField3", "candidateField5", false);
doTest(TestClass.class, "baseField4", "candidateField3", false);
doTest(TestClass.class, "baseField4", "candidateField5", false);
doTest(TestClass.class, "baseField4", "candidateField6", true);
}

private void doTest(Class<?> clazz, String baseFieldName, String candidateFieldName, boolean expectedMatch)
throws NoSuchFieldException
{
WildcardType wildcardType = (WildcardType) getGenericFieldType(clazz, baseFieldName);
assertEquals(expectedMatch, matcher.match(wildcardType, getGenericFieldType(clazz, candidateFieldName)));
}

private Type getGenericFieldType(Class<?> clazz, String fieldName) throws NoSuchFieldException {
ParameterizedType parameterizedType = (ParameterizedType) clazz.getField(fieldName).getGenericType();
return parameterizedType.getActualTypeArguments()[0];
}

@Test
public void toGenericArray() throws NoSuchFieldException {
class TestClass<T> {
public Collection<? extends Collection<Integer>[]> field1;
public Collection<? super Collection<Long>[]> field2;
}
assertFalse(matcher.match(
wildcard,
((ParameterizedType)TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0]
));
assertFalse(matcher.match(
boundToNumber,
((ParameterizedType)TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0]
));
assertFalse(matcher.match(
boundToLong,
((ParameterizedType)TestClass.class.getField("field1").getGenericType()).getActualTypeArguments()[0]
));
}

@Test
public void toTypeVariable() {
class TestClass<
A extends List<? extends Integer>,
B extends List<? super Long>,
C extends Collection<Comparable<?>>,
D
> {}
TypeVariable<Class<TestClass>>[] typeVariables = TestClass.class.getTypeParameters();

assertFalse(matcher.match(wildcard, typeVariables[0]));
assertFalse(matcher.match(wildcard, typeVariables[1]));
assertFalse(matcher.match(wildcard, typeVariables[2]));
assertFalse(matcher.match(wildcard, typeVariables[3]));

assertTrue(matcher.match(boundToNumber, typeVariables[0]));
assertFalse(matcher.match(boundToNumber, typeVariables[1]));
assertFalse(matcher.match(boundToNumber, typeVariables[3]));

assertFalse(matcher.match(boundToTypeVariable, typeVariables[0]));
assertTrue(matcher.match(boundToTypeVariable, typeVariables[2]));
assertFalse(matcher.match(boundToTypeVariable, typeVariables[3]));
}

@Test
public void toClass() {
class TestClass1 extends ArrayList<Long> {}
class TestClass2 extends HashSet<String> {}
class TestClass3 extends TreeSet<Comparable<String>> {}
class TestClass4 extends LinkedList<Comparable<Number>> {}

assertFalse(matcher.match(wildcard, TestClass1.class));
assertFalse(matcher.match(wildcard, TestClass2.class));

assertTrue(matcher.match(boundToNumber, TestClass1.class));
assertFalse(matcher.match(boundToNumber, TestClass2.class));

assertTrue(matcher.match(boundToTypeVariable, TestClass1.class));
assertTrue(matcher.match(boundToTypeVariable, TestClass2.class));
assertTrue(matcher.match(boundToTypeVariable, TestClass3.class));
assertTrue(matcher.match(boundToTypeVariable, TestClass4.class));

assertFalse(matcher.match(boundToLong, TestClass1.class));
assertFalse(matcher.match(boundToLong, TestClass2.class));
assertFalse(matcher.match(boundToLong, TestClass3.class));
assertFalse(matcher.match(boundToLong, TestClass4.class));
}

static class TestClass<T> {
public Collection<?> field1;
public Collection<? extends Collection<? extends Number>> field2;
public Collection<? extends Collection<Comparable<T>>> field3;
public Collection<? super Collection<? super Long>> field4;
}
}


And finally the clue that gets all matchers together and serves as an entry point for all type compliance matching clients, mr. Triple Dispatch - CompositeTypeComplianceMatcher:


package org.springcontrib.util.generic.compliance;

import org.springcontrib.util.generic.TypeVisitor;

import java.lang.reflect.*;

/**
* Generalizes {@link TypeComplianceMatcher} contract in order to perform double dispatch for the
* <code>'base'</code> type in order to delegate the job to more specialized implementation.
* <p/>
* This class is not singleton but offers single-point-of-usage field ({@link #INSTANCE}).
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since Nov 3, 2009
*/

public class CompositeTypeComplianceMatcher extends AbstractTypeComplianceMatcher<Type> {

/** Single-point-of-usage field. */
public static final CompositeTypeComplianceMatcher INSTANCE = new CompositeTypeComplianceMatcher();

private final ClassComplianceMatcher classComplianceMatcher = new ClassComplianceMatcher(this);
private final WildcardTypeComplianceMatcher wildcardTypeComplianceMatcher
= new WildcardTypeComplianceMatcher(this);
private final ParameterizedTypeComplianceMatcher parameterizedTypeComplianceMatcher
= new ParameterizedTypeComplianceMatcher(this);
private final GenericArrayTypeComplianceMatcher genericArrayTypeComplianceMatcher
= new GenericArrayTypeComplianceMatcher(this);
private final TypeVariableComplianceMatcher typeVariableComplianceMatcher = new TypeVariableComplianceMatcher(this);
private final TopLevelTypeComplianceMatcher topLevelTypeComplianceMatcher = new TopLevelTypeComplianceMatcher(this);

private final TypeVisitor visitor = new TypeVisitor() {
@Override
public void visitParameterizedType(ParameterizedType type) {
setMatched(parameterizedTypeComplianceMatcher.match(type, getBaseType(), isStrict()));
}

@Override
public void visitWildcardType(WildcardType type) {
setMatched(wildcardTypeComplianceMatcher.match(type, getBaseType(), isStrict()));
}

@Override
public void visitGenericArrayType(GenericArrayType type) {
setMatched(genericArrayTypeComplianceMatcher.match(type, getBaseType(), isStrict()));
}

@Override
public void visitTypeVariable(TypeVariable<? extends GenericDeclaration> type) {
setMatched(typeVariableComplianceMatcher.match(type, getBaseType(), isStrict()));
}

@Override
public void visitClass(Class<?> clazz) {
setMatched(classComplianceMatcher.match(clazz, getBaseType(), isStrict()));
}

@Override
public void visitType(Type type) {
setMatched(topLevelTypeComplianceMatcher.match(type, getBaseType(), isStrict()));
}
};

/** {@inheritDoc} */
@Override
public boolean match(Type base, Type candidate) throws IllegalArgumentException {
// Overrides basic method in order to perform triple dispatch. I.e. first type dispatch is performed
// against 'base' type in order to find corresponding TypeComplianceMatcher implementation and that
// implementation is asked to check given 'candidate' type.
return super.match(candidate, base, false);
}

/** {@inheritDoc} */
@Override
public boolean match(Type base, Type candidate, boolean topLevelCheck) {
// Overrides basic method in order to perform triple dispatch. I.e. first type dispatch is performed
// against 'base' type in order to find corresponding TypeComplianceMatcher implementation and that
// implementation is asked to check given 'candidate' type.
return super.match(candidate, base, topLevelCheck);
}

/** {@inheritDoc} */
@Override
protected TypeVisitor getVisitor() {
return visitor;
}
}


Phew..

No comments:

Post a Comment