Skip to content

Commit 41695cb

Browse files
committed
Add @PassthroughDefaultMethods annotation to allow using default methods on assisted factories (but only when explicitly requested by the user, see #1347 (comment))
1 parent ddb6315 commit 41695cb

File tree

3 files changed

+126
-6
lines changed

3 files changed

+126
-6
lines changed

extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,12 @@ public TypeLiteral<?> getImplementationType() {
271271
continue;
272272
}
273273

274-
// Skip default methods that java8 may have created.
275-
if (isDefault(method) && (method.isBridge() || method.isSynthetic())) {
276-
// Even synthetic default methods need the return type validation...
274+
// Skip default methods that java8 may have created (or the user specifies to skip).
275+
if (isDefault(method)
276+
&& (method.isBridge() || method.isSynthetic()
277+
|| method.isAnnotationPresent(PassthroughDefaultMethods.class)
278+
|| factoryRawType.isAnnotationPresent(PassthroughDefaultMethods.class))) {
279+
// Even default methods need the return type validation...
277280
// unavoidable consequence of javac8. :-(
278281
validateFactoryReturnType(errors, method.getReturnType(), factoryRawType);
279282
defaultMethods.put(method.getName(), method);
@@ -388,7 +391,7 @@ public TypeLiteral<?> getImplementationType() {
388391
warnedAboutUserLookups = true;
389392
logger.log(
390393
Level.WARNING,
391-
"AssistedInject factory {0} is non-public and has javac-generated default methods. "
394+
"AssistedInject factory {0} is non-public and has default methods."
392395
+ " Please pass a `MethodHandles.lookup()` with"
393396
+ " FactoryModuleBuilder.withLookups when using this factory so that Guice can"
394397
+ " properly call the default methods. Guice will try to workaround this, but "
@@ -436,6 +439,10 @@ public TypeLiteral<?> getImplementationType() {
436439
+ " public.";
437440
if (handle != null) {
438441
methodHandleBuilder.put(defaultMethod, handle);
442+
} else if (userSpecifiedDefaultMethod(factoryRawType, defaultMethod)) {
443+
// Don't try to find matching signature for user-specified default methods
444+
errors.addMessage(failureMsg.get());
445+
throw new IllegalStateException("Can't find method compatible with: " + defaultMethod);
439446
} else if (!allowMethodHandleWorkaround) {
440447
errors.addMessage(failureMsg.get());
441448
} else {
@@ -452,8 +459,7 @@ public TypeLiteral<?> getImplementationType() {
452459
}
453460
}
454461
// We always expect to find at least one match, because we only deal with javac-generated
455-
// default methods. If we ever allow user-specified default methods, this will need to
456-
// change.
462+
// default methods here.
457463
if (!foundMatch) {
458464
throw new IllegalStateException("Can't find method compatible with: " + defaultMethod);
459465
}
@@ -473,6 +479,12 @@ public TypeLiteral<?> getImplementationType() {
473479
}
474480
}
475481

482+
private static boolean userSpecifiedDefaultMethod(Class<?> factoryRawType, Method defaultMethod) {
483+
return defaultMethod.isAnnotationPresent(PassthroughDefaultMethods.class)
484+
|| (factoryRawType.isAnnotationPresent(PassthroughDefaultMethods.class)
485+
&& !defaultMethod.isBridge() && !defaultMethod.isSynthetic());
486+
}
487+
476488
static boolean isDefault(Method method) {
477489
// Per the javadoc, default methods are non-abstract, public, non-static.
478490
// They're also in interfaces, but we can guarantee that already since we only act
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (C) 2023 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.assistedinject;
18+
19+
import com.google.inject.BindingAnnotation;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.Target;
22+
import java.lang.invoke.MethodHandles;
23+
24+
import static java.lang.annotation.ElementType.METHOD;
25+
import static java.lang.annotation.ElementType.TYPE;
26+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
27+
28+
/**
29+
* Annotates a factory interface to indicate its default methods
30+
* should be pass-through on the generated factory implementation,
31+
* instead of treated as standard factory methods.
32+
*
33+
* <p>This annotation may also be used on individual default methods
34+
* of factory interfaces, but it is named with the assumption that
35+
* the general use case wants default methods treated in a uniform
36+
* fashion for an entire factory.</p>
37+
*
38+
* @see FactoryModuleBuilder#withLookups(MethodHandles.Lookup)
39+
*/
40+
@BindingAnnotation
41+
@Target({METHOD, TYPE})
42+
@Retention(RUNTIME)
43+
public @interface PassthroughDefaultMethods {
44+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (C) 2023 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.assistedinject;
18+
19+
import com.google.inject.AbstractModule;
20+
import com.google.inject.Guice;
21+
import com.google.inject.Inject;
22+
import com.google.inject.Injector;
23+
import java.lang.invoke.MethodHandles;
24+
import junit.framework.TestCase;
25+
26+
public class PassthroughDefaultMethodsTest extends TestCase {
27+
private static class Thing {
28+
final int i;
29+
30+
@Inject
31+
Thing(@Assisted int i) {
32+
this.i = i;
33+
}
34+
}
35+
36+
@PassthroughDefaultMethods
37+
private interface Factory {
38+
Thing create(int i);
39+
40+
default Thing one() {
41+
return this.create(1);
42+
}
43+
44+
default Thing createPow(int i, int pow) {
45+
return this.create((int) Math.pow(i, pow));
46+
}
47+
}
48+
49+
public void testAssistedInjection() throws IllegalAccessException {
50+
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Factory.class, MethodHandles.lookup());
51+
Injector injector =
52+
Guice.createInjector(
53+
new AbstractModule() {
54+
@Override
55+
protected void configure() {
56+
install(new FactoryModuleBuilder().withLookups(lookup).build(Factory.class));
57+
}
58+
});
59+
Factory factory = injector.getInstance(Factory.class);
60+
assertEquals(1, factory.create(1).i);
61+
assertEquals(1, factory.one().i);
62+
assertEquals(256, factory.createPow(2, 8).i);
63+
}
64+
}

0 commit comments

Comments
 (0)