// code originates from TypeUtilsTest.java in jtype (http://code.google.com/p/jtype/) and has been modified to suite
// the HV requirements and code style
/*
 * Copyright 2009 IIZUKA Software Technologies Ltd
 *
 * 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.hibernate.validator.test.internal.util;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;

import java.io.Serializable;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorDescriptor;
import org.hibernate.validator.internal.util.TypeHelper;
import org.hibernate.validator.testutil.TestForIssue;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * Tests {@code TypeUtils}.
 *
 * @author Mark Hobson
 * @see org.hibernate.validator.internal.util.TypeHelper
 */
public class TypeHelperTest {
	private static final Type[] DEFAULT_UPPER_BOUNDS = new Type[] { Object.class };
	private static final Type[] DEFAULT_LOWER_BOUNDS = new Type[0];
	private GenericDeclaration declaration;

	private static class IntegerArrayList extends ArrayList<Integer> {
		// simple subclass to fix generics
	}

	private static class IntegerKeyHashMap<V> extends HashMap<Integer, V> {
		// simple subclass to fix generics
	}

	private static class StringsByIntegerHashMap extends IntegerKeyHashMap<String> {
		// simple subclass to fix generics
	}

	@BeforeClass
	public void beforeClass() throws Exception {
		declaration = getClass().getConstructor();
	}

	@Test
	public void isAssignableWithPrimitiveDouble() {
		assertAssignable( Double.TYPE, Double.TYPE );
		assertAsymmetricallyAssignable( Double.TYPE, Float.TYPE );
		assertAsymmetricallyAssignable( Double.TYPE, Long.TYPE );
		assertAsymmetricallyAssignable( Double.TYPE, Integer.TYPE );
		assertAsymmetricallyAssignable( Double.TYPE, Character.TYPE );
		assertAsymmetricallyAssignable( Double.TYPE, Short.TYPE );
		assertAsymmetricallyAssignable( Double.TYPE, Byte.TYPE );
	}

	@Test
	public void isAssignableWithPrimitiveFloat() {
		assertAssignable( Float.TYPE, Float.TYPE );
		assertAsymmetricallyAssignable( Float.TYPE, Long.TYPE );
		assertAsymmetricallyAssignable( Float.TYPE, Integer.TYPE );
		assertAsymmetricallyAssignable( Float.TYPE, Character.TYPE );
		assertAsymmetricallyAssignable( Float.TYPE, Short.TYPE );
		assertAsymmetricallyAssignable( Float.TYPE, Byte.TYPE );
	}

	@Test
	public void isAssignableWithPrimitiveLong() {
		assertAssignable( Long.TYPE, Long.TYPE );
		assertAsymmetricallyAssignable( Long.TYPE, Integer.TYPE );
		assertAsymmetricallyAssignable( Long.TYPE, Character.TYPE );
		assertAsymmetricallyAssignable( Long.TYPE, Short.TYPE );
		assertAsymmetricallyAssignable( Long.TYPE, Byte.TYPE );
	}

	@Test
	public void isAssignableWithPrimitiveInt() {
		assertAssignable( Integer.TYPE, Integer.TYPE );
		assertAsymmetricallyAssignable( Integer.TYPE, Character.TYPE );
		assertAsymmetricallyAssignable( Integer.TYPE, Short.TYPE );
		assertAsymmetricallyAssignable( Integer.TYPE, Byte.TYPE );
	}

	@Test
	public void isAssignableWithPrimitiveShort() {
		assertAssignable( Short.TYPE, Short.TYPE );
		assertAsymmetricallyAssignable( Short.TYPE, Byte.TYPE );
	}

	// JLS 4.10.2 Subtyping among Class and Interface Types

	/**
	 * The direct superclasses of C.
	 */
	@Test
	public void isAssignableWithDirectSuperclassFromParameterizedType() {
		assertAssignable( AbstractList.class, TypeHelper.parameterizedType( ArrayList.class, Integer.class ) );
	}

	/**
	 * The direct superinterfaces of C.
	 */
	@Test
	public void isAssignableWithDirectSuperinterfaceFromParameterizedType() {
		assertAssignable( Collection.class, TypeHelper.parameterizedType( List.class, Integer.class ) );
	}

	/**
	 * The type Object, if C is an interface type with no direct superinterfaces.
	 */
	@Test
	public void isAssignableWithObjectFromInterface() {
		assertAssignable( Object.class, Iterable.class );
	}

	/**
	 * The raw type C.
	 */
	@Test
	public void isAssignableWithRawTypeFromParameterizedType() {
		assertAssignable( List.class, TypeHelper.parameterizedType( List.class, Integer.class ) );
	}

	// TODO: finish 4.10.2

	// JLS 4.10.3 Subtyping among Array Types

	/**
	 * If S and T are both reference types, then S[] >1 T[] iff S >1 T.
	 */
	@Test
	public void isAssignableWithArrayClassFromDirectSubtypeArrayClass() {
		assertAsymmetricallyAssignable( Number[].class, Integer[].class );
	}

	@Test
	public void isAssignableWithArrayClassFromIndirectSubtypeArrayClass() {
		assertAsymmetricallyAssignable( Object[].class, Integer[].class );
	}

	@Test
	public void isAssignableWithArrayClassFromGenericArrayType() {
		assertAssignable( Integer[].class, TypeHelper.genericArrayType( Integer.class ) );
	}

	@Test
	public void isAssignableWithArrayClassFromDirectSubtypeGenericArrayType() {
		assertAsymmetricallyAssignable( Number[].class, TypeHelper.genericArrayType( Integer.class ) );
	}

	@Test
	public void isAssignableWithArrayClassFromIndirectSubtypeGenericArrayType() {
		assertAsymmetricallyAssignable( Object[].class, TypeHelper.genericArrayType( Integer.class ) );
	}

	@Test
	public void isAssignableWithGenericArrayTypeFromDirectSubtypeGenericArrayType() {
		assertAsymmetricallyAssignable(
				TypeHelper.genericArrayType( Number.class ),
				TypeHelper.genericArrayType( Integer.class )
		);
	}

	@Test
	public void isAssignableWithGenericArrayTypeFromIndirectSubtypeGenericArrayType() {
		assertAsymmetricallyAssignable(
				TypeHelper.genericArrayType( Object.class ),
				TypeHelper.genericArrayType( Integer.class )
		);
	}

	@Test
	public void isAssignableWithGenericArrayTypeFromArrayClass() {
		assertAssignable( TypeHelper.genericArrayType( Integer.class ), Integer[].class );
	}

	@Test
	public void isAssignableWithGenericArrayTypeFromDirectSubtypeArrayClass() {
		assertAsymmetricallyAssignable( TypeHelper.genericArrayType( Number.class ), Integer[].class );
	}

	@Test
	public void isAssignableWithGenericArrayTypeFromIndirectSubtypeArrayClass() {
		assertAsymmetricallyAssignable( TypeHelper.genericArrayType( Object.class ), Integer[].class );
	}

	/**
	 * Object >1 Object[].
	 */
	@Test
	public void isAssignableWithObjectFromObjectArrayClass() {
		assertAsymmetricallyAssignable( Object.class, Object[].class );
	}

	@Test
	public void isAssignableWithObjectFromArrayClass() {
		assertAsymmetricallyAssignable( Object.class, Integer[].class );
	}

	@Test
	public void isAssignableWithObjectFromObjectGenericArrayType() {
		assertAsymmetricallyAssignable( Object.class, TypeHelper.genericArrayType( Object.class ) );
	}

	@Test
	public void isAssignableWithObjectFromGenericArrayType() {
		assertAsymmetricallyAssignable( Object.class, TypeHelper.genericArrayType( Integer.class ) );
	}

	/**
	 * Cloneable >1 Object[].
	 */
	@Test
	public void isAssignableWithCloneableFromObjectArrayClass() {
		assertAsymmetricallyAssignable( Cloneable.class, Object[].class );
	}

	@Test
	public void isAssignableWithCloneableFromArrayClass() {
		assertAsymmetricallyAssignable( Cloneable.class, Integer[].class );
	}

	@Test
	public void isAssignableWithCloneableFromObjectGenericArrayType() {
		assertAsymmetricallyAssignable( Cloneable.class, TypeHelper.genericArrayType( Object.class ) );
	}

	@Test
	public void isAssignableWithCloneableFromGenericArrayType() {
		assertAsymmetricallyAssignable( Cloneable.class, TypeHelper.genericArrayType( Integer.class ) );
	}

	/**
	 * java.io.Serializable >1 Object[].
	 */
	@Test
	public void isAssignableWithSerializableFromObjectArrayClass() {
		assertAsymmetricallyAssignable( Serializable.class, Object[].class );
	}

	@Test
	public void isAssignableWithSerializableFromArrayClass() {
		assertAsymmetricallyAssignable( Serializable.class, Integer[].class );
	}

	@Test
	public void isAssignableWithSerializableFromObjectGenericArrayType() {
		assertAsymmetricallyAssignable( Serializable.class, TypeHelper.genericArrayType( Object.class ) );
	}

	@Test
	public void isAssignableWithSerializableFromGenericArrayType() {
		assertAsymmetricallyAssignable( Serializable.class, TypeHelper.genericArrayType( Integer.class ) );
	}

	/**
	 * If p is a primitive type, then Object >1 p[].
	 */
	@Test
	public void isAssignableWithObjectFromPrimitiveArray() {
		assertAsymmetricallyAssignable( Object.class, int[].class );
	}

	/**
	 * If p is a primitive type, then Cloneable >1 p[].
	 */
	@Test
	public void isAssignableWithCloneableFromPrimitiveArray() {
		assertAsymmetricallyAssignable( Cloneable.class, int[].class );
	}

	/**
	 * If p is a primitive type, then java.io.Serializable >1 p[].
	 */
	@Test
	public void isAssignableWithSerializableFromPrimitiveArray() {
		assertAsymmetricallyAssignable( Serializable.class, int[].class );
	}

	@Test(expectedExceptions = IllegalArgumentException.class)
	public void isAssignableWithNullSupertype() {
		assertAssignable( null, Integer.class );
	}

	@Test(expectedExceptions = IllegalArgumentException.class)
	public void isAssignableWithNullType() {
		assertAssignable( Integer.class, null );
	}

	/**
	 * Tests that classes are assignable to their direct superclasses.
	 *
	 * {@literal Number <: Integer}
	 */
	@Test
	public void isAssignableWithClassFromDirectSubclass() {
		assertAsymmetricallyAssignable( Number.class, Integer.class );
	}

	/**
	 * Tests that classes are assignable to their indirect superclasses.
	 *
	 * {@literal Object <: Integer}
	 */
	@Test
	public void isAssignableWithClassFromIndirectSubclass() {
		assertAsymmetricallyAssignable( Object.class, Integer.class );
	}

	/**
	 * Tests that parameterized types are assignable to their raw types.
	 *
	 * {@literal List <: List<Integer>}
	 */
	@Test
	public void isAssignableWithClassFromParameterizedType() {
		assertAsymmetricallyAssignable( List.class, TypeHelper.parameterizedType( List.class, Integer.class ) );
	}

	/**
	 * Tests that parameterized types are assignable if their raw types are directly assignable.
	 *
	 * {@literal Collection<Integer> <: List<Integer>}
	 */
	@Test
	public void isAssignableWithDirectlyAssignableParameterizedTypeRawTypes() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType( Collection.class, Integer.class ),
				TypeHelper.parameterizedType( List.class, Integer.class )
		);
	}

	/**
	 * Tests that parameterized types are assignable if their raw types are indirectly assignable.
	 *
	 * {@literal Collection<Integer> <: ArrayList<Integer>}
	 */
	@Test
	public void isAssignableWithIndirectlyAssignableParameterizedTypeRawTypes() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType( Collection.class, Integer.class ),
				TypeHelper.parameterizedType( ArrayList.class, Integer.class )
		);
	}

	/**
	 * Tests that parameterized types are not assignable if their raw types are not assignable.
	 *
	 * {@literal List<Integer> !<: Set<Integer>}
	 */
	@Test
	public void isAssignableWithUnassignableParameterizedTypeRawTypes() {
		assertUnassignable(
				TypeHelper.parameterizedType( List.class, Integer.class ),
				TypeHelper.parameterizedType( Set.class, Integer.class )
		);

		assertUnassignable(
				TypeHelper.parameterizedType( Set.class, Integer.class ),
				TypeHelper.parameterizedType( List.class, Integer.class )
		);
	}

	/**
	 * Tests that parameterized types are not assignable even if their type arguments are assignable.
	 *
	 * {@literal List<Number> !<: List<Integer>}
	 */
	@Test
	public void isAssignableWithAssignableParameterizedTypeArguments() {
		assertUnassignable(
				TypeHelper.parameterizedType( List.class, Number.class ),
				TypeHelper.parameterizedType( List.class, Integer.class )
		);

		assertUnassignable(
				TypeHelper.parameterizedType( List.class, Integer.class ),
				TypeHelper.parameterizedType( List.class, Number.class )
		);
	}

	/**
	 * Tests that parameterized type arguments are assignable to wildcard types.
	 *
	 * {@literal List<?> <: List<Integer>}
	 */
	@Test
	public void isAssignableWithWildcardParameterizedTypeFromParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType( List.class, wildcardType() ),
				TypeHelper.parameterizedType( List.class, Integer.class )
		);
	}

	/**
	 * Tests that parameterized type upper bounded wildcard type arguments are assignable to wildcard types.
	 *
	 * {@literal List<?> <: List<? extends Number>}
	 */
	@Test
	public void isAssignableWithWildcardParameterizedTypeFromUpperBoundedWildcardParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType( List.class, wildcardType() ),
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeUpperBounded( new Type[] { Number.class } )
				)
		);
	}

	/**
	 * Tests that parameterized type arguments are assignable to wildcard types on their upper bound.
	 *
	 * {@literal List<? extends Number> <: List<Number>}
	 */
	@Test
	public void isAssignableWithUpperBoundedWildcardParameterizedTypeFromParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType( List.class, Number.class )

		);
	}

	/**
	 * Tests that parameterized type arguments are assignable to wildcard types within their upper bound.
	 *
	 * {@literal List<? extends Number> <: List<Integer>}
	 */
	@Test
	public void isAssignableWithUpperBoundedWildcardParameterizedTypeFromInBoundsParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeUpperBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType( List.class, Integer.class )

		);
	}

	/**
	 * Tests that parameterized type arguments are not assignable to wildcard types outside of their upper bound.
	 *
	 * {@literal List<? extends Number> !<: List<Object>}
	 */
	@Test
	public void isAssignableWithUpperBoundedWildcardParameterizedTypeFromOutOfBoundsParameterizedType() {
		assertUnassignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeUpperBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType( List.class, Object.class )

		);

		assertUnassignable(
				TypeHelper.parameterizedType( List.class, Object.class ),
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				)
		);
	}

	/**
	 * {@literal List<? extends Number> <: List<? extends Integer>}
	 */
	@Test
	public void isAssignableWithUpperBoundedWildcardParameterizedTypeFromInBoundsUpperBoundedWildcardParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Integer.class } )
				)

		);
	}

	/**
	 * Tests that parameterized type arguments are assignable to wildcard types on their lower bound.
	 *
	 * {@literal List<? super Number> <: List<Number>}
	 */
	@Test
	public void isAssignableWithLowerBoundedWildcardParameterizedTypeFromParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType(
						List.class,
						Number.class
				)

		);
	}

	/**
	 * Tests that parameterized type arguments are assignable to wildcard types within their lower bound.
	 *
	 * {@literal List<? super Number> <: List<Object>}
	 */
	@Test
	public void isAssignableWithLowerBoundedWildcardParameterizedTypeFromInBoundsParameterizedType() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType(
						List.class,
						Object.class
				)

		);
	}

	/**
	 * Tests that parameterized type arguments are assignable to wildcard types outside of their lower bound.
	 *
	 * {@literal List<? super Number> !<: List<Integer>}
	 */
	@Test
	public void isAssignableWithLowerBoundedWildcardParameterizedTypeFromOutOfBoundsParameterizedType() {
		assertUnassignable(
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				),
				TypeHelper.parameterizedType(
						List.class,
						Integer.class
				)

		);

		assertUnassignable(
				TypeHelper.parameterizedType(
						List.class,
						Integer.class
				),
				TypeHelper.parameterizedType(
						List.class,
						wildcardTypeLowerBounded( new Type[] { Number.class } )
				)
		);
	}

	/**
	 * Tests that classes are assignable to parameterized supertypes.
	 *
	 * {@literal List<Integer> <: IntegerArrayList}
	 */
	@Test
	public void isAssignableWithParameterizedTypeFromClassWithActualTypeArguments() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType( List.class, Integer.class ),
				IntegerArrayList.class
		);
	}

	@Test
	public void isAssignableWithUnboundedWildcardParameterizedTypeFromClass() {

		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType( List.class, wildcardType() ),
				ArrayList.class
		);
	}

	@Test
	public void isAssignableWithUnboundedWildcardParameterizedTypeFromClassWithActualTypeArguments() {
		assertAsymmetricallyAssignable(
				TypeHelper.parameterizedType(
						Map.class,
						wildcardType(),
						wildcardType()
				),
				StringsByIntegerHashMap.class
		);
	}

	/**
	 * Tests that unbounded type variables are assignable to Object.
	 *
	 * {@literal Object <: T}
	 */
	@Test
	public void isAssignableWithObjectFromTypeVariableWithNoBounds() {
		assertAssignable( Object.class, typeVariable( declaration, "T" ) );
	}

	/**
	 * Tests that type variables with a single bound are assignable to their bound.
	 *
	 * {@literal Number <: T extends Number}
	 */
	@Test
	public void isAssignableWithBoundFromTypeVariableWithBound() {
		assertAssignable( Object.class, typeVariable( declaration, "T", Number.class ) );
	}

	/**
	 * Tests that type variables with a single bound are not assignable to subtypes of their bound.
	 *
	 * {@literal Integer !<: T extends Number}
	 */
	@Test
	public void isAssignableWithTypeOutsideOfBoundFromTypeVariableWithBound() {
		assertUnassignable( Integer.class, typeVariable( declaration, "T", Number.class ) );
	}

	/**
	 * Tests that type variables with a single bound are assignable to supertypes of their bound.
	 *
	 * {@literal Number <: T extends Integer}
	 */
	@Test
	public void isAssignableWithTypeInsideOfBoundFromTypeVariableWithBound() {
		assertAssignable( Number.class, typeVariable( declaration, "T", Integer.class ) );
	}

	/**
	 * Tests that type variables with multiple bounds are assignable to their bounds.
	 *
	 * {@literal Number, Collection <: T extends Number & Collection}
	 */
	@Test
	public void isAssignableWithBoundsFromTypeVariableWithBounds() {
		TypeVariable<?> type = typeVariable( declaration, "T", Number.class, Collection.class );

		assertAssignable( Number.class, type );
		assertAssignable( Collection.class, type );
	}

	/**
	 * Tests that type variables with multiple bounds are not assignable to supertypes of their bounds.
	 *
	 * {@literal Integer, Thread !<: T extends Number & Runnable}
	 */
	@Test
	public void isAssignableWithTypeOutsideOfBoundsFromTypeVariableWithBounds() {
		TypeVariable<?> type = typeVariable( declaration, "T", Number.class, Collection.class );

		assertUnassignable( Integer.class, type );
		assertUnassignable( List.class, type );
	}

	/**
	 * Tests that type variables with multiple bounds are assignable to subtypes of their bounds.
	 *
	 * {@literal Number, Collection <: T extends Integer & List}
	 */
	@Test
	public void isAssignableWithTypeInsideOfBoundsFromTypeVariableWithBounds() {
		TypeVariable<?> type = typeVariable( declaration, "T", Integer.class, List.class );

		assertAssignable( Number.class, type );
		assertAssignable( Collection.class, type );
	}

	/**
	 * Tests that unbounded wildcards are assignable to Object.
	 *
	 * {@literal Object <: ?}
	 */
	@Test
	public void isAssignableWithObjectFromUnboundedWildcardType() {
		assertAssignable( Object.class, wildcardType() );
	}

	/**
	 * Tests that upper bounded wildcards are assignable to their upper bound.
	 *
	 * {@literal Number <: ? extends Number}
	 */
	@Test
	public void isAssignableWithBoundFromUpperBoundedWildcardType() {
		assertAssignable( Number.class, wildcardTypeUpperBounded( new Type[] { Number.class } ) );
	}

	/**
	 * Tests that upper bounded wildcards are assignable to supertypes of their upper bound.
	 *
	 * {@literal Number <: ? extends Integer}
	 */
	@Test
	public void isAssignableWithBoundSupertypeFromUpperBoundedWildcardType() {
		assertAssignable( Number.class, wildcardTypeUpperBounded( new Type[] { Integer.class } ) );
	}

	/**
	 * Tests that upper bounded wildcards are not assignable to subtypes of their upper bound.
	 *
	 * {@literal Integer !<: ? extends Number}
	 */
	@Test
	public void isAssignableWithBoundSubtypeFromUpperBoundedWildcardType() {
		assertUnassignable( Integer.class, wildcardTypeUpperBounded( new Type[] { Number.class } ) );
	}

	/**
	 * Tests that lower bounded wildcards are assignable to Object.
	 *
	 * {@literal Object <: ? super Number}
	 */
	@Test
	public void isAssignableWithObjectFromLowerBoundedWildcardType() {
		assertAssignable( Object.class, wildcardTypeLowerBounded( new Type[] { Number.class } ) );
	}

	/**
	 * Tests that lower bounded wildcards are not assignable to their lower bound.
	 *
	 * {@literal Number !<: ? super Number}
	 */
	@Test
	public void isAssignableWithBoundFromLowerBoundedWildcardType() {
		assertUnassignable( Number.class, wildcardTypeLowerBounded( new Type[] { Number.class } ) );
	}

	/**
	 * Tests that lower bounded wildcards are not assignable to supertypes of their lower bound.
	 *
	 * {@literal Number !<: ? super Integer}
	 */
	@Test
	public void isAssignableWithBoundSupertypeFromLowerBoundedWildcardType() {
		assertUnassignable( Number.class, wildcardTypeLowerBounded( new Type[] { Integer.class } ) );
	}

	/**
	 * Tests that upper bounded wildcards are not assignable to subtypes of their upper bound.
	 *
	 * {@literal Integer <: ? super Number}
	 */
	@Test
	public void isAssignableWithBoundSubtypeFromLowerBoundedWildcardType() {
		assertUnassignable( Integer.class, wildcardTypeLowerBounded( new Type[] { Number.class } ) );
	}

	@Test(expectedExceptions = NullPointerException.class)
	public void isInstanceWithNullType() {
		TypeHelper.isInstance( null, 123 );
	}

	@Test
	public void isInstanceWithClass() {
		assertTrue( TypeHelper.isInstance( Integer.class, 123 ) );
	}

	@Test
	public void isInstanceWithClassAndSubclass() {
		assertTrue( TypeHelper.isInstance( Number.class, 123 ) );
	}

	@Test
	public void isInstanceWithClassAndSuperclass() {
		assertFalse( TypeHelper.isInstance( Number.class, new Object() ) );
	}

	@Test
	public void isInstanceWithClassAndDisjointClass() {
		assertFalse( TypeHelper.isInstance( Integer.class, 123L ) );
	}

	@Test
	public void isInstanceWithClassAndNull() {
		assertFalse( TypeHelper.isInstance( Integer.class, null ) );
	}

	@Test
	public void isInstanceWithClassArray() {
		assertTrue( TypeHelper.isInstance( Integer[].class, new Integer[0] ) );
	}

	@Test
	public void isInstanceWithClassArrayAndSubclassArray() {
		assertTrue( TypeHelper.isInstance( Number[].class, new Integer[0] ) );
	}

	@Test
	public void isInstanceWithClassArrayAndSuperclassArray() {
		assertFalse( TypeHelper.isInstance( Number[].class, new Object[0] ) );
	}

	@Test
	public void isInstanceWithClassArrayAndDisjointClass() {
		assertFalse( TypeHelper.isInstance( Integer[].class, new Long[0] ) );
	}

	@Test
	public void isArrayWithNull() {
		assertFalse( TypeHelper.isArray( null ) );
	}

	@Test
	public void isArrayWithClass() {
		assertFalse( TypeHelper.isArray( Integer.class ) );
	}

	@Test
	public void isArrayWithClassArray() {
		assertTrue( TypeHelper.isArray( Integer[].class ) );
	}

	@Test
	public void isArrayWithGenericArrayType() {
		assertTrue( TypeHelper.isArray( TypeHelper.genericArrayType( Integer.class ) ) );
	}

	@Test
	public void isArrayWithParameterizedType() {
		assertFalse( TypeHelper.isArray( TypeHelper.parameterizedType( List.class, Integer.class ) ) );
	}

	@Test
	public void testTypeDiscovery() {
		List<ConstraintValidatorDescriptor<Positive>> validatorDescriptors = new ArrayList<>();
		validatorDescriptors.add( ConstraintValidatorDescriptor.forClass( PositiveConstraintValidator.class, Positive.class ) );
		Map<Type, ConstraintValidatorDescriptor<Positive>> validatorsTypes = TypeHelper
				.getValidatorTypes( Positive.class, validatorDescriptors );

		assertEquals( validatorsTypes.get( Integer.class ).getValidatorClass(), PositiveConstraintValidator.class );
		assertNull( validatorsTypes.get( String.class ) );
	}

	@Test
	@TestForIssue(jiraKey = "HV-1032")
	public void testTypeHelperDoesntGoIntoInfiniteLoop() {
		Type parentEntityType = ChildEntity.class.getGenericSuperclass();
		ParameterizedType childEntityType = TypeHelper.parameterizedType( ChildEntity.class, ChildEntity.class.getTypeParameters() );

		assertTrue( TypeHelper.isAssignable( parentEntityType, childEntityType ) );
	}

	private static void assertAsymmetricallyAssignable(Type supertype, Type type) {
		assertAssignable( supertype, type );
		assertUnassignable( type, supertype );
	}

	private static void assertAssignable(Type supertype, Type type) {
		assertTrue( TypeHelper.isAssignable( supertype, type ), "Expected " + type + " assignable to " + supertype );
	}

	private static void assertUnassignable(Type supertype, Type type) {
		assertFalse(
				TypeHelper.isAssignable( supertype, type ), "Expected " + type + " not assignable to " + supertype
		);
	}

	private static WildcardType wildcardTypeUpperBounded(final Type[] upperBounds) {
		return wildcardType( upperBounds, DEFAULT_LOWER_BOUNDS );
	}

	private static WildcardType wildcardTypeLowerBounded(final Type[] lowerBounds) {
		return wildcardType( DEFAULT_UPPER_BOUNDS, lowerBounds );
	}

	private static WildcardType wildcardType() {
		return wildcardType( DEFAULT_UPPER_BOUNDS, DEFAULT_LOWER_BOUNDS );
	}

	private static WildcardType wildcardType(final Type[] upperBounds, final Type[] lowerBounds) {
		return new WildcardType() {

			@Override
			public Type[] getUpperBounds() {
				return upperBounds;
			}

			@Override
			public Type[] getLowerBounds() {
				return lowerBounds;
			}
		};
	}

	private static TypeVariable<GenericDeclaration> typeVariable(final GenericDeclaration declaration,
			final String name,
			final Type... bounds) {

		Class<?>[] interfaces = { TypeVariable.class };

		// HV-871 Implementing TypeVariable via a dynamic proxy to ensure this code can be compiled with Java 7 and 8;
		// New methods have been added to the TypeVariable interface in Java 8; To ensure compatibility with Java 7, we
		// don't directly implement these as they use types which themselves have been added in Java 8
		@SuppressWarnings("unchecked")
		TypeVariable<GenericDeclaration> typeVariable = (TypeVariable<GenericDeclaration>) Proxy.newProxyInstance(
				TypeHelperTest.class.getClassLoader(),
				interfaces,
				new TypeVariableImpl( bounds, declaration, name )
		);

		return typeVariable;
	}

	private static class TypeVariableImpl implements InvocationHandler {

		private final Type[] bounds;
		GenericDeclaration declaration;
		String name;

		public TypeVariableImpl(Type[] bounds, GenericDeclaration declaration, String name) {
			this.bounds = bounds;
			this.declaration = declaration;
			this.name = name;
		}

		public Type[] getBounds() {
			if ( bounds == null || bounds.length == 0 ) {
				return new Type[] { Object.class };
			}
			return bounds;
		}

		public GenericDeclaration getGenericDeclaration() {
			return declaration;
		}

		public String getName() {
			return name;
		}

		@Override
		public String toString() {
			return "TypeVariableImpl [bounds=" + Arrays.toString( bounds )
					+ ", declaration=" + declaration + ", name=" + name + "]";
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if ( "getBounds".equals( method.getName() ) ) {
			 	return getBounds();
			}
			else if ( "getGenericDeclaration".equals( method.getName() ) ) {
				return getGenericDeclaration();
			}
			else if ( "getName".equals( method.getName() ) ) {
				return getName();
			}
			else if ( "toString".equals( method.getName() ) ) {
				return toString();
			}
			else {
				throw new UnsupportedOperationException( "Method " + method + " is not implemented." );
			}
		}
	}
}
