/******************************************************************************* * Copyright (c) 2010-present Sonatype, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stuart McCulloch (Sonatype, Inc.) - initial API and implementation *******************************************************************************/ package org.eclipse.sisu.inject; 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.util.AbstractMap; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import com.google.inject.ImplementedBy; import com.google.inject.ProvidedBy; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.util.Types; import junit.framework.TestCase; public class TypeArgumentsTest extends TestCase { static TypeLiteral<Object> OBJECT_TYPE = TypeLiteral.get( Object.class ); static TypeLiteral<String> STRING_TYPE = TypeLiteral.get( String.class ); static TypeLiteral<Float> FLOAT_TYPE = TypeLiteral.get( Float.class ); static TypeLiteral<Short> SHORT_TYPE = TypeLiteral.get( Short.class ); static TypeLiteral<Number> NUMBER_TYPE = TypeLiteral.get( Number.class ); @SuppressWarnings( "rawtypes" ) List rawList; List<Short> shortList; List<?> wildcardList; List<? extends String> wildcardStringList; @SuppressWarnings( "rawtypes" ) Map rawMap; Map<String, Float> stringFloatMap; Map<?, ?> wildcardMap; Map<? extends Float, ? extends Short> wildcardFloatShortMap; interface CallableNumber<T extends Number> extends Callable<T> { } public void testTypeArguments() { TypeLiteral<?>[] types; assertEquals( OBJECT_TYPE, TypeArguments.get( getFieldType( "rawList" ), 0 ) ); types = TypeArguments.get( getFieldType( "rawList" ) ); assertEquals( 0, types.length ); assertEquals( OBJECT_TYPE, TypeArguments.get( getFieldType( "rawMap" ), 0 ) ); assertEquals( OBJECT_TYPE, TypeArguments.get( getFieldType( "rawMap" ), 1 ) ); types = TypeArguments.get( getFieldType( "rawMap" ) ); assertEquals( 0, types.length ); assertEquals( SHORT_TYPE, TypeArguments.get( getFieldType( "shortList" ), 0 ) ); types = TypeArguments.get( getFieldType( "shortList" ) ); assertEquals( 1, types.length ); assertEquals( SHORT_TYPE, types[0] ); assertEquals( STRING_TYPE, TypeArguments.get( getFieldType( "stringFloatMap" ), 0 ) ); assertEquals( FLOAT_TYPE, TypeArguments.get( getFieldType( "stringFloatMap" ), 1 ) ); types = TypeArguments.get( getFieldType( "stringFloatMap" ) ); assertEquals( 2, types.length ); assertEquals( STRING_TYPE, types[0] ); assertEquals( FLOAT_TYPE, types[1] ); assertEquals( OBJECT_TYPE, TypeArguments.get( getFieldType( "wildcardList" ), 0 ) ); types = TypeArguments.get( getFieldType( "wildcardList" ) ); assertEquals( 1, types.length ); assertEquals( OBJECT_TYPE, types[0] ); assertEquals( OBJECT_TYPE, TypeArguments.get( getFieldType( "wildcardMap" ), 0 ) ); assertEquals( OBJECT_TYPE, TypeArguments.get( getFieldType( "wildcardMap" ), 1 ) ); types = TypeArguments.get( getFieldType( "wildcardMap" ) ); assertEquals( 2, types.length ); assertEquals( OBJECT_TYPE, types[0] ); assertEquals( OBJECT_TYPE, types[1] ); assertEquals( STRING_TYPE, TypeArguments.get( getFieldType( "wildcardStringList" ), 0 ) ); types = TypeArguments.get( getFieldType( "wildcardStringList" ) ); assertEquals( 1, types.length ); assertEquals( STRING_TYPE, types[0] ); assertEquals( FLOAT_TYPE, TypeArguments.get( getFieldType( "wildcardFloatShortMap" ), 0 ) ); assertEquals( SHORT_TYPE, TypeArguments.get( getFieldType( "wildcardFloatShortMap" ), 1 ) ); types = TypeArguments.get( getFieldType( "wildcardFloatShortMap" ) ); assertEquals( 2, types.length ); assertEquals( FLOAT_TYPE, types[0] ); assertEquals( SHORT_TYPE, types[1] ); final TypeLiteral<?> genericSuperType = TypeLiteral.get( CallableNumber.class ).getSupertype( Callable.class ); assertEquals( NUMBER_TYPE, TypeArguments.get( genericSuperType, 0 ) ); types = TypeArguments.get( genericSuperType ); assertEquals( 1, types.length ); assertEquals( NUMBER_TYPE, types[0] ); } @SuppressWarnings( "rawtypes" ) List[] rawListArray; List<Short>[] shortListArray; List<?>[] wildcardListArray; List<? extends String>[] wildcardStringListArray; @SuppressWarnings( "rawtypes" ) Map[] rawMapArray; Map<String, Float>[] stringFloatMapArray; Map<?, ?>[] wildcardMapArray; Map<? extends Float, ? extends Short>[] wildcardFloatShortMapArray; List<String[]> stringArrayList; public void testComponentType() { TypeLiteral<?>[] types; types = TypeArguments.get( getFieldType( "rawListArray" ) ); assertEquals( getFieldType( "rawList" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "rawListArray" ), 0 ) ); assertEquals( List.class, types[0].getType() ); types = TypeArguments.get( getFieldType( "rawMapArray" ) ); assertEquals( getFieldType( "rawMap" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "rawMapArray" ), 0 ) ); assertEquals( Map.class, types[0].getType() ); types = TypeArguments.get( getFieldType( "shortListArray" ) ); assertEquals( getFieldType( "shortList" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "shortListArray" ), 0 ) ); assertEquals( Types.listOf( Short.class ), types[0].getType() ); types = TypeArguments.get( getFieldType( "stringFloatMapArray" ) ); assertEquals( getFieldType( "stringFloatMap" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "stringFloatMapArray" ), 0 ) ); assertEquals( Types.mapOf( String.class, Float.class ), types[0].getType() ); types = TypeArguments.get( getFieldType( "wildcardListArray" ) ); assertEquals( getFieldType( "wildcardList" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "wildcardListArray" ), 0 ) ); assertEquals( Types.listOf( Types.subtypeOf( Object.class ) ), types[0].getType() ); types = TypeArguments.get( getFieldType( "wildcardMapArray" ) ); assertEquals( getFieldType( "wildcardMap" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "wildcardMapArray" ), 0 ) ); assertEquals( Types.mapOf( Types.subtypeOf( Object.class ), Types.subtypeOf( Object.class ) ), types[0].getType() ); types = TypeArguments.get( getFieldType( "wildcardStringListArray" ) ); assertEquals( getFieldType( "wildcardStringList" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "wildcardStringListArray" ), 0 ) ); assertEquals( Types.listOf( Types.subtypeOf( String.class ) ), types[0].getType() ); types = TypeArguments.get( getFieldType( "wildcardFloatShortMapArray" ) ); assertEquals( getFieldType( "wildcardFloatShortMap" ), types[0] ); assertEquals( types[0], TypeArguments.get( getFieldType( "wildcardFloatShortMapArray" ), 0 ) ); assertEquals( Types.mapOf( Types.subtypeOf( Float.class ), Types.subtypeOf( Short.class ) ), types[0].getType() ); types = TypeArguments.get( TypeArguments.get( getFieldType( "stringArrayList" ) )[0] ); assertEquals( STRING_TYPE, types[0] ); assertEquals( types[0], TypeArguments.get( TypeArguments.get( getFieldType( "stringArrayList" ), 0 ), 0 ) ); } public void testTypeArgumentRangeChecks() { try { TypeArguments.get( getFieldType( "stringFloatMap" ), -1 ); fail( "Expected IndexOutOfBoundsException" ); } catch ( final IndexOutOfBoundsException e ) { } try { TypeArguments.get( getFieldType( "stringFloatMap" ), 2 ); fail( "Expected IndexOutOfBoundsException" ); } catch ( final IndexOutOfBoundsException e ) { } try { TypeArguments.get( getFieldType( "wildcardStringListArray" ), -1 ); fail( "Expected IndexOutOfBoundsException" ); } catch ( final IndexOutOfBoundsException e ) { } try { TypeArguments.get( getFieldType( "wildcardStringListArray" ), 1 ); fail( "Expected IndexOutOfBoundsException" ); } catch ( final IndexOutOfBoundsException e ) { } } static class CallableImpl<T> implements Callable<T> { public T call() throws Exception { return null; } } static class CallableNumberImpl<T extends Number> implements CallableNumber<T> { public T call() throws Exception { return null; } } @SuppressWarnings( "rawtypes" ) static class CallableListImpl implements Callable<List> { public List call() throws Exception { return null; } } @SuppressWarnings( "rawtypes" ) public void testIsAssignableFrom() { // === simple types === assertTrue( TypeArguments.isAssignableFrom( TypeLiteral.get( Object.class ), TypeLiteral.get( String.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( TypeLiteral.get( Number.class ), TypeLiteral.get( Short.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( TypeLiteral.get( Collection.class ), TypeLiteral.get( Set.class ) ) ); // === generic types === assertFalse( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<Collection>>() { }, TypeLiteral.get( CallableListImpl.class ) ) ); // not assignable since no wild-card assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<List>>() { }, TypeLiteral.get( CallableListImpl.class ) ) ); assertFalse( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<String>>() { }, TypeLiteral.get( CallableListImpl.class ) ) ); // === unbound type-variables === assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable>() { }, TypeLiteral.get( Callable.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable>() { }, TypeLiteral.get( CallableImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<String>>() { }, TypeLiteral.get( CallableImpl.class ) ) ); assertFalse( TypeArguments.isAssignableFrom( new TypeLiteral<CallableImpl>() { }, TypeLiteral.get( Callable.class ) ) ); // === bound type-variables === assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<CallableNumber>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<CallableNumber<Number>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<CallableNumber<Float>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); assertFalse( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<String>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); // mismatched type-bounds // === unbound wild-cards === assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<?>>() { }, TypeLiteral.get( CallableImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<?>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<CallableNumber<?>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); // === bound wild-cards === assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<? extends Collection>>() { }, TypeLiteral.get( CallableListImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<? extends Number>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); assertTrue( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<? extends Float>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); assertFalse( TypeArguments.isAssignableFrom( new TypeLiteral<Callable<? extends String>>() { }, TypeLiteral.get( CallableNumberImpl.class ) ) ); // === array types === assertTrue( TypeArguments.isAssignableFrom( TypeLiteral.get( Types.arrayOf( Object.class ) ), TypeLiteral.get( Types.arrayOf( String.class ) ) ) ); assertTrue( TypeArguments.isAssignableFrom( TypeLiteral.get( Types.arrayOf( Number.class ) ), TypeLiteral.get( Types.arrayOf( Float.class ) ) ) ); // === mismatched types === assertFalse( TypeArguments.isAssignableFrom( TypeLiteral.get( Types.arrayOf( Object.class ) ), TypeLiteral.get( Types.listOf( Object.class ) ) ) ); assertFalse( TypeArguments.isAssignableFrom( TypeLiteral.get( Types.listOf( Object.class ) ), TypeLiteral.get( Types.arrayOf( Object.class ) ) ) ); // === corner case === final Type T = (Type) Proxy.newProxyInstance( getClass().getClassLoader(), new Class<?>[] { TypeVariable.class }, new InvocationHandler() { public Object invoke( final Object proxy, final Method method, final Object[] args ) throws Throwable { final String name = method.getName(); if ( "getBounds".equals( name ) ) { return new Type[] { String.class }; } if ( "getName".equals( name ) ) { return "T"; } if ( "hashCode".equals( name ) ) { return hashCode(); } if ( "equals".equals( name ) ) { return equals( args[0] ); } return null; } } ); final Type callableT = (Type) Proxy.newProxyInstance( getClass().getClassLoader(), new Class<?>[] { ParameterizedType.class }, new InvocationHandler() { public Object invoke( final Object proxy, final Method method, final Object[] args ) throws Throwable { final String name = method.getName(); if ( "getActualTypeArguments".equals( name ) ) { return new Type[] { T }; } if ( "getRawType".equals( name ) ) { return Callable.class; } if ( "hashCode".equals( name ) ) { return hashCode(); } if ( "equals".equals( name ) ) { return equals( args[0] ); } return null; } } ); assertFalse( TypeArguments.isAssignableFrom( TypeLiteral.get( callableT ), TypeLiteral.get( Callable.class ) ) ); assertFalse( TypeArguments.isAssignableFrom( TypeLiteral.get( callableT ), TypeLiteral.get( CallableNumberImpl.class ) ) ); } public void testIsConcrete() { assertFalse( TypeArguments.isConcrete( Map.class ) ); assertFalse( TypeArguments.isConcrete( AbstractMap.class ) ); assertTrue( TypeArguments.isConcrete( HashMap.class ) ); assertFalse( TypeArguments.isConcrete( new TypeLiteral<Map<String, String>>() { } ) ); assertFalse( TypeArguments.isConcrete( new TypeLiteral<AbstractMap<String, String>>() { } ) ); assertTrue( TypeArguments.isConcrete( new TypeLiteral<HashMap<String, String>>() { } ) ); } @ImplementedBy( Object.class ) static interface Implicit1<T> { } static class SomeProvider implements Provider<Object> { public Object get() { return null; } } @ProvidedBy( SomeProvider.class ) static interface Implicit2<T> { } public void testIsImplicit() { assertFalse( TypeArguments.isImplicit( Map.class ) ); assertFalse( TypeArguments.isImplicit( AbstractMap.class ) ); assertTrue( TypeArguments.isImplicit( HashMap.class ) ); assertFalse( TypeArguments.isImplicit( new TypeLiteral<Map<String, String>>() { } ) ); assertFalse( TypeArguments.isImplicit( new TypeLiteral<AbstractMap<String, String>>() { } ) ); assertTrue( TypeArguments.isImplicit( new TypeLiteral<HashMap<String, String>>() { } ) ); assertTrue( TypeArguments.isImplicit( Implicit1.class ) ); assertTrue( TypeArguments.isImplicit( Implicit2.class ) ); assertTrue( TypeArguments.isImplicit( new TypeLiteral<Implicit1<String>>() { } ) ); assertTrue( TypeArguments.isImplicit( new TypeLiteral<Implicit2<String>>() { } ) ); } private static TypeLiteral<?> getFieldType( final String name ) { try { return TypeLiteral.get( TypeArgumentsTest.class.getDeclaredField( name ).getGenericType() ); } catch ( final NoSuchFieldException e ) { throw new IllegalArgumentException( "Unknown test field " + name ); } } }