/******************************************************************************* * 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.wire; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.sisu.BeanEntry; import org.eclipse.sisu.Dynamic; import org.eclipse.sisu.Nullable; import org.eclipse.sisu.inject.BeanLocator; import org.eclipse.sisu.inject.MutableBeanLocator; import org.eclipse.sisu.inject.Sources; import org.eclipse.sisu.inject.TypeArguments; 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.BindingAnnotation; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.ImplementedBy; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.ProvidedBy; import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.name.Names; import junit.framework.TestCase; public class BeanImportTest extends TestCase { @Target( FIELD ) @Retention( RUNTIME ) @BindingAnnotation public @interface Fuzzy { } static class FuzzyImpl implements Fuzzy { public Class<? extends Annotation> annotationType() { return Fuzzy.class; } @Override public boolean equals( final Object rhs ) { return rhs instanceof Fuzzy; } @Override public int hashCode() { return 0; } } interface X { } public interface Y { double fn( double x, double y ); } interface Z<T> { } @ProvidedBy( XProvider.class ) interface ImplicitX extends X { } @ImplementedBy( YImpl.class ) public interface ImplicitY extends Y { } static class XProvider implements Provider<ImplicitX> { public ImplicitX get() { return new ImplicitX() { }; } } @Named( "CustomName" ) public static class ImplWithName { } public static abstract class AbstractY implements Y { public double fn( final double x, final double y ) { return x + y; } } public static class YImpl extends AbstractY implements ImplicitY { } static class ZImpl<T> implements Z<T> { T element; } static abstract class AbstractX implements X { @Inject Injector injector; // @Inject // Logger logger; @Inject ImplicitX implicitX; @Inject ImplicitY implicitY; @Inject YImpl concreteY; @Inject @Nullable AbstractY abstractY; @Inject @Fuzzy Y fuzzy; @Inject @Named( "fixed" ) Y fixed; @Inject @Named( "CustomName" ) ImplWithName implWithName; @Inject Map<Annotation, Y> annotatedMap; @Inject Map<Named, Y> namedMap; @Inject Map<Named, Provider<Y>> namedProviderMap; } static class UnrestrictedInstance extends AbstractX { final Y single; @Inject UnrestrictedInstance( @Nullable final Y single, @Named( "fixed" ) final Y fixed ) { this.single = single; this.fixed = fixed; } } static class UnrestrictedList extends AbstractX { @Inject List<Y> list; @Inject Collection<Y> coll; @Inject Iterable<Y> iterable; @Inject List<Provider<Y>> providerList; } static class UnrestrictedSet extends AbstractX { @Inject Set<Y> set; @Inject Set<Provider<Y>> providerSet; } static class NamedType extends AbstractX { final Y single; @Inject NamedType( @Named( "fixed" ) final Y fixed, @Nullable @Named final Y single ) { this.single = single; this.fixed = fixed; } } static class NamedInstance extends AbstractX { final Y single; @Inject NamedInstance( @Nullable @Named( "TEST" ) final Y single ) { this.single = single; } @Inject void setFixed( final @Named( "fixed" ) Y fixed ) { this.fixed = fixed; } } static class HintMap extends AbstractX { @Inject Map<String, Y> map; @Inject Map<String, Provider<Y>> providerMap; } static class BeanEntries implements X { @Inject Iterable<BeanEntry<?, Y>> entries; @Inject Iterable<BeanEntry<Named, Y>> namedEntries; } static class PlaceholderInstance extends AbstractX { @Inject @Nullable @Named( "${name}" ) Y single; } static class PlaceholderString extends AbstractX { @Inject @Nullable @Named( "${text}" ) String config; @Inject @Nullable @Named( "text" ) String plain; } static class PlaceholderConfig extends AbstractX { @Inject @Nullable @Named( "4${value}2" ) int single; } static class BadMap implements X { @Inject Map<Integer, Integer> map; } static class RawList implements X { @Inject @SuppressWarnings( "rawtypes" ) List list; } static class RawMap implements X { @Inject @SuppressWarnings( "rawtypes" ) Map map; } static class MissingList implements X { @Inject @Named( "missing" ) List<Y> list; } static class MissingSet implements X { @Inject @Named( "missing" ) Set<Y> set; } static class MissingMap implements X { @Inject @Named( "missing" ) Map<Named, Y> map; } static class GenericInstance implements X { @Inject Z<? extends Number> number; @Inject Z<String> chars; @Inject Z<Random> random; } static class DynamicInstance implements X { @Inject @Dynamic Y interfaceProxy; @Inject @Dynamic YImpl concreteProxy; } static Map<String, Object> PROPS = new HashMap<String, Object>(); class TestModule extends AbstractModule { @Override protected void configure() { bind( ClassSpace.class ).toInstance( new URLClassSpace( BeanImportTest.class.getClassLoader() ) ); bindInterceptor( Matchers.subclassesOf( X.class ), Matchers.any() ); requestInjection( BeanImportTest.this ); bind( X.class ).annotatedWith( Names.named( "UI" ) ).to( UnrestrictedInstance.class ); bind( X.class ).annotatedWith( Names.named( "UL" ) ).to( UnrestrictedList.class ); bind( X.class ).annotatedWith( Names.named( "US" ) ).to( UnrestrictedSet.class ); bind( X.class ).annotatedWith( Names.named( "NT" ) ).to( NamedType.class ); bind( X.class ).annotatedWith( Names.named( "NI" ) ).to( NamedInstance.class ); bind( X.class ).annotatedWith( Names.named( "HM" ) ).to( HintMap.class ); bind( X.class ).annotatedWith( Names.named( "BE" ) ).to( BeanEntries.class ); bind( X.class ).annotatedWith( Names.named( "PI" ) ).to( PlaceholderInstance.class ); bind( X.class ).annotatedWith( Names.named( "PS" ) ).to( PlaceholderString.class ); bind( X.class ).annotatedWith( Names.named( "PC" ) ).to( PlaceholderConfig.class ); bind( X.class ).annotatedWith( Names.named( "GI" ) ).to( GenericInstance.class ); bind( X.class ).annotatedWith( Names.named( "DI" ) ).to( DynamicInstance.class ); bind( Y.class ).annotatedWith( Names.named( "fixed" ) ).toInstance( new YImpl() ); bind( Y.class ).annotatedWith( Names.named( "unscoped" ) ).to( YImpl.class ); bind( Y.class ).annotatedWith( new FuzzyImpl() ).toInstance( new YImpl() ); bind( Z.class ).annotatedWith( Names.named( "integer" ) ).toInstance( new ZImpl<Integer>() { } ); bind( Z.class ).annotatedWith( Names.named( "string" ) ).toInstance( new ZImpl<String>() { } ); bind( Z.class ).annotatedWith( Names.named( "raw" ) ).to( ZImpl.class ); bind( ImplWithName.class ); bind( ParameterKeys.PROPERTIES ).toInstance( PROPS ); } } public void testUnrestrictedImport() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); final UnrestrictedInstance unrestrictedInstance = (UnrestrictedInstance) injector.getInstance( Key.get( X.class, Names.named( "UI" ) ) ); assertSame( unrestrictedInstance.fixed, unrestrictedInstance.single ); final UnrestrictedList unrestrictedList = (UnrestrictedList) injector.getInstance( Key.get( X.class, Names.named( "UL" ) ) ); assertEquals( 3, unrestrictedList.list.size() ); assertSame( unrestrictedInstance.fixed, unrestrictedList.list.get( 0 ) ); assertSame( unrestrictedList.fixed, unrestrictedList.list.get( 0 ) ); assertSame( unrestrictedInstance.fuzzy, unrestrictedList.list.get( 2 ) ); assertSame( unrestrictedList.fuzzy, unrestrictedList.list.get( 2 ) ); assertNotSame( unrestrictedList.list.get( 0 ), unrestrictedList.list.get( 2 ) ); final Object[] listArray = unrestrictedList.list.toArray(); final Object[] collArray = unrestrictedList.coll.toArray(); assertSame( listArray[0], collArray[0] ); assertNotSame( listArray[1], collArray[1] ); assertSame( listArray[2], collArray[2] ); final Iterator<?> iterator = unrestrictedList.iterable.iterator(); assertTrue( iterator.hasNext() ); assertSame( unrestrictedList.list.get( 0 ), iterator.next() ); iterator.next(); assertSame( unrestrictedList.list.get( 2 ), iterator.next() ); assertFalse( iterator.hasNext() ); final UnrestrictedSet unrestrictedSet = (UnrestrictedSet) injector.getInstance( Key.get( X.class, Names.named( "US" ) ) ); assertEquals( 3, unrestrictedSet.set.size() ); assertTrue( unrestrictedSet.set.contains( unrestrictedInstance.fixed ) ); assertTrue( unrestrictedSet.set.contains( unrestrictedList.fixed ) ); assertTrue( unrestrictedSet.set.contains( unrestrictedInstance.fuzzy ) ); assertTrue( unrestrictedSet.set.contains( unrestrictedList.fuzzy ) ); } public void testNamedImports() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); final NamedType namedType = (NamedType) injector.getInstance( Key.get( X.class, Names.named( "NT" ) ) ); final NamedInstance namedInstance = (NamedInstance) injector.getInstance( Key.get( X.class, Names.named( "NI" ) ) ); assertNotNull( namedType.single ); assertSame( namedType.fixed, namedType.single ); assertNull( namedInstance.single ); final HintMap hintMap = (HintMap) injector.getInstance( Key.get( X.class, Names.named( "HM" ) ) ); assertSame( namedType.fixed, hintMap.map.get( "fixed" ) ); assertSame( hintMap.fixed, hintMap.map.get( "fixed" ) ); assertNotSame( namedType.fixed, hintMap.map.get( "unscoped" ) ); assertNotSame( hintMap.fixed, hintMap.map.get( "unscoped" ) ); assertEquals( 2, hintMap.map.size() ); } public void testProviderImports() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); final UnrestrictedList unrestrictedList = (UnrestrictedList) injector.getInstance( Key.get( X.class, Names.named( "UL" ) ) ); final UnrestrictedSet unrestrictedSet = (UnrestrictedSet) injector.getInstance( Key.get( X.class, Names.named( "US" ) ) ); final HintMap hintMap = (HintMap) injector.getInstance( Key.get( X.class, Names.named( "HM" ) ) ); Provider<Y> provider; provider = unrestrictedList.providerList.get( 0 ); assertTrue( provider.get() instanceof YImpl ); assertSame( provider.get(), provider.get() ); provider = unrestrictedList.providerList.get( 1 ); assertTrue( provider.get() instanceof YImpl ); assertNotSame( provider.get(), provider.get() ); assertEquals( 3, unrestrictedList.providerList.size() ); final Iterator<Provider<Y>> itr = unrestrictedSet.providerSet.iterator(); provider = itr.next(); assertTrue( provider.get() instanceof YImpl ); assertSame( provider.get(), provider.get() ); provider = itr.next(); assertTrue( provider.get() instanceof YImpl ); assertNotSame( provider.get(), provider.get() ); assertEquals( 3, unrestrictedList.providerList.size() ); provider = hintMap.providerMap.get( "unscoped" ); assertTrue( provider.get() instanceof YImpl ); assertNotSame( provider.get(), provider.get() ); assertEquals( 2, hintMap.providerMap.size() ); provider = hintMap.namedProviderMap.get( Names.named( "unscoped" ) ); assertTrue( provider.get() instanceof YImpl ); assertNotSame( provider.get(), provider.get() ); assertEquals( 2, hintMap.namedProviderMap.size() ); provider = hintMap.providerMap.get( "fixed" ); assertTrue( provider.get() instanceof YImpl ); assertSame( provider.get(), provider.get() ); assertEquals( 2, hintMap.providerMap.size() ); provider = hintMap.namedProviderMap.get( Names.named( "fixed" ) ); assertTrue( provider.get() instanceof YImpl ); assertSame( provider.get(), provider.get() ); assertEquals( 2, hintMap.namedProviderMap.size() ); } public void testBeanEntries() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); final BeanEntries beans = (BeanEntries) injector.getInstance( Key.get( X.class, Names.named( "BE" ) ) ); final HintMap hintMap = (HintMap) injector.getInstance( Key.get( X.class, Names.named( "HM" ) ) ); Iterator<? extends BeanEntry<?, Y>> i = beans.namedEntries.iterator(); assertTrue( i.hasNext() ); assertSame( hintMap.map.get( "fixed" ), i.next().getValue() ); assertNotSame( hintMap.map.get( "unscoped" ), i.next().getValue() ); assertFalse( i.hasNext() ); i = beans.entries.iterator(); assertTrue( i.hasNext() ); assertEquals( Names.named( "fixed" ), i.next().getKey() ); assertNotSame( Names.named( "unscoped" ), i.next().getKey() ); assertNotSame( new FuzzyImpl(), i.next().getKey() ); assertFalse( i.hasNext() ); } public void testPlaceholderImports() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); PlaceholderInstance placeholderInstance; placeholderInstance = (PlaceholderInstance) injector.getInstance( Key.get( X.class, Names.named( "PI" ) ) ); assertNull( placeholderInstance.single ); final Y why = new YImpl(); PROPS.put( "name", why ); placeholderInstance = (PlaceholderInstance) injector.getInstance( Key.get( X.class, Names.named( "PI" ) ) ); assertSame( why, placeholderInstance.single ); PROPS.put( "name", "fixed" ); placeholderInstance = (PlaceholderInstance) injector.getInstance( Key.get( X.class, Names.named( "PI" ) ) ); assertSame( placeholderInstance.fixed, placeholderInstance.single ); PlaceholderString placeholderString; placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertNull( placeholderString.config ); assertNull( placeholderString.plain ); PROPS.put( "text", "Hello, world!" ); placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertEquals( "Hello, world!", placeholderString.config ); assertEquals( "Hello, world!", placeholderString.plain ); PROPS.put( "text", "text" ); placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertEquals( "text", placeholderString.config ); assertEquals( "text", placeholderString.plain ); PROPS.put( "text", "${text}" ); try { placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); fail( "Expected ProvisionException" ); } catch ( final ProvisionException e ) { assertTrue( e.getMessage().contains( "${text}" ) ); } PROPS.put( "text", ">${one}{" ); PROPS.put( "one", "-${two}=" ); PROPS.put( "two", "<${three}}" ); PROPS.put( "three", "|${text}|" ); try { placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); fail( "Expected ProvisionException" ); } catch ( final ProvisionException e ) { assertTrue( e.getMessage().contains( ">-<|>-<|${text}|}={|}={" ) ); } PROPS.put( "text", ">${text" ); placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertEquals( ">${text", placeholderString.config ); assertEquals( ">${text", placeholderString.plain ); PROPS.put( "text", "${key:-default}" ); placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertEquals( "default", placeholderString.config ); assertEquals( "default", placeholderString.plain ); PROPS.put( "key", "configured" ); placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertEquals( "configured", placeholderString.config ); assertEquals( "configured", placeholderString.plain ); PROPS.put( "text", "${:-some:-default:-value:-}" ); placeholderString = (PlaceholderString) injector.getInstance( Key.get( X.class, Names.named( "PS" ) ) ); assertEquals( "some:-default:-value:-", placeholderString.config ); assertEquals( "some:-default:-value:-", placeholderString.plain ); try { injector.getInstance( Key.get( X.class, Names.named( "PC" ) ) ); fail( "Expected RuntimeException" ); } catch ( final RuntimeException e ) { System.out.println( e ); } PROPS.put( "value", "53" ); assertEquals( 4532, ( (PlaceholderConfig) injector.getInstance( Key.get( X.class, Names.named( "PC" ) ) ) ).single ); } public void testDuplicatesAreIgnored() { Guice.createInjector( new WireModule( new TestModule(), new TestModule(), new TestModule() ) ); } public void testImportSource() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); assertEquals( LocatorWiring.class.getName(), injector.getBinding( Y.class ).getSource().toString() ); } public void testInvalidTypeArguments() { try { Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( X.class ).annotatedWith( Names.named( "BM" ) ).to( BadMap.class ); } } ) ); fail( "Expected CreationException" ); } catch ( final CreationException e ) { } try { Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( X.class ).annotatedWith( Names.named( "RL" ) ).to( RawList.class ); } } ) ); fail( "Expected CreationException" ); } catch ( final CreationException e ) { } try { Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( X.class ).annotatedWith( Names.named( "RM" ) ).to( RawMap.class ); } } ) ); fail( "Expected CreationException" ); } catch ( final CreationException e ) { } try { Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( X.class ).annotatedWith( Names.named( "ML" ) ).to( MissingList.class ); } } ) ); fail( "Expected CreationException" ); } catch ( final CreationException e ) { } try { Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( X.class ).annotatedWith( Names.named( "MS" ) ).to( MissingSet.class ); } } ) ); fail( "Expected CreationException" ); } catch ( final CreationException e ) { } try { Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( X.class ).annotatedWith( Names.named( "MM" ) ).to( MissingMap.class ); } } ) ); fail( "Expected CreationException" ); } catch ( final CreationException e ) { } } public void testGenericInjection() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); final GenericInstance genericInstance = (GenericInstance) injector.getInstance( Key.get( X.class, Names.named( "GI" ) ) ); // ZImpl<Integer> is best match for Z<? extends Number> assertEquals( TypeLiteral.get( Integer.class ), TypeArguments.get( TypeLiteral.get( genericInstance.number.getClass() ).getSupertype( Z.class ), 0 ) ); // ZImpl<String> is best match for Z<String> assertEquals( TypeLiteral.get( String.class ), TypeArguments.get( TypeLiteral.get( genericInstance.chars.getClass() ).getSupertype( Z.class ), 0 ) ); // raw ZImpl is best match for Z<Random> assertEquals( TypeLiteral.get( Object.class ), TypeArguments.get( TypeLiteral.get( genericInstance.random.getClass() ).getSupertype( Z.class ), 0 ) ); } public void testChildWiring() { final Y y = new YImpl(); final Injector parent = Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind( Y.class ).annotatedWith( Names.named( "fixed" ) ).toInstance( y ); } } ); final Injector child = parent.createChildInjector( new AbstractModule() { @Override protected void configure() { bind( Y.class ).annotatedWith( new FuzzyImpl() ).toInstance( y ); } } ); final Injector grandchild = child.createChildInjector( new ChildWireModule( child, new TestModule() ) ); assertSame( y, ( (PlaceholderString) grandchild.getInstance( Key.get( X.class, Names.named( "PS" ) ) ) ).fixed ); assertSame( y, ( (PlaceholderString) grandchild.getInstance( Key.get( X.class, Names.named( "PS" ) ) ) ).fuzzy ); } public void testParametersLookup() { final BeanLocator locator = Guice.createInjector( new WireModule( new AbstractModule() { @Override protected void configure() { bind( ParameterKeys.PROPERTIES ).toInstance( Collections.singletonMap( "Hello", "world!" ) ); } } ) ).getInstance( BeanLocator.class ); @SuppressWarnings( { "rawtypes", "unchecked" } ) final Iterator<Map<?, ?>> itr = new EntryListAdapter( locator.locate( ParameterKeys.PROPERTIES ) ).iterator(); assertTrue( itr.hasNext() ); final Map<?, ?> parameters = itr.next(); assertEquals( 1, parameters.size() ); assertEquals( "world!", parameters.get( "Hello" ) ); assertFalse( itr.hasNext() ); } @SuppressWarnings( "boxing" ) public void testDynamicProxy() { final Injector injector = Guice.createInjector( new WireModule( new TestModule() ) ); final DynamicInstance dynamicInstance = (DynamicInstance) injector.getInstance( Key.get( X.class, Names.named( "DI" ) ) ); assertEquals( 42.0, dynamicInstance.interfaceProxy.fn( 12.3, 29.7 ) ); assertEquals( 9.0, dynamicInstance.concreteProxy.fn( 7, 2 ) ); // add new Y binding that multiplies the arguments instead of adding them final Injector child1 = injector.createChildInjector( new ChildWireModule( injector, new AbstractModule() { @Override protected void configure() { final Binder overrides = binder().withSource( Sources.prioritize( Integer.MAX_VALUE ) ); overrides.bind( Y.class ).annotatedWith( Names.named( "multiply" ) ).toInstance( new YImpl() { @Override public double fn( final double x, final double y ) { return x * y; } } ); } } ) ); // interface proxy should now delegate to multiplying implementation assertEquals( 365.31, dynamicInstance.interfaceProxy.fn( 12.3, 29.7 ) ); // concrete proxy shouldn't be affected by the interface binding assertEquals( 9.0, dynamicInstance.concreteProxy.fn( 7, 2 ) ); // add new YImpl binding that divides the arguments instead of adding them final Injector child2 = injector.createChildInjector( new ChildWireModule( injector, new AbstractModule() { @Override protected void configure() { final Binder overrides = binder().withSource( Sources.prioritize( Integer.MAX_VALUE ) ); overrides.bind( YImpl.class ).annotatedWith( Names.named( "divide" ) ).toInstance( new YImpl() { @Override public double fn( final double x, final double y ) { return x / y; } } ); } } ) ); // interface proxy should still delegate to multiplier implementation assertEquals( 365.31, dynamicInstance.interfaceProxy.fn( 12.3, 29.7 ) ); // concrete proxy should now delegate to the dividing implementation assertEquals( 3.5, dynamicInstance.concreteProxy.fn( 7, 2 ) ); // Object.toString delegates to the active instance final Y multiply = child1.getInstance( Key.get( Y.class, Names.named( "multiply" ) ) ); final Y divide = child2.getInstance( Key.get( YImpl.class, Names.named( "divide" ) ) ); assertTrue( dynamicInstance.interfaceProxy.toString().equals( multiply.toString() ) ); assertTrue( dynamicInstance.concreteProxy.toString().equals( divide.toString() ) ); // but Object.hashCode and Object.equals default to superclass instead assertEquals( System.identityHashCode( dynamicInstance.interfaceProxy ), dynamicInstance.interfaceProxy.hashCode() ); assertEquals( System.identityHashCode( dynamicInstance.concreteProxy ), dynamicInstance.concreteProxy.hashCode() ); assertTrue( dynamicInstance.interfaceProxy.equals( dynamicInstance.interfaceProxy ) ); assertTrue( dynamicInstance.concreteProxy.equals( dynamicInstance.concreteProxy ) ); // remove all implementations from the shared locator injector.getInstance( MutableBeanLocator.class ).clear(); try { dynamicInstance.interfaceProxy.fn( 12.3, 29.7 ); fail( "Expected IllegalStateException" ); } catch ( final IllegalStateException e ) { // should now get an exception on invoke } try { dynamicInstance.concreteProxy.fn( 7, 2 ); fail( "Expected IllegalStateException" ); } catch ( final IllegalStateException e ) { // should now get an exception on invoke } // all Object methods default to superclass implementation when delegate is missing assertTrue( dynamicInstance.interfaceProxy.toString().startsWith( Y.class.getName() + "$__sisu__$" ) ); assertTrue( dynamicInstance.concreteProxy.toString().startsWith( YImpl.class.getName() + "$__sisu__$" ) ); assertEquals( System.identityHashCode( dynamicInstance.interfaceProxy ), dynamicInstance.interfaceProxy.hashCode() ); assertEquals( System.identityHashCode( dynamicInstance.concreteProxy ), dynamicInstance.concreteProxy.hashCode() ); assertTrue( dynamicInstance.interfaceProxy.equals( dynamicInstance.interfaceProxy ) ); assertTrue( dynamicInstance.concreteProxy.equals( dynamicInstance.concreteProxy ) ); } }