/******************************************************************************* * 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() ); } }