blob: 6997914577139e066e3787491b7250e611f523b6 [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.plexus;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.sisu.inject.DeferredClass;
import org.eclipse.sisu.space.ClassSpace;
import org.eclipse.sisu.space.URLClassSpace;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.ImplementedBy;
import com.google.inject.Injector;
import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.name.Names;
import junit.framework.TestCase;
public class PlexusRequirementTest
extends TestCase
{
@Inject
Component1 component;
@Inject
Injector injector;
@Override
protected void setUp()
{
Guice.createInjector( new AbstractModule()
{
@Override
@SuppressWarnings( "unchecked" )
protected void configure()
{
final ClassSpace space = new URLClassSpace( TestCase.class.getClassLoader() );
final DeferredClass<A> deferA = (DeferredClass<A>) space.deferLoadClass( BrokenAImpl.class.getName() );
install( new PlexusDateTypeConverter() );
bind( PlexusBeanLocator.class ).to( DefaultPlexusBeanLocator.class );
bind( PlexusBeanConverter.class ).to( PlexusXmlBeanConverter.class );
bind( A.class ).annotatedWith( Names.named( "AA" ) ).to( AAImpl.class );
bind( A.class ).annotatedWith( Names.named( "broken" ) ).toProvider( deferA.asProvider() );
bind( A.class ).annotatedWith( Names.named( "AB" ) ).to( ABImpl.class );
bind( A.class ).to( AImpl.class ).in( Scopes.SINGLETON );
bind( A.class ).annotatedWith( Names.named( "AC" ) ).to( ACImpl.class );
bind( B.class ).annotatedWith( Names.named( "B" ) ).to( BImpl.class );
bind( D.class ).annotatedWith( Names.named( "" ) ).to( DImpl.class );
install( new PlexusBindingModule( null, new PlexusBeanModule()
{
public PlexusBeanSource configure( final Binder binder )
{
binder.bind( Alpha.class ).to( AlphaImpl.class ).in( Scopes.SINGLETON );
binder.bind( Omega.class ).to( OmegaImpl.class ).in( Scopes.SINGLETON );
final DeferredClass<Gamma> gammaProvider =
(DeferredClass<Gamma>) space.deferLoadClass( "some-broken-class" ).asProvider();
binder.bind( Gamma.class ).toProvider( gammaProvider.asProvider() ).in( Scopes.SINGLETON );
return null;
}
}, new PlexusAnnotatedBeanModule( null, null ) ) );
requestInjection( PlexusRequirementTest.this );
}
} );
}
@ImplementedBy( AImpl.class )
interface A
{
}
interface B
{
}
interface C
{
}
interface D
{
}
static class AImpl
implements A
{
}
static class BImpl
implements B
{
}
static class DImpl
implements D
{
}
static class AAImpl
extends AImpl
{
}
static class ABImpl
extends AImpl
{
}
static class ACImpl
extends AImpl
{
}
static class BrokenAImpl
extends AImpl
{
public BrokenAImpl( @SuppressWarnings( "unused" ) final MissingClass missing )
{
}
}
static class MissingClass
{
}
@Component( role = Component1.class, instantiationStrategy = Strategies.PER_LOOKUP )
static class Component1
{
@Requirement
A testField;
A testSetter;
@Requirement( hints = { "default" } )
void setValue( final A a )
{
testSetter = a;
}
@Requirement( role = A.class )
Object testRole;
@Requirement( hint = "AB", optional = true )
A testHint;
@Requirement( hint = "MISSING", optional = true )
A testOptional = new ACImpl();
@Requirement( role = A.class )
Map<String, ?> testMap;
@Requirement( hints = { "AC", "AB" } )
Map<String, A> testSubMap;
@Requirement
Map<String, C> testEmptyMap;
@Requirement( role = A.class )
List<?> testList;
@Requirement( hints = { "AC", "AA" } )
List<? extends A> testSubList;
@Requirement
List<C> testEmptyList;
@Requirement( role = A.class )
Collection<?> testCollection;
@Requirement( role = A.class )
Iterable<?> testIterable;
@Requirement( role = A.class )
Set<?> testSet;
@Requirement
B testWildcard;
@Requirement( optional = true )
C optionalRequirement;
}
@Component( role = Component2.class )
static class Component2
{
@Requirement
void testZeroArgSetter()
{
throw new RuntimeException();
}
}
@Component( role = Component3.class )
static class Component3
{
@Requirement
@SuppressWarnings( "unused" )
void testMultiArgSetter( final A a1, final A a2 )
{
throw new RuntimeException();
}
}
@Component( role = Component4.class )
static class Component4
{
@Requirement
C testMissingRequirement;
}
@Component( role = Component5.class )
static class Component5
{
@Requirement( hint = "B!" )
B testNoSuchHint;
}
@Component( role = Component6.class )
static class Component6
{
@Requirement( hints = { "AA", "AZ", "A!" } )
Map<String, B> testNoSuchHint;
}
@Component( role = Component7.class )
static class Component7
{
@Requirement( hints = { "AA", "AZ", "A!" } )
List<C> testNoSuchHint;
}
@Component( role = Component8.class )
static class Component8
{
@Requirement( hints = { "" } )
List<A> testWildcardHint;
}
@Component( role = Component9.class )
static class Component9
{
@Requirement( hint = "default" )
B testNoDefault;
}
public void testRepeatInjection()
{
final Component1 duplicate = injector.getInstance( Component1.class );
assertSame( component.testField, duplicate.testField );
assertSame( component.testSetter, duplicate.testSetter );
assertSame( component.testRole, duplicate.testRole );
}
public void testSingleRequirement()
{
assertEquals( AImpl.class, component.testField.getClass() );
assertEquals( AImpl.class, component.testSetter.getClass() );
assertEquals( AImpl.class, component.testRole.getClass() );
assertEquals( ABImpl.class, component.testHint.getClass() );
assertEquals( ACImpl.class, component.testOptional.getClass() );
assertEquals( BImpl.class, component.testWildcard.getClass() );
}
public void testRequirementMap()
{
assertEquals( 5, component.testMap.size() );
assertEquals( 0, component.testEmptyMap.size() );
// check mapping
assertEquals( AImpl.class, component.testMap.get( "default" ).getClass() );
assertEquals( AAImpl.class, component.testMap.get( "AA" ).getClass() );
assertEquals( ABImpl.class, component.testMap.get( "AB" ).getClass() );
assertEquals( ACImpl.class, component.testMap.get( "AC" ).getClass() );
// check key ordering is same as original map-binder
final Iterator<String> keys = component.testMap.keySet().iterator();
assertEquals( Hints.DEFAULT_HINT, keys.next() );
assertEquals( "AA", keys.next() );
assertEquals( "broken", keys.next() );
assertEquals( "AB", keys.next() );
assertEquals( "AC", keys.next() );
assertFalse( keys.hasNext() );
// check value ordering is same as original map-binder
final Iterator<?> values = component.testMap.values().iterator();
assertEquals( AImpl.class, values.next().getClass() );
assertEquals( AAImpl.class, values.next().getClass() );
try
{
values.next();
fail( "Expected NoClassDefFoundError" );
}
catch ( final NoClassDefFoundError e )
{
}
assertEquals( ABImpl.class, values.next().getClass() );
assertEquals( ACImpl.class, values.next().getClass() );
assertFalse( values.hasNext() );
}
public void testRequirementSubMap()
{
assertEquals( 2, component.testSubMap.size() );
// check mapping
assertEquals( ABImpl.class, component.testSubMap.get( "AB" ).getClass() );
assertEquals( ACImpl.class, component.testSubMap.get( "AC" ).getClass() );
// check key ordering is same as original map-binder
final Iterator<String> keys = component.testSubMap.keySet().iterator();
assertEquals( "AC", keys.next() );
assertEquals( "AB", keys.next() );
assertFalse( keys.hasNext() );
// check value ordering is same as hints
final Iterator<A> values = component.testSubMap.values().iterator();
assertEquals( ACImpl.class, values.next().getClass() );
assertEquals( ABImpl.class, values.next().getClass() );
assertFalse( values.hasNext() );
}
public void testRequirementList()
{
assertEquals( 5, component.testList.size() );
assertEquals( 0, component.testEmptyList.size() );
// check ordering is same as original map-binder
final Iterator<?> i = component.testList.iterator();
assertEquals( AImpl.class, i.next().getClass() );
assertEquals( AAImpl.class, i.next().getClass() );
try
{
i.next();
fail( "Expected NoClassDefFoundError" );
}
catch ( final NoClassDefFoundError e )
{
}
assertEquals( ABImpl.class, i.next().getClass() );
assertEquals( ACImpl.class, i.next().getClass() );
assertFalse( i.hasNext() );
}
public void testRequirementSubList()
{
assertEquals( 2, component.testSubList.size() );
// check ordering is same as hints
final Iterator<? extends A> i = component.testSubList.iterator();
assertEquals( ACImpl.class, i.next().getClass() );
assertEquals( AAImpl.class, i.next().getClass() );
assertFalse( i.hasNext() );
}
public void testRequirementCollection()
{
assertEquals( 5, component.testCollection.size() );
// check ordering is same as original map-binder
final Iterator<?> i = component.testCollection.iterator();
assertEquals( AImpl.class, i.next().getClass() );
assertEquals( AAImpl.class, i.next().getClass() );
try
{
i.next();
fail( "Expected NoClassDefFoundError" );
}
catch ( final NoClassDefFoundError e )
{
}
assertEquals( ABImpl.class, i.next().getClass() );
assertEquals( ACImpl.class, i.next().getClass() );
assertFalse( i.hasNext() );
}
public void testRequirementIterable()
{
// check ordering is same as original map-binder
final Iterator<?> i = component.testIterable.iterator();
assertEquals( AImpl.class, i.next().getClass() );
assertEquals( AAImpl.class, i.next().getClass() );
try
{
i.next();
fail( "Expected NoClassDefFoundError" );
}
catch ( final NoClassDefFoundError e )
{
}
assertEquals( ABImpl.class, i.next().getClass() );
assertEquals( ACImpl.class, i.next().getClass() );
assertFalse( i.hasNext() );
}
public void testRequirementSet()
{
assertEquals( 5, component.testSet.size() );
// check ordering is same as original map-binder
final Iterator<?> i = component.testSet.iterator();
assertEquals( AImpl.class, i.next().getClass() );
assertEquals( AAImpl.class, i.next().getClass() );
try
{
i.next();
fail( "Expected NoClassDefFoundError" );
}
catch ( final NoClassDefFoundError e )
{
}
assertEquals( ABImpl.class, i.next().getClass() );
assertEquals( ACImpl.class, i.next().getClass() );
assertFalse( i.hasNext() );
}
public void testZeroArgSetterError()
{
injector.getInstance( Component2.class );
}
public void testMultiArgSetterError()
{
injector.getInstance( Component3.class );
}
public void testMissingRequirement()
{
try
{
injector.getInstance( Component4.class );
fail( "Expected error for missing requirement" );
}
catch ( final ProvisionException e )
{
}
}
public void testNoSuchHint()
{
try
{
injector.getInstance( Component5.class );
fail( "Expected error for no such hint" );
}
catch ( final ProvisionException e )
{
}
}
public void testNoSuchMapHint()
{
try
{
injector.getInstance( Component6.class ).testNoSuchHint.toString();
fail( "Expected error for no such hint" );
}
catch ( final ProvisionException e )
{
}
}
public void testNoSuchListHint()
{
try
{
injector.getInstance( Component7.class ).testNoSuchHint.toString();
fail( "Expected error for no such hint" );
}
catch ( final ProvisionException e )
{
}
}
public void testWildcardHint()
{
final List<A> testList = injector.getInstance( Component8.class ).testWildcardHint;
assertEquals( 5, testList.size() );
// check ordering is same as original map-binder
final Iterator<?> i = testList.iterator();
assertEquals( AImpl.class, i.next().getClass() );
assertEquals( AAImpl.class, i.next().getClass() );
try
{
i.next();
fail( "Expected NoClassDefFoundError" );
}
catch ( final NoClassDefFoundError e )
{
}
assertEquals( ABImpl.class, i.next().getClass() );
assertEquals( ACImpl.class, i.next().getClass() );
assertFalse( i.hasNext() );
}
public void testNoDefault()
{
try
{
injector.getInstance( Component9.class );
fail( "Expected error for missing default requirement" );
}
catch ( final ProvisionException e )
{
}
}
interface Alpha
{
}
interface Omega
{
}
interface Gamma
{
}
@Component( role = Alpha.class )
static class AlphaImpl
implements Alpha
{
@Requirement
Omega omega;
}
@Component( role = Omega.class )
static class OmegaImpl
implements Omega
{
@Requirement
Alpha alpha;
}
@Inject
Alpha alpha;
@Inject
Omega omega;
public void testCircularity()
{
assertNotNull( ( (OmegaImpl) omega ).alpha );
assertNotNull( ( (AlphaImpl) alpha ).omega );
assertSame( alpha, ( (OmegaImpl) omega ).alpha );
assertSame( omega, ( (AlphaImpl) alpha ).omega );
}
public void testBadDeferredRole()
{
try
{
injector.getInstance( Gamma.class );
fail( "Expected ProvisionException" );
}
catch ( final ProvisionException e )
{
}
}
public void testPlexus121Compatibility()
throws Exception
{
final List<URL> urls = new ArrayList<URL>();
urls.add( new File( "target/dependency/plexus-component-annotations-1.2.1.jar" ).toURI().toURL() );
Collections.addAll( urls, new URLClassSpace( getClass().getClassLoader() ).getURLs() );
// check binding works with Plexus 1.2.1 annotations: @Requirement does not have optional setting
final ClassLoader legacyLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null )
{
@Override
protected synchronized Class<?> loadClass( final String name, final boolean resolve )
throws ClassNotFoundException
{
if ( name.contains( "cobertura" ) )
{
return PlexusRequirementTest.class.getClassLoader().loadClass( name );
}
return super.loadClass( name, resolve );
}
};
legacyLoader.loadClass( SimpleRequirementExample.class.getName() ).newInstance();
}
@SuppressWarnings( "unchecked" )
static <S, T extends S> DeferredClass<T> defer( final Class<S> clazz )
{
return (DeferredClass<T>) new URLClassSpace( TestCase.class.getClassLoader() ).deferLoadClass( clazz.getName() );
}
}