package tc.oc.evil; import javax.inject.Inject; import com.google.inject.Binder; import org.junit.Test; import tc.oc.test.InjectedTestCase; import static org.junit.Assert.*; import static tc.oc.test.Assert.*; public class DecoratorFactoryTest extends InjectedTestCase { @Inject DecoratorFactory factory; @Override protected void configure(Binder binder) { super.configure(binder); binder.bind(DecoratorFactory.class).toInstance(DecoratorFactory.get()); } interface I { String name(); int age(); } static class C implements I { final String name; final int age; C(String name, int age) { this.name = name; this.age = age; } @Override public String name() { return name; } @Override public int age() { return age; } } @Test public void decorateInterface() throws Exception { abstract class D implements I, Decorator<I> { @Override public String name() { return "yep"; } @Override public I delegate() { return new C("nope", 123); } } Class<? extends I> ti = factory.implement(I.class, D.class); I i = factory.create(I.class, D.class, new Class<?>[]{DecoratorFactoryTest.class}, new Object[]{this}); assertEquals("yep", i.name()); // override assertEquals(123, i.age()); // delegated } @Test public void decorateClass() throws Exception { abstract class D extends C implements Decorator<C> { D() { super("nope", 456); } @Override public String name() { return "yep"; } @Override public C delegate() { return new C("nah", 123); } } Class<? extends C> tc = factory.implement(C.class, D.class); C c = factory.create(C.class, D.class, new Class[]{DecoratorFactoryTest.class}, new Object[]{this}); assertEquals("yep", c.name()); // override assertEquals(123, c.age()); // delegated } @Test public void missingMethod() throws Exception { abstract class D implements I, Decorator<I> { abstract void missing(); @Override public I delegate() { return new C("nope", 123); } } assertThrows(IllegalStateException.class, () -> factory.implement(I.class, D.class)); } interface J { String name(); } @Test public void delegateShadowedMethod() throws Exception { // The proxy should forward J#name() to I, even though J is not assignable to I. // This is a tricky case. abstract class D implements J, I, Decorator<I> { @Override public I delegate() { return new C("yep", 123); } } J j = factory.create(I.class, D.class, new Class[]{DecoratorFactoryTest.class}, new Object[]{this}); assertEquals("yep", j.name()); } }