package info.ebenoit.ebul.cmp;


import org.junit.Test;

import info.ebenoit.ebul.func.ThrowingBiConsumer;
import info.ebenoit.ebul.func.ThrowingConsumer;
import test.inherited.CBase;
import test.inherited.CChild;
import test.inherited.CChildOfAbstract;
import test.interfaces.CImplementation;
import test.simple.CSimple;

import static org.junit.Assert.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;



/**
 * Tests for the {@link NewComponentInfo} class
 * <p>
 * Note: since most methods in this class are just accessors, they are not tested.
 */
public class TestNewComponentInfo
{
	/** Class that has no default constructor */
	private static class TestFail1
	{
		private TestFail1( int arg )
		{
			// EMPTY
		}
	}

	/** Test component with no information */
	private static class TestCmp1
	{
		// EMPTY
	}

	/** Test component with a name, autostart and dependency information */
	@Component( "Test component" )
	@Autostart
	@Dependencies( {
			"TestCmp1" , "TestCmp3"
	} )
	private static class TestCmp2
	{
		// EMPTY
	}

	/** Test component acting as a driver for another */
	@DriverFor( "TestCmp1" )
	private static class TestCmp3
	{
		// EMPTY
	}

	/** Test component that should inherit dependencies */
	@Dependencies( "TestCmp5" )
	private static class TestCmp4
			extends TestCmp2
	{
		// EMPTY
	}

	/** Test anonymous component */
	@Anonymous
	private static class TestCmpAnon
	{
		// EMPTY
	}

	/** Test anonymous component with empty {@link Component} annotation */
	@Anonymous
	@Component
	private static class TestCmpAnonComponent
	{
		// EMPTY
	}

	/** Test anonymous component that has a name provider (invalid!) */
	@Anonymous
	private static class TestCmpAnonPN
	{

		@NameProvider
		public String getComponentName( )
		{
			return null;
		}

	}

	/** Test anonymous component that has a specified name (invalid!) */
	@Component( "Fail!" )
	@Anonymous
	private static class TestCmpAnonNamed
	{
		// EMPTY
	}

	/** Test component with a name provider */
	private static class TestCmpPN
	{

		@NameProvider
		public String getComponentName( )
		{
			return "TestCmpPN";
		}

	}

	/** Test component with a name provider and an annotation-specified name (invalid!) */
	@Component( "Fail" )
	private static class TestCmpPNFail1
	{

		@NameProvider
		public String getComponentName( )
		{
			return "TestCmpPNFail";
		}

	}

	/** Test component with a static name provider (invalid) */
	private static class TestCmpPNFail2
	{

		@NameProvider
		static public String getComponentName( )
		{
			return "TestCmpPNFail";
		}

	}

	/** Test component with a name provider that doesn't return a string (invalid) */
	private static class TestCmpPNFail3
	{

		@NameProvider
		static public Object getComponentName( )
		{
			return "TestCmpPNFail";
		}

	}

	/** Test component with a name provider that has arguments (invalid) */
	private static class TestCmpPNFail4
	{

		@NameProvider
		static public String getComponentName( String fail )
		{
			return "TestCmpPNFail";
		}

	}

	/** Test component with multiple name providers (invalid) */
	private static class TestCmpPNFail5
	{

		@NameProvider
		static public String getComponentName( )
		{
			return "TestCmpPNFail";
		}


		@NameProvider
		static public String getComponentNameToo( String fail )
		{
			return "TestCmpPNFail";
		}

	}

	/** Test component that has lifecycle methods */
	private static class TestCmp5
	{
		protected final boolean hadLifecycleAction[] = new boolean[ 4 ];


		@LifecycleMethod( LifecycleStage.INITIALISE )
		private void init( )
		{
			hadLifecycleAction[ LifecycleStage.INITIALISE.ordinal( ) ] = true;
		}


		@LifecycleMethod( LifecycleStage.START )
		private void start( )
		{
			hadLifecycleAction[ LifecycleStage.START.ordinal( ) ] = true;
		}


		@LifecycleMethod( LifecycleStage.STOP )
		private void stop( )
		{
			hadLifecycleAction[ LifecycleStage.STOP.ordinal( ) ] = true;
		}


		@LifecycleMethod( LifecycleStage.DESTROY )
		private void destroy( )
		{
			hadLifecycleAction[ LifecycleStage.DESTROY.ordinal( ) ] = true;
		}

	}

	/** Test component that has inherited and overridden lifecycle methods */
	private static class TestCmp6
			extends TestCmp5
	{
		private boolean hadLocalInitialise;


		@LifecycleMethod( LifecycleStage.INITIALISE )
		private void init( )
		{
			hadLocalInitialise = true;
		}

	}

	/** Test interface that defines a lifecycle method */
	private static interface ITest7
	{
		@LifecycleMethod( LifecycleStage.INITIALISE )
		public void init( );
	}

	/** Test component that has an interface-defined lifecycle method */
	private static class TestCmp7
			implements ITest7
	{
		private boolean initialised;


		@Override
		public void init( )
		{
			this.initialised = true;
		}

	}

	/** Test component with duplicate lifecycle methods */
	private static class TestFail2
	{

		@LifecycleMethod( LifecycleStage.INITIALISE )
		private void init1( )
		{
			// EMPTY
		}


		@LifecycleMethod( LifecycleStage.INITIALISE )
		private void init2( )
		{
			// EMPTY
		}

	}

	/** Test component with a dependency injection field using a name */
	private static class TestCmp8
	{
		@UseComponent( "TestCmp1" )
		protected TestCmp1 dependency;
	}

	/** Test component with a dependency injection field using the field's type */
	private static class TestCmp9
	{
		@UseComponent
		private TestCmp1 dependency;
	}

	/** Test component with a static dependency injection field */
	private static class TestFail3
	{
		@UseComponent
		private static TestCmp1 dependency;
	}

	/** Test component with a final dependency injection field */
	private static class TestFail4
	{
		@UseComponent
		private final TestCmp1 dependency;


		private TestFail4( )
		{
			super( );
			this.dependency = null;
		}

	}

	/** Test component with a primitive dependency injection field */
	private static class TestFail5
	{
		@UseComponent( "x" )
		private int dependency;

	}

	/** Test component with an array dependency injection field */
	private static class TestFail6
	{
		@UseComponent( "x" )
		private Object[] dependency;

	}

	/** Test component with a dependency injection method using a name */
	private static class TestCmp10
	{
		protected TestCmp1 dependency;


		@UseComponent( "TestCmp1" )
		private void inject( TestCmp1 dependency )
		{
			this.dependency = dependency;
		}
	}

	/** Test component with a dependency injection method with no name */
	private static class TestCmp11
	{
		private TestCmp1 dependency;


		@UseComponent
		private void inject( TestCmp1 dependency )
		{
			this.dependency = dependency;
		}
	}

	/** Test component with a static dependency injection method */
	private static class TestFail7
	{
		@UseComponent
		private static void inject( TestCmp1 dependency )
		{
			// EMPTY
		}
	}

	/** Test component with a dependency injection method that has no parameters */
	private static class TestFail8
	{
		@UseComponent( "x" )
		private void inject( )
		{
			// EMPTY
		}

	}

	/** Test component with a dependency injection method that has two parameters */
	private static class TestFail9
	{
		@UseComponent
		private void inject( TestCmp1 dependency , int x )
		{
			// EMPTY
		}

	}

	/** Test component with a primitive dependency injection method */
	private static class TestFail10
	{
		@UseComponent( "x" )
		private void inject( int x )
		{
			// EMPTY
		}

	}

	/** Test component with an array dependency injection field */
	private static class TestFail11
	{
		@UseComponent( "x" )
		private void inject( Object[] x )
		{
			// EMPTY
		}

	}

	/** Test component that inherits a dependency injection method */
	private static class TestCmp12
			extends TestCmp10
	{
		// EMPTY
	}

	/** Test component that inherits a dependency injection */
	private static class TestCmp13
			extends TestCmp8
	{
		// EMPTY
	}

	/** Interface that defines a dependency injection method */
	private static interface ITest14
	{
		@UseComponent( "TestCmp1" )
		public void inject( TestCmp1 dependency );
	}

	/** Test component with a dependency injection method defined in an interface */
	private static class TestCmp14
			implements ITest14
	{
		private TestCmp1 dependency;


		@Override
		public void inject( TestCmp1 dependency )
		{
			this.dependency = dependency;
		}
	}


	/** Test: {@link NewComponentInfo#addDependencyInjector(DependencyInfo, ThrowingBiConsumer)} adds a dependency */
	@Test
	public void testAddDependencyInjector( )
	{
		NewComponentInfo< Object > nci = new NewComponentInfo< Object >( new Object( ) );
		nci.addDependencyInjector( new DependencyInfo( "test" ) , ( cmp , dep ) -> {
		} );
		assertTrue( nci.getDependencies( ).contains( new DependencyInfo( "test" ) ) );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class with no default constructor */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail1( )
	{
		NewComponentInfo.fromClass( TestFail1.class );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class with no information */
	@Test
	public void testFromClass1( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmp1.class );
		assertEquals( "" , nci.getName( ) );
		assertFalse( nci.getAutostart( ) );
		assertTrue( nci.getDependencies( ).isEmpty( ) );
		assertNull( nci.getDriverFor( ) );
		assertTrue( nci.getInjectors( ).isEmpty( ) );
		assertTrue( nci.getSupplier( ).get( ) instanceof TestCmp1 );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class with a name, dependencies and autostart */
	@Test
	public void testFromClass2( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmp2.class );
		assertEquals( "Test component" , nci.getName( ) );
		assertTrue( nci.getAutostart( ) );
		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 2 , deps.size( ) );
		assertTrue( deps.contains( new DependencyInfo( "TestCmp1" ) ) );
		assertTrue( deps.contains( new DependencyInfo( "TestCmp3" ) ) );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class set to be a driver for another component */
	@Test
	public void testFromClass3( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmp3.class );
		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( new DependencyInfo( "TestCmp1" ) ) );
		assertEquals( "TestCmp1" , nci.getDriverFor( ) );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class with inherited + local dependencies */
	@Test
	public void testFromClass4( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmp4.class );
		assertNotEquals( "component name was inherited!" , "Test component" , nci.getName( ) );
		assertFalse( "autostart state was inherited!" , nci.getAutostart( ) );
		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 3 , deps.size( ) );
		assertTrue( deps.contains( new DependencyInfo( "TestCmp1" ) ) );
		assertTrue( deps.contains( new DependencyInfo( "TestCmp3" ) ) );
		assertTrue( deps.contains( new DependencyInfo( "TestCmp5" ) ) );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class with the {@link Anonymous} annotation */
	@Test
	public void testFromClassAnon( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmpAnon.class );
		assertNull( nci.getName( ) );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with the {@link Anonymous} annotation and an
	 * empty {@link Component} annotation
	 */
	@Test
	public void testFromClassAnonComponent( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmpAnonComponent.class );
		assertNull( nci.getName( ) );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with the {@link Anonymous} annotation that
	 * includes a {@link NameProvider}
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassAnonPN( )
	{
		NewComponentInfo.fromClass( TestCmpAnonPN.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with the {@link Anonymous} annotation and a
	 * {@link Component} annotation carrying a value
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassAnonNamed( )
	{
		NewComponentInfo.fromClass( TestCmpAnonNamed.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that uses a {@link NameProvider}
	 */
	@Test
	public void testFromClassPN1( )
	{
		NewComponentInfo< ? > nci = NewComponentInfo.fromClass( TestCmpPN.class );
		assertEquals( "" , nci.getName( ) );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that uses a {@link NameProvider} but also
	 * has an annotation-specified name
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassPNFail1( )
	{
		NewComponentInfo.fromClass( TestCmpPNFail1.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that uses {@link NameProvider} on a static
	 * method
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassPNFail2( )
	{
		NewComponentInfo.fromClass( TestCmpPNFail2.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that uses {@link NameProvider} on a method
	 * that doesn't return a string
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassPNFail3( )
	{
		NewComponentInfo.fromClass( TestCmpPNFail3.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that uses {@link NameProvider} on a method
	 * that has arguments
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassPNFail4( )
	{
		NewComponentInfo.fromClass( TestCmpPNFail4.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that uses {@link NameProvider} on more than
	 * one method
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassPNFail5( )
	{
		NewComponentInfo.fromClass( TestCmpPNFail5.class );
	}


	/** Test: {@link NewComponentInfo#fromClass(Class)} on a component class with lifecycle methods */
	@Test
	public void testFromClass5( )
	{
		NewComponentInfo< TestCmp5 > nci = NewComponentInfo.fromClass( TestCmp5.class );
		TestCmp5 cmp = nci.getSupplier( ).get( );

		for ( LifecycleStage stage : LifecycleStage.values( ) ) {
			ThrowingConsumer< ? super TestCmp5 > f = nci.getLifecycleAction( stage );
			assertNotNull( f );
			assertFalse( cmp.hadLifecycleAction[ stage.ordinal( ) ] );
			f.accept( cmp );
			assertTrue( cmp.hadLifecycleAction[ stage.ordinal( ) ] );
		}
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with inherited and overridden lifecycle
	 * methods
	 */
	@Test
	public void testFromClass6( )
	{
		NewComponentInfo< TestCmp6 > nci = NewComponentInfo.fromClass( TestCmp6.class );
		TestCmp6 cmp = nci.getSupplier( ).get( );

		for ( LifecycleStage stage : LifecycleStage.values( ) ) {
			ThrowingConsumer< ? super TestCmp6 > f = nci.getLifecycleAction( stage );
			assertNotNull( f );
			assertFalse( cmp.hadLifecycleAction[ stage.ordinal( ) ] );
			f.accept( cmp );
			if ( stage == LifecycleStage.INITIALISE ) {
				assertFalse( cmp.hadLifecycleAction[ stage.ordinal( ) ] );
				assertTrue( cmp.hadLocalInitialise );
			} else {
				assertTrue( cmp.hadLifecycleAction[ stage.ordinal( ) ] );
			}
		}
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with interface-defined lifecycle methods
	 */
	@Test
	public void testFromClass7( )
	{
		NewComponentInfo< TestCmp7 > nci = NewComponentInfo.fromClass( TestCmp7.class );
		TestCmp7 cmp = nci.getSupplier( ).get( );

		ThrowingConsumer< ? super TestCmp7 > f = nci.getLifecycleAction( LifecycleStage.INITIALISE );
		assertNotNull( f );
		f.accept( cmp );
		assertTrue( cmp.initialised );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that defines more than one method for a
	 * single lifecycle stage
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail2( )
	{
		NewComponentInfo.fromClass( TestFail2.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with a dependency injection field that names
	 * its target
	 */
	@Test
	public void testFromClass8( )
	{
		NewComponentInfo< TestCmp8 > nci = NewComponentInfo.fromClass( TestCmp8.class );
		DependencyInfo expectedDep = new DependencyInfo( "TestCmp1" );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp8 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp8 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with a dependency injection field that
	 * doesn't name its target
	 */
	@Test
	public void testFromClass9( )
	{
		NewComponentInfo< TestCmp9 > nci = NewComponentInfo.fromClass( TestCmp9.class );
		DependencyInfo expectedDep = new DependencyInfo( TestCmp1.class );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp9 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp9 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a static dependency injection field
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail3( )
	{
		NewComponentInfo.fromClass( TestFail3.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a final dependency injection field
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail4( )
	{
		NewComponentInfo.fromClass( TestFail4.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a primitive dependency injection
	 * field
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail5( )
	{
		NewComponentInfo.fromClass( TestFail5.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has an array dependency injection field
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail6( )
	{
		NewComponentInfo.fromClass( TestFail6.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with a dependency injection method that
	 * names its target
	 */
	@Test
	public void testFromClass10( )
	{
		NewComponentInfo< TestCmp10 > nci = NewComponentInfo.fromClass( TestCmp10.class );
		DependencyInfo expectedDep = new DependencyInfo( "TestCmp1" );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp10 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp10 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with a dependency injection method that
	 * doesn't name its target
	 */
	@Test
	public void testFromClass11( )
	{
		NewComponentInfo< TestCmp11 > nci = NewComponentInfo.fromClass( TestCmp11.class );
		DependencyInfo expectedDep = new DependencyInfo( TestCmp1.class );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp11 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp11 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a static dependency injection
	 * method
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail7( )
	{
		NewComponentInfo.fromClass( TestFail7.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a dependency injection method with
	 * no arguments
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail8( )
	{
		NewComponentInfo.fromClass( TestFail8.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a dependency injection method with
	 * more than one argument field
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail9( )
	{
		NewComponentInfo.fromClass( TestFail9.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a dependency injection method with
	 * a primitive argument
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail10( )
	{
		NewComponentInfo.fromClass( TestFail10.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class that has a dependency injection method with
	 * an array argument
	 */
	@Test( expected = ComponentDefinitionException.class )
	public void testFromClassFail11( )
	{
		NewComponentInfo.fromClass( TestFail11.class );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with an inherited dependency injection
	 * method
	 */
	@Test
	public void testFromClass12( )
	{
		NewComponentInfo< TestCmp12 > nci = NewComponentInfo.fromClass( TestCmp12.class );
		DependencyInfo expectedDep = new DependencyInfo( "TestCmp1" );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp12 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp12 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with an inherited dependency injection field
	 */
	@Test
	public void testFromClass13( )
	{
		NewComponentInfo< TestCmp13 > nci = NewComponentInfo.fromClass( TestCmp13.class );
		DependencyInfo expectedDep = new DependencyInfo( "TestCmp1" );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp13 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp13 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#fromClass(Class)} on a component class with an interface-defined dependency
	 * injection method
	 */
	@Test
	public void testFromClass14( )
	{
		NewComponentInfo< TestCmp14 > nci = NewComponentInfo.fromClass( TestCmp14.class );
		DependencyInfo expectedDep = new DependencyInfo( "TestCmp1" );

		HashSet< DependencyInfo > deps = nci.getDependencies( );
		assertEquals( 1 , deps.size( ) );
		assertTrue( deps.contains( expectedDep ) );

		HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > injectors = nci.getInjectors( );
		assertEquals( 1 , injectors.size( ) );
		ArrayList< ThrowingBiConsumer< Object , Object > > diList = injectors.get( expectedDep );
		assertEquals( 1 , diList.size( ) );
		ThrowingBiConsumer< ? super TestCmp14 , Object > injector = diList.get( 0 );
		assertNotNull( injector );

		TestCmp14 cmp = nci.getSupplier( ).get( );
		TestCmp1 inject = new TestCmp1( );
		injector.accept( cmp , inject );
		assertSame( inject , cmp.dependency );
	}


	/**
	 * Test: {@link NewComponentInfo#scanPackage(String, boolean)} finds a simple {@link Component}-annotated class
	 */
	@Test
	public void testScanPackageSimple( )
			throws ComponentDefinitionException , ClassNotFoundException , IOException
	{
		ArrayList< NewComponentInfo< ? > > result = NewComponentInfo.scanPackage( "test.simple" , false );
		assertEquals( 1 , result.size( ) );
		assertSame( CSimple.class , result.get( 0 ).getSupplier( ).get( ).getClass( ) );
	}


	/**
	 * Test: {@link NewComponentInfo#scanPackage(String, boolean)} finds components that inherit each other, ignoring
	 * abstract classes
	 */
	@Test
	public void testScanPackageInheritance( )
			throws ComponentDefinitionException , ClassNotFoundException , IOException
	{
		ArrayList< NewComponentInfo< ? > > result = NewComponentInfo.scanPackage( "test.inherited" , false );
		assertEquals( 3 , result.size( ) );

		Set< ? > classes = result.stream( ).map( x -> x.getSupplier( ).get( ).getClass( ) )
				.collect( Collectors.toSet( ) );
		assertTrue( classes.contains( CBase.class ) );
		assertTrue( classes.contains( CChild.class ) );
		assertTrue( classes.contains( CChildOfAbstract.class ) );
	}


	/**
	 * Test: {@link NewComponentInfo#scanPackage(String, boolean)} finds components defined by implementing a
	 * {@link Component}-annotated interface
	 */
	@Test
	public void testScanPackageInterfaces( )
			throws ComponentDefinitionException , ClassNotFoundException , IOException
	{
		ArrayList< NewComponentInfo< ? > > result = NewComponentInfo.scanPackage( "test.interfaces" , false );
		assertEquals( 1 , result.size( ) );

		assertSame( CImplementation.class , result.get( 0 ).getSupplier( ).get( ).getClass( ) );
	}
}