1 /** 2 * Copyright (C) 2014 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject.jdk8; 18 19 import static java.lang.annotation.ElementType.METHOD; 20 import static java.lang.annotation.ElementType.TYPE; 21 import static java.lang.annotation.RetentionPolicy.RUNTIME; 22 23 import com.google.inject.AbstractModule; 24 import com.google.inject.Guice; 25 import com.google.inject.Injector; 26 import com.google.inject.matcher.Matchers; 27 28 import junit.framework.TestCase; 29 30 import org.aopalliance.intercept.MethodInterceptor; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.Target; 34 import java.util.concurrent.atomic.AtomicInteger; 35 36 /** 37 * Tests for interception of default methods. 38 * 39 * @author cgdecker@google.com (Colin Decker) 40 */ 41 public class DefaultMethodInterceptionTest extends TestCase { 42 43 private static final AtomicInteger callCount = new AtomicInteger(0); 44 private static final AtomicInteger interceptedCallCount = new AtomicInteger(0); 45 46 // the interceptor's a lambda too 47 private final MethodInterceptor interceptor = invocation -> { 48 interceptedCallCount.incrementAndGet(); 49 return invocation.proceed(); 50 }; 51 52 @Override setUp()53 protected void setUp() throws Exception { 54 callCount.set(0); 55 interceptedCallCount.set(0); 56 } 57 58 @Retention(RUNTIME) 59 @Target({METHOD, TYPE}) 60 public @interface InterceptMe {} 61 62 /** Interface with a default method annotated to be intercepted. */ 63 public interface Foo { 64 @InterceptMe defaultMethod()65 default String defaultMethod() { 66 callCount.incrementAndGet(); 67 return "Foo"; 68 } 69 } 70 71 /** Foo implementation that does not override the default method. */ 72 public static class NonOverridingFoo implements Foo { methodCallingDefault()73 public String methodCallingDefault() { 74 return "NonOverriding-" + defaultMethod(); 75 } 76 } 77 testInterceptedDefaultMethod()78 public void testInterceptedDefaultMethod() { 79 Injector injector = Guice.createInjector(new AbstractModule() { 80 @Override 81 protected void configure() { 82 bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), 83 interceptor); 84 bind(Foo.class).to(NonOverridingFoo.class); 85 } 86 }); 87 88 Foo foo = injector.getInstance(Foo.class); 89 assertEquals("Foo", foo.defaultMethod()); 90 assertEquals(1, callCount.get()); 91 assertEquals(1, interceptedCallCount.get()); 92 } 93 testInterceptedDefaultMethod_calledByAnotherMethod()94 public void testInterceptedDefaultMethod_calledByAnotherMethod() { 95 Injector injector = Guice.createInjector(new AbstractModule() { 96 @Override 97 protected void configure() { 98 bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), 99 interceptor); 100 } 101 }); 102 103 NonOverridingFoo foo = injector.getInstance(NonOverridingFoo.class); 104 assertEquals("NonOverriding-Foo", foo.methodCallingDefault()); 105 assertEquals(1, callCount.get()); 106 assertEquals(1, interceptedCallCount.get()); 107 } 108 109 /** A base class defining a method with the same signature as Foo's default method. */ 110 public static class BaseClass { 111 // the definition of this method on the class will win over the default method defaultMethod()112 public String defaultMethod() { 113 callCount.incrementAndGet(); 114 return "BaseClass"; 115 } 116 } 117 118 /** Foo implementation that should use superclass method rather than default method. */ 119 public static class InheritingFoo extends BaseClass implements Foo { 120 } 121 testInterceptedDefaultMethod_whenParentClassDefinesNonInterceptedMethod()122 public void testInterceptedDefaultMethod_whenParentClassDefinesNonInterceptedMethod() { 123 Injector injector = Guice.createInjector(new AbstractModule() { 124 @Override 125 protected void configure() { 126 bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), 127 interceptor); 128 bind(Foo.class).to(InheritingFoo.class); 129 } 130 }); 131 132 // the concrete implementation that wins is not annotated 133 Foo foo = injector.getInstance(Foo.class); 134 assertEquals("BaseClass", foo.defaultMethod()); 135 assertEquals(1, callCount.get()); 136 assertEquals(0, interceptedCallCount.get()); 137 } 138 139 /** 140 * A base class defining an intercepted method with the same signature as Foo's default method. 141 */ 142 public static class BaseClass2 { 143 // the definition of this method on the class will win over the default method 144 @InterceptMe defaultMethod()145 public String defaultMethod() { 146 callCount.incrementAndGet(); 147 return "BaseClass2"; 148 } 149 } 150 151 /** 152 * Foo implementation that should use intercepted superclass method rather than default method. 153 */ 154 public static class InheritingFoo2 extends BaseClass2 implements Foo { 155 } 156 testInterceptedDefaultMethod_whenParentClassDefinesInterceptedMethod()157 public void testInterceptedDefaultMethod_whenParentClassDefinesInterceptedMethod() { 158 Injector injector = Guice.createInjector(new AbstractModule() { 159 @Override 160 protected void configure() { 161 bindInterceptor(Matchers.any(), Matchers.annotatedWith(InterceptMe.class), 162 interceptor); 163 bind(Foo.class).to(InheritingFoo2.class); 164 } 165 }); 166 167 // the concrete implementation that wins is not annotated 168 Foo foo = injector.getInstance(Foo.class); 169 assertEquals("BaseClass2", foo.defaultMethod()); 170 assertEquals(1, callCount.get()); 171 assertEquals(1, interceptedCallCount.get()); 172 } 173 174 public interface Baz { doSomething()175 default String doSomething() { 176 return "Baz"; 177 } 178 doSomethingElse()179 String doSomethingElse(); 180 } 181 182 public static class BazImpl implements Baz { 183 184 @Override doSomethingElse()185 public String doSomethingElse() { 186 return "BazImpl"; 187 } 188 } 189 testInterception_ofAllMethodsOnType()190 public void testInterception_ofAllMethodsOnType() { 191 Injector injector = Guice.createInjector(new AbstractModule() { 192 @Override 193 protected void configure() { 194 bindInterceptor(Matchers.subclassesOf(Baz.class), Matchers.any(), interceptor); 195 bind(Baz.class).to(BazImpl.class); 196 } 197 }); 198 199 Baz baz = injector.getInstance(Baz.class); 200 201 assertEquals("Baz", baz.doSomething()); 202 assertEquals("BazImpl", baz.doSomethingElse()); 203 204 assertEquals(2, interceptedCallCount.get()); 205 } 206 testInterception_ofAllMethodsOnType_interceptsInheritedDefaultMethod()207 public void testInterception_ofAllMethodsOnType_interceptsInheritedDefaultMethod() { 208 Injector injector = Guice.createInjector(new AbstractModule() { 209 @Override 210 protected void configure() { 211 bindInterceptor(Matchers.subclassesOf(BazImpl.class), Matchers.any(), interceptor); 212 bind(Baz.class).to(BazImpl.class); 213 } 214 }); 215 216 Baz baz = injector.getInstance(Baz.class); 217 218 assertEquals("Baz", baz.doSomething()); 219 assertEquals("BazImpl", baz.doSomethingElse()); 220 221 assertEquals(2, interceptedCallCount.get()); 222 } 223 } 224