blob: 99691a277005547be3251aac1a17c9ae9d5d5249 [file] [log] [blame]
/*******************************************************************************
* 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 );
}
}
}