/** * Copyright (C) 2014 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.jdk8; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.matcher.Matchers; import junit.framework.TestCase; import org.aopalliance.intercept.MethodInterceptor; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.concurrent.atomic.AtomicInteger; /** * Tests for interception of default methods. * * @author cgdecker@google.com (Colin Decker) */ public class DefaultMethodInterceptionTest extends TestCase { private static final AtomicInteger callCount = new AtomicInteger(0); private static final AtomicInteger interceptedCallCount = new AtomicInteger(0); // the interceptor's a lambda too private final MethodInterceptor interceptor = invocation -> { interceptedCallCount.incrementAndGet(); return invocation.proceed(); }; @Override protected void setUp() throws Exception { callCount.set(0); interceptedCallCount.set(0); } @Retention(RUNTIME) @Target({METHOD, TYPE}) public @interface InterceptMe {} /** Interface with a default method annotated to be intercepted. */ public interface Foo { @InterceptMe default String defaultMethod() { callCount.incrementAndGet(); return "Foo"; } } /** Foo implementation that does not override the default method. */ public static class NonOverridingFoo implements Foo { public String methodCallingDefault() { return "NonOverriding-" + defaultMethod(); } } public void testInterceptedDefaultMethod() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), interceptor); bind(Foo.class).to(NonOverridingFoo.class); } }); Foo foo = injector.getInstance(Foo.class); assertEquals("Foo", foo.defaultMethod()); assertEquals(1, callCount.get()); assertEquals(1, interceptedCallCount.get()); } public void testInterceptedDefaultMethod_calledByAnotherMethod() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), interceptor); } }); NonOverridingFoo foo = injector.getInstance(NonOverridingFoo.class); assertEquals("NonOverriding-Foo", foo.methodCallingDefault()); assertEquals(1, callCount.get()); assertEquals(1, interceptedCallCount.get()); } /** A base class defining a method with the same signature as Foo's default method. */ public static class BaseClass { // the definition of this method on the class will win over the default method public String defaultMethod() { callCount.incrementAndGet(); return "BaseClass"; } } /** Foo implementation that should use superclass method rather than default method. */ public static class InheritingFoo extends BaseClass implements Foo { } public void testInterceptedDefaultMethod_whenParentClassDefinesNonInterceptedMethod() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), interceptor); bind(Foo.class).to(InheritingFoo.class); } }); // the concrete implementation that wins is not annotated Foo foo = injector.getInstance(Foo.class); assertEquals("BaseClass", foo.defaultMethod()); assertEquals(1, callCount.get()); assertEquals(0, interceptedCallCount.get()); } /** * A base class defining an intercepted method with the same signature as Foo's default method. */ public static class BaseClass2 { // the definition of this method on the class will win over the default method @InterceptMe public String defaultMethod() { callCount.incrementAndGet(); return "BaseClass2"; } } /** * Foo implementation that should use intercepted superclass method rather than default method. */ public static class InheritingFoo2 extends BaseClass2 implements Foo { } public void testInterceptedDefaultMethod_whenParentClassDefinesInterceptedMethod() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), interceptor); bind(Foo.class).to(InheritingFoo2.class); } }); // the concrete implementation that wins is not annotated Foo foo = injector.getInstance(Foo.class); assertEquals("BaseClass2", foo.defaultMethod()); assertEquals(1, callCount.get()); assertEquals(1, interceptedCallCount.get()); } public interface Baz { default String doSomething() { return "Baz"; } String doSomethingElse(); } public static class BazImpl implements Baz { @Override public String doSomethingElse() { return "BazImpl"; } } public void testInterception_ofAllMethodsOnType() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindInterceptor(Matchers.subclassesOf(Baz.class), Matchers.any(), interceptor); bind(Baz.class).to(BazImpl.class); } }); Baz baz = injector.getInstance(Baz.class); assertEquals("Baz", baz.doSomething()); assertEquals("BazImpl", baz.doSomethingElse()); assertEquals(2, interceptedCallCount.get()); } public void testInterception_ofAllMethodsOnType_interceptsInheritedDefaultMethod() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindInterceptor(Matchers.subclassesOf(BazImpl.class), Matchers.any(), interceptor); bind(Baz.class).to(BazImpl.class); } }); Baz baz = injector.getInstance(Baz.class); assertEquals("Baz", baz.doSomething()); assertEquals("BazImpl", baz.doSomethingElse()); assertEquals(2, interceptedCallCount.get()); } }