diff --git a/WORKSPACE b/WORKSPACE index 19f3f83dd1..873f353368 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -36,6 +36,7 @@ maven_install( "com.google.guava:guava:31.0.1-jre", "commons-logging:commons-logging:1.2", "javax.inject:javax.inject:1", + "jakarta.inject:jakarta.inject-api:2.0.1", "javax.persistence:javax.persistence-api:2.2", "javax.servlet:servlet-api:2.5", "org.apache.struts:struts2-core:2.5.30", @@ -75,6 +76,12 @@ maven_install( "1", testonly = True, ), + maven.artifact( + "jakarta.inject", + "jakarta.inject-tck", + "2.0.1", + testonly = True, + ), maven.artifact( "junit", "junit", diff --git a/core/pom.xml b/core/pom.xml index fc67192b12..791147d883 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -18,6 +18,10 @@ javax.inject javax.inject + + jakarta.inject + jakarta.inject-api + aopalliance aopalliance @@ -90,6 +94,21 @@ org.codehaus.mojo animal-sniffer-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + + **/GuiceJakartaTck.java + + + maven-surefire-plugin diff --git a/core/src/com/google/inject/BUILD b/core/src/com/google/inject/BUILD index a530976fa2..766e931b5d 100644 --- a/core/src/com/google/inject/BUILD +++ b/core/src/com/google/inject/BUILD @@ -97,6 +97,7 @@ java_library( "//third_party/java/error_prone:annotations", "//third_party/java/jsr305_annotations", "//third_party/java/jsr330_inject", + "//third_party/java/jakarta_inject", ], ) diff --git a/core/src/com/google/inject/Provider.java b/core/src/com/google/inject/Provider.java index 4ce1e31501..88e1c2dde9 100644 --- a/core/src/com/google/inject/Provider.java +++ b/core/src/com/google/inject/Provider.java @@ -21,24 +21,24 @@ * by Guice: * *
    - *
  • When the default means for obtaining instances (an injectable or parameterless constructor) - * is insufficient for a particular binding, the module can specify a custom {@code Provider} - * instead, to control exactly how Guice creates or obtains instances for the binding. - *
  • An implementation class may always choose to have a {@code Provider} instance injected, - * rather than having a {@code T} injected directly. This may give you access to multiple - * instances, instances you wish to safely mutate and discard, instances which are out of scope - * (e.g. using a {@code @RequestScoped} object from within a {@code @SessionScoped} object), or - * instances that will be initialized lazily. - *
  • A custom {@link Scope} is implemented as a decorator of {@code Provider}, which decides - * when to delegate to the backing provider and when to provide the instance some other way. - *
  • The {@link Injector} offers access to the {@code Provider} it uses to fulfill requests for - * a given key, via the {@link Injector#getProvider} methods. + *
  • When the default means for obtaining instances (an injectable or parameterless constructor) + * is insufficient for a particular binding, the module can specify a custom {@code Provider} + * instead, to control exactly how Guice creates or obtains instances for the binding. + *
  • An implementation class may always choose to have a {@code Provider} instance injected, + * rather than having a {@code T} injected directly. This may give you access to multiple + * instances, instances you wish to safely mutate and discard, instances which are out of + * scope (e.g. using a {@code @RequestScoped} object from within a {@code @SessionScoped} + * object), or instances that will be initialized lazily. + *
  • A custom {@link Scope} is implemented as a decorator of {@code Provider}, which decides + * when to delegate to the backing provider and when to provide the instance some other way. + *
  • The {@link Injector} offers access to the {@code Provider} it uses to fulfill requests + * for a given key, via the {@link Injector#getProvider} methods. *
* * @param the type of object this provides * @author crazybob@google.com (Bob Lee) */ -public interface Provider extends javax.inject.Provider { +public interface Provider extends javax.inject.Provider, jakarta.inject.Provider { /** * Provides an instance of {@code T}. diff --git a/core/src/com/google/inject/Scopes.java b/core/src/com/google/inject/Scopes.java index 472c41f6af..02c8d5ec5a 100644 --- a/core/src/com/google/inject/Scopes.java +++ b/core/src/com/google/inject/Scopes.java @@ -69,7 +69,8 @@ public Boolean visitNoScoping() { @Override public Boolean visitScopeAnnotation(Class scopeAnnotation) { return scopeAnnotation == Singleton.class - || scopeAnnotation == javax.inject.Singleton.class; + || scopeAnnotation == javax.inject.Singleton.class + || scopeAnnotation == jakarta.inject.Singleton.class; } @Override diff --git a/core/src/com/google/inject/internal/Annotations.java b/core/src/com/google/inject/internal/Annotations.java index cfa8d2785d..6e7f1df091 100644 --- a/core/src/com/google/inject/internal/Annotations.java +++ b/core/src/com/google/inject/internal/Annotations.java @@ -345,7 +345,9 @@ boolean hasAnnotations(Class annotated) { } private static final AnnotationChecker scopeChecker = - new AnnotationChecker(Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class)); + new AnnotationChecker( + Arrays.asList( + ScopeAnnotation.class, javax.inject.Scope.class, jakarta.inject.Scope.class)); public static boolean isScopeAnnotation(Class annotationType) { return scopeChecker.hasAnnotations(annotationType); @@ -402,7 +404,8 @@ public static Annotation findBindingAnnotation( } private static final AnnotationChecker bindingAnnotationChecker = - new AnnotationChecker(Arrays.asList(BindingAnnotation.class, Qualifier.class)); + new AnnotationChecker( + Arrays.asList(BindingAnnotation.class, Qualifier.class, jakarta.inject.Qualifier.class)); /** Returns true if annotations of the specified type are binding annotations. */ public static boolean isBindingAnnotation(Class annotationType) { @@ -416,6 +419,8 @@ public static boolean isBindingAnnotation(Class annotation public static Annotation canonicalizeIfNamed(Annotation annotation) { if (annotation instanceof javax.inject.Named) { return Names.named(((javax.inject.Named) annotation).value()); + } else if (annotation instanceof jakarta.inject.Named) { + return Names.named(((jakarta.inject.Named) annotation).value()); } else { return annotation; } @@ -427,7 +432,8 @@ public static Annotation canonicalizeIfNamed(Annotation annotation) { */ public static Class canonicalizeIfNamed( Class annotationType) { - if (annotationType == javax.inject.Named.class) { + if (annotationType == javax.inject.Named.class + || annotationType == jakarta.inject.Named.class) { return Named.class; } else { return annotationType; diff --git a/core/src/com/google/inject/internal/DelegatingInvocationHandler.java b/core/src/com/google/inject/internal/DelegatingInvocationHandler.java index dd00fe3a61..f78262f65f 100644 --- a/core/src/com/google/inject/internal/DelegatingInvocationHandler.java +++ b/core/src/com/google/inject/internal/DelegatingInvocationHandler.java @@ -44,7 +44,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl + " proxying is initialized to null." + " No methods can be called."); - // TODO: user -; ? + // TODO: method.setAccessible(true); ? // this would fix visibility errors when we proxy a // non-public interface. return method.invoke(delegate, args); diff --git a/core/src/com/google/inject/internal/InjectorShell.java b/core/src/com/google/inject/internal/InjectorShell.java index 02626b9a29..3b86cac63a 100644 --- a/core/src/com/google/inject/internal/InjectorShell.java +++ b/core/src/com/google/inject/internal/InjectorShell.java @@ -339,6 +339,7 @@ public void configure(Binder binder) { binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE); binder.bindScope(Singleton.class, SINGLETON); binder.bindScope(javax.inject.Singleton.class, SINGLETON); + binder.bindScope(jakarta.inject.Singleton.class, SINGLETON); } } diff --git a/core/src/com/google/inject/internal/MoreTypes.java b/core/src/com/google/inject/internal/MoreTypes.java index da4423e466..391990430b 100644 --- a/core/src/com/google/inject/internal/MoreTypes.java +++ b/core/src/com/google/inject/internal/MoreTypes.java @@ -77,8 +77,9 @@ public static Key canonicalizeKey(Key key) { /** * Returns an type that's appropriate for use in a key. * - *

If the raw type of {@code typeLiteral} is a {@code javax.inject.Provider}, this returns a - * {@code com.google.inject.Provider} with the same type parameters. + *

If the raw type of {@code typeLiteral} is a {@code javax.inject.Provider} or {@code + * jakarta.inject.Provider}, this returns a {@code com.google.inject.Provider} with the same type + * parameters. * *

If the type is a primitive, the corresponding wrapper type will be returned. * @@ -91,11 +92,12 @@ public static TypeLiteral canonicalizeForKey(TypeLiteral typeLiteral) throw new ConfigurationException(errors.getMessages()); } - if (typeLiteral.getRawType() == javax.inject.Provider.class) { + if (typeLiteral.getRawType() == javax.inject.Provider.class + || typeLiteral.getRawType() == jakarta.inject.Provider.class) { ParameterizedType parameterizedType = (ParameterizedType) type; // the following casts are generally unsafe, but com.google.inject.Provider extends - // javax.inject.Provider and is covariant + // javax.inject.Provider & jakarta.inject.Provider and is covariant @SuppressWarnings("unchecked") TypeLiteral guiceProviderType = (TypeLiteral) diff --git a/core/src/com/google/inject/spi/InjectionPoint.java b/core/src/com/google/inject/spi/InjectionPoint.java index 84b91e406b..2f566614ef 100644 --- a/core/src/com/google/inject/spi/InjectionPoint.java +++ b/core/src/com/google/inject/spi/InjectionPoint.java @@ -167,7 +167,7 @@ private Dependency newDependency(Key key, boolean allowsNull, int para /** Returns the injected constructor, field, or method. */ public Member getMember() { - // TODO -). + // TODO: Don't expose the original member (which probably has setAccessible(true)). return member; } @@ -300,7 +300,8 @@ public static InjectionPoint forConstructorOf(TypeLiteral type, boolean atInj .filter( constructor -> constructor.isAnnotationPresent(Inject.class) - || constructor.isAnnotationPresent(javax.inject.Inject.class)) + || constructor.isAnnotationPresent(javax.inject.Inject.class) + || constructor.isAnnotationPresent(jakarta.inject.Inject.class)) .collect(Collectors.toList()); Constructor injectableConstructor = null; @@ -477,20 +478,21 @@ private static boolean checkForMisplacedBindingAnnotations(Member member, Errors abstract static class InjectableMember { final TypeLiteral declaringType; final boolean optional; - final boolean jsr330; + final boolean specInject; InjectableMember previous; InjectableMember next; InjectableMember(TypeLiteral declaringType, Annotation atInject) { this.declaringType = declaringType; - if (atInject.annotationType() == javax.inject.Inject.class) { + if (atInject.annotationType() == javax.inject.Inject.class + || atInject.annotationType() == jakarta.inject.Inject.class) { optional = false; - jsr330 = true; + specInject = true; return; } - jsr330 = false; + specInject = false; optional = ((Inject) atInject).optional(); } @@ -536,6 +538,9 @@ public boolean isFinal() { static Annotation getAtInject(AnnotatedElement member) { Annotation a = member.getAnnotation(javax.inject.Inject.class); + if (a == null) { + a = member.getAnnotation(jakarta.inject.Inject.class); + } return a == null ? member.getAnnotation(Inject.class) : a; } @@ -646,7 +651,7 @@ boolean removeIfOverriddenBy( InjectableMethod possiblyOverridden = iterator.next(); if (overrides(method, possiblyOverridden.method)) { boolean wasGuiceInject = - !possiblyOverridden.jsr330 || possiblyOverridden.overrodeGuiceInject; + !possiblyOverridden.specInject || possiblyOverridden.overrodeGuiceInject; if (injectableMethod != null) { injectableMethod.overrodeGuiceInject = wasGuiceInject; } @@ -719,7 +724,7 @@ private static Set getInjectionPoints( Annotation atInject = getAtInject(field); if (atInject != null) { InjectableField injectableField = new InjectableField(current, field, atInject); - if (injectableField.jsr330 && Modifier.isFinal(field.getModifiers())) { + if (injectableField.specInject && Modifier.isFinal(field.getModifiers())) { errors.cannotInjectFinalField(field); } injectableMembers.add(injectableField); @@ -837,7 +842,7 @@ private static boolean isEligibleForInjection(Method method, boolean statics) { private static boolean isValidMethod(InjectableMethod injectableMethod, Errors errors) { boolean result = true; - if (injectableMethod.jsr330) { + if (injectableMethod.specInject) { Method method = injectableMethod.method; if (Modifier.isAbstract(method.getModifiers())) { errors.cannotInjectAbstractMethod(method); diff --git a/core/src/com/google/inject/util/Providers.java b/core/src/com/google/inject/util/Providers.java index e3a7dc136f..fd8c2c91d0 100644 --- a/core/src/com/google/inject/util/Providers.java +++ b/core/src/com/google/inject/util/Providers.java @@ -17,6 +17,7 @@ package com.google.inject.util; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; @@ -82,6 +83,19 @@ public int hashCode() { } } + /** + * Returns itself. This exists primarily to avoid ambiguous method reference compile errors when + * calling guicify with a Guice provider. + * + * @since vNext + * @deprecated Marked as deprecated as a hint to users that calling this is unnecessary, because + * the provider is already a guice Provider. + */ + @Deprecated + public static Provider guicify(Provider provider) { + return provider; + } + /** * Returns a Guice-friendly {@code com.google.inject.Provider} for the given JSR-330 {@code * javax.inject.Provider}. The converse method is unnecessary, since Guice providers directly @@ -100,21 +114,49 @@ public static Provider guicify(javax.inject.Provider provider) { Set injectionPoints = InjectionPoint.forInstanceMethodsAndFields(provider.getClass()); if (injectionPoints.isEmpty()) { - return new GuicifiedProvider(delegate); + return new GuicifiedJSR330Provider(delegate); } else { Set> mutableDeps = Sets.newHashSet(); for (InjectionPoint ip : injectionPoints) { mutableDeps.addAll(ip.getDependencies()); } final Set> dependencies = ImmutableSet.copyOf(mutableDeps); - return new GuicifiedProviderWithDependencies(dependencies, delegate); + return new GuicifiedJSR330ProviderWithDependencies(dependencies, delegate); + } + } + + /** + * Returns a Guice-friendly {@code com.google.inject.Provider} for the given {@code + * jakarta.inject.Provider}. The converse method is unnecessary, since Guice providers directly + * implement the jakarta.inject.Provider interface. + * + * @since vNext + */ + public static Provider guicify(jakarta.inject.Provider provider) { + if (provider instanceof Provider) { + return (Provider) provider; + } + + jakarta.inject.Provider delegate = checkNotNull(provider, "provider"); + + // Ensure that we inject all injection points from the delegate provider. + Set injectionPoints = + InjectionPoint.forInstanceMethodsAndFields(provider.getClass()); + if (injectionPoints.isEmpty()) { + return new GuicifiedJakartaProvider(delegate); + } else { + ImmutableSet> dependencies = + injectionPoints.stream() + .flatMap(ip -> ip.getDependencies().stream()) + .collect(toImmutableSet()); + return new GuicifiedJakartaProviderWithDependencies(dependencies, delegate); } } - private static class GuicifiedProvider implements Provider { + private static class GuicifiedJSR330Provider implements Provider { protected final javax.inject.Provider delegate; - private GuicifiedProvider(javax.inject.Provider delegate) { + private GuicifiedJSR330Provider(javax.inject.Provider delegate) { this.delegate = delegate; } @@ -130,8 +172,8 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof GuicifiedProvider) - && Objects.equal(delegate, ((GuicifiedProvider) obj).delegate); + return (obj instanceof GuicifiedJSR330Provider) + && Objects.equal(delegate, ((GuicifiedJSR330Provider) obj).delegate); } @Override @@ -140,11 +182,40 @@ public int hashCode() { } } - private static final class GuicifiedProviderWithDependencies extends GuicifiedProvider - implements ProviderWithDependencies { + private static class GuicifiedJakartaProvider implements Provider { + protected final jakarta.inject.Provider delegate; + + private GuicifiedJakartaProvider(jakarta.inject.Provider delegate) { + this.delegate = delegate; + } + + @Override + public T get() { + return delegate.get(); + } + + @Override + public String toString() { + return "guicified(" + delegate + ")"; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof GuicifiedJakartaProvider) + && Objects.equal(delegate, ((GuicifiedJakartaProvider) obj).delegate); + } + + @Override + public int hashCode() { + return Objects.hashCode(delegate); + } + } + + private static final class GuicifiedJSR330ProviderWithDependencies + extends GuicifiedJSR330Provider implements ProviderWithDependencies { private final Set> dependencies; - private GuicifiedProviderWithDependencies( + private GuicifiedJSR330ProviderWithDependencies( Set> dependencies, javax.inject.Provider delegate) { super(delegate); this.dependencies = dependencies; @@ -161,4 +232,26 @@ public Set> getDependencies() { return dependencies; } } + + private static final class GuicifiedJakartaProviderWithDependencies + extends GuicifiedJakartaProvider implements ProviderWithDependencies { + private final Set> dependencies; + + private GuicifiedJakartaProviderWithDependencies( + Set> dependencies, jakarta.inject.Provider delegate) { + super(delegate); + this.dependencies = dependencies; + } + + @SuppressWarnings("unused") + @Inject + void initialize(Injector injector) { + injector.injectMembers(delegate); + } + + @Override + public Set> getDependencies() { + return dependencies; + } + } } diff --git a/core/src/com/google/inject/util/Types.java b/core/src/com/google/inject/util/Types.java index 32714cc7dc..54934a056d 100644 --- a/core/src/com/google/inject/util/Types.java +++ b/core/src/com/google/inject/util/Types.java @@ -144,4 +144,15 @@ public static ParameterizedType providerOf(Type providedType) { public static Type javaxProviderOf(Type type) { return Types.newParameterizedType(javax.inject.Provider.class, type); } + + /** + * Returns a type modelling a {@link jakarta.inject.Provider} that provides elements of type + * {@code elementType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + * @since vNext + */ + public static Type jakartaProviderOf(Type type) { + return Types.newParameterizedType(jakarta.inject.Provider.class, type); + } } diff --git a/core/test/com/google/inject/BUILD b/core/test/com/google/inject/BUILD index d3893ffd6b..0090ae5e45 100644 --- a/core/test/com/google/inject/BUILD +++ b/core/test/com/google/inject/BUILD @@ -57,6 +57,7 @@ java_library( "//third_party/java/guava/collect", "//third_party/java/guava/testing", "//third_party/java/guava/util/concurrent", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", "//third_party/java/junit", "//third_party/java/truth", diff --git a/core/test/com/google/inject/name/NamedEquivalanceTest.java b/core/test/com/google/inject/name/NamedEquivalanceTest.java index 67ca44f198..7db3c7b254 100644 --- a/core/test/com/google/inject/name/NamedEquivalanceTest.java +++ b/core/test/com/google/inject/name/NamedEquivalanceTest.java @@ -17,7 +17,9 @@ package com.google.inject.name; import static com.google.inject.Asserts.assertContains; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import com.google.common.testing.EqualsTester; import com.google.inject.AbstractModule; import com.google.inject.ConfigurationException; import com.google.inject.CreationException; @@ -30,12 +32,15 @@ import com.google.inject.internal.Annotations; import java.io.Serializable; import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; import java.util.Properties; +import javax.inject.Qualifier; import junit.framework.TestCase; /** - * Tests that {@code javax.inject.Named} and {@code com.google.inject.name.Named} are completely - * interchangeable: bindings for one can be used to inject the other. + * Tests that {@code javax.inject.Named}, {@code jakarta.inject.Named} and {@code + * com.google.inject.name.Named} are completely interchangeable: bindings for one can be used to + * inject the other. * * @author cgdecker@gmail.com (Colin Decker) */ @@ -43,18 +48,36 @@ public class NamedEquivalanceTest extends TestCase { private static final Module GUICE_BINDING_MODULE = moduleWithAnnotation(Names.named("foo")); private static final Module JSR330_BINDING_MODULE = moduleWithAnnotation(new JsrNamed("foo")); + private static final Module JAKARTA_BINDING_MODULE = + moduleWithAnnotation(new JakartaNamed("foo")); private static final Module GUICE_PROVIDER_METHOD_MODULE = getGuiceBindingProviderMethodModule(); private static final Module JSR330_PROVIDER_METHOD_MODULE = getJsr330BindingProviderMethodModule(); + private static final Module JAKARTA_PROVIDER_METHOD_MODULE = + getJakartaBindingProviderMethodModule(); - public void testKeysCreatedWithDifferentTypesAreEqual() { - assertEquals(keyForAnnotation(new GuiceNamed("foo")), keyForAnnotation(new JsrNamed("foo"))); - assertEquals(keyForAnnotation(Names.named("foo")), keyForAnnotation(new GuiceNamed("foo"))); - assertEquals(keyForAnnotation(Names.named("foo")), keyForAnnotation(new JsrNamed("foo"))); + @Qualifier + @Retention(RUNTIME) + @interface NotNamed {} - assertEquals( - keyForAnnotationType(com.google.inject.name.Named.class), - keyForAnnotationType(javax.inject.Named.class)); + public void testKeysCreatedWithDifferentTypesAreEqual() { + new EqualsTester() + .addEqualityGroup( + keyForAnnotation(new GuiceNamed("foo")), + keyForAnnotation(new JsrNamed("foo")), + keyForAnnotation(new JakartaNamed("foo"))) + .addEqualityGroup( + keyForAnnotation(new GuiceNamed("bar")), + keyForAnnotation(new JsrNamed("bar")), + keyForAnnotation(new JakartaNamed("bar"))) + .testEquals(); + + new EqualsTester() + .addEqualityGroup( + keyForAnnotationType(com.google.inject.name.Named.class), + keyForAnnotationType(javax.inject.Named.class), + keyForAnnotationType(jakarta.inject.Named.class)) + .addEqualityGroup(keyForAnnotationType(NotNamed.class)); } private static Key keyForAnnotation(Annotation annotation) { @@ -65,35 +88,50 @@ private static Key keyForAnnotationType(Class anno return Key.get(String.class, annotationType); } - public void testBindingWithNamesCanInjectBothTypes() { + public void testBindingWithNamesCanInjectAllTypes() { assertInjectionsSucceed(GUICE_BINDING_MODULE); } - public void testBindingWithJsr330AnnotationCanInjectBothTypes() { + public void testBindingWithJsr330AnnotationCanInjectAllTypes() { assertInjectionsSucceed(JSR330_BINDING_MODULE); } - public void testBindingWithGuiceNamedAnnotatedProviderMethodCanInjectBothTypes() { + public void testBindingWithJakartannotationCanInjectAllTypes() { + assertInjectionsSucceed(JAKARTA_BINDING_MODULE); + } + + public void testBindingWithGuiceNamedAnnotatedProviderMethodCanInjectAllTypes() { assertInjectionsSucceed(GUICE_PROVIDER_METHOD_MODULE); } - public void testBindingWithJsr330NamedAnnotatedProviderMethodCanInjectBothTypes() { + public void testBindingWithJsr330NamedAnnotatedProviderMethodCanInjectAllTypes() { assertInjectionsSucceed(JSR330_PROVIDER_METHOD_MODULE); } + public void testBindingWithJakartaNamedAnnotatedProviderMethodCanInjectAllTypes() { + assertInjectionsSucceed(JAKARTA_PROVIDER_METHOD_MODULE); + } + public void testBindingDifferentTypesWithSameValueIsIgnored() { - assertDuplicateBinding(GUICE_BINDING_MODULE, JSR330_BINDING_MODULE, false); - assertDuplicateBinding(JSR330_BINDING_MODULE, GUICE_BINDING_MODULE, false); + assertDuplicateBinding( + false, GUICE_BINDING_MODULE, JSR330_BINDING_MODULE, JAKARTA_BINDING_MODULE); } public void testBindingDifferentTypesWithSameValueIsAnErrorWithProviderMethods() { - assertDuplicateBinding(GUICE_PROVIDER_METHOD_MODULE, JSR330_PROVIDER_METHOD_MODULE, true); - assertDuplicateBinding(JSR330_PROVIDER_METHOD_MODULE, GUICE_PROVIDER_METHOD_MODULE, true); + assertDuplicateBinding(true, GUICE_PROVIDER_METHOD_MODULE, JSR330_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, GUICE_PROVIDER_METHOD_MODULE, JAKARTA_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JSR330_PROVIDER_METHOD_MODULE, GUICE_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JSR330_PROVIDER_METHOD_MODULE, JAKARTA_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JAKARTA_PROVIDER_METHOD_MODULE, JSR330_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JAKARTA_PROVIDER_METHOD_MODULE, GUICE_PROVIDER_METHOD_MODULE); } public void testBindingDifferentTypesWithSameValueIsAnErrorMixed() { - assertDuplicateBinding(GUICE_BINDING_MODULE, JSR330_PROVIDER_METHOD_MODULE, true); - assertDuplicateBinding(JSR330_BINDING_MODULE, GUICE_PROVIDER_METHOD_MODULE, true); + assertDuplicateBinding(true, GUICE_BINDING_MODULE, JSR330_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, GUICE_BINDING_MODULE, JAKARTA_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JSR330_BINDING_MODULE, GUICE_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JSR330_BINDING_MODULE, JAKARTA_PROVIDER_METHOD_MODULE); + assertDuplicateBinding(true, JAKARTA_BINDING_MODULE, GUICE_PROVIDER_METHOD_MODULE); } public void testMissingBindingForGuiceNamedUsesSameTypeInErrorMessage() { @@ -104,6 +142,10 @@ public void testMissingBindingForJsr330NamedUsesSameTypeInErrorMessage() { assertMissingBindingErrorMessageUsesType(Jsr330NamedClient.class); } + public void testMissingBindingForJakartaamedUsesSameTypeInErrorMessage() { + assertMissingBindingErrorMessageUsesType(Jsr330NamedClient.class); + } + public void testBindPropertiesWorksWithJsr330() { assertInjectionsSucceed( new AbstractModule() { @@ -130,9 +172,9 @@ private static void assertMissingBindingErrorMessageUsesType(Class clientType } } - private static void assertDuplicateBinding(Module a, Module b, boolean fails) { + private static void assertDuplicateBinding(boolean fails, Module... modules) { try { - Guice.createInjector(a, b); + Guice.createInjector(modules); if (fails) { fail("should have thrown CreationException"); } @@ -162,12 +204,29 @@ private static void assertInjectionsSucceed(Module module) { Injector injector = Guice.createInjector(module); assertInjected( injector.getInstance(GuiceNamedClient.class), - injector.getInstance(Jsr330NamedClient.class)); + injector.getInstance(Jsr330NamedClient.class), + injector.getInstance(JakartaNamedClient.class)); } - private static void assertInjected(GuiceNamedClient guiceClient, Jsr330NamedClient jsr330Client) { + private static void assertInjected( + GuiceNamedClient guiceClient, + Jsr330NamedClient jsr330Client, + JakartaNamedClient jakartaClient) { assertEquals("bar", guiceClient.foo); assertEquals("bar", jsr330Client.foo); + assertEquals("bar", jakartaClient.foo); + } + + private static Module getJakartaBindingProviderMethodModule() { + return new AbstractModule() { + + @SuppressWarnings("unused") + @Provides + @jakarta.inject.Named("foo") + String provideFoo() { + return "bar"; + } + }; } private static Module getJsr330BindingProviderMethodModule() { @@ -206,6 +265,57 @@ private static class Jsr330NamedClient { String foo; } + private static class JakartaNamedClient { + @Inject + @jakarta.inject.Named("foo") + String foo; + } + + private static class JakartaNamed implements jakarta.inject.Named, Serializable { + private final String value; + + public JakartaNamed(String value) { + this.value = value; + } + + @Override + public String value() { + return this.value; + } + + @Override + public int hashCode() { + // This is specified in java.lang.Annotation. + return (127 * "value".hashCode()) ^ value.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof jakarta.inject.Named)) { + return false; + } + + jakarta.inject.Named other = (jakarta.inject.Named) o; + return value.equals(other.value()); + } + + @Override + public String toString() { + return "@" + + jakarta.inject.Named.class.getName() + + "(value=" + + Annotations.memberValueString("value", value) + + ")"; + } + + @Override + public Class annotationType() { + return jakarta.inject.Named.class; + } + + private static final long serialVersionUID = 0; + } + private static class JsrNamed implements javax.inject.Named, Serializable { private final String value; diff --git a/core/test/com/google/inject/spi/InjectorSpiTest.java b/core/test/com/google/inject/spi/InjectorSpiTest.java index 2f3b82190d..dba27424fa 100644 --- a/core/test/com/google/inject/spi/InjectorSpiTest.java +++ b/core/test/com/google/inject/spi/InjectorSpiTest.java @@ -124,7 +124,8 @@ protected Void visitOther(Element element) { } assertThat(actualKeys) .containsExactly(Key.get(Stage.class), Key.get(Injector.class), Key.get(Logger.class)); - assertThat(scopes).hasSize(2); // singleton for javax.inject.Singleton & c.g.i.Singleton + // singleton for javax.inject.Singleton, jakarta.inject.Singleton & c.g.i.Singleton + assertThat(scopes).hasSize(3); assertThat(typeConverters).hasSize(10); // all the built-in converters. } diff --git a/core/test/com/google/inject/util/ProvidersTest.java b/core/test/com/google/inject/util/ProvidersTest.java index 7c3c8b7b2e..e10adbb6e6 100644 --- a/core/test/com/google/inject/util/ProvidersTest.java +++ b/core/test/com/google/inject/util/ProvidersTest.java @@ -52,11 +52,18 @@ public void testGuicifyEquality() { new EqualsTester() .addEqualityGroup( Providers.guicify(new JavaxProvider(10)), Providers.guicify(new JavaxProvider(10))) + .addEqualityGroup( + Providers.guicify(new JakartaProvider(10)), Providers.guicify(new JakartaProvider(10))) .addEqualityGroup( Providers.guicify(new JavaxProvider(11)), Providers.guicify(new JavaxProvider(11))) + .addEqualityGroup( + Providers.guicify(new JakartaProvider(11)), Providers.guicify(new JakartaProvider(11))) .addEqualityGroup( Providers.guicify(new JavaxProviderWithDependencies()), Providers.guicify(new JavaxProviderWithDependencies())) + .addEqualityGroup( + Providers.guicify(new JakartaProviderWithDependencies()), + Providers.guicify(new JakartaProviderWithDependencies())) .testEquals(); } @@ -83,6 +90,29 @@ public boolean equals(Object obj) { } } + private static class JakartaProvider implements jakarta.inject.Provider { + private final int value; + + public JakartaProvider(int value) { + this.value = value; + } + + @Override + public Integer get() { + return value; + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof JakartaProvider) && (value == ((JakartaProvider) obj).value); + } + } + private static class JavaxProviderWithDependencies implements javax.inject.Provider { private int value; @@ -106,4 +136,28 @@ public boolean equals(Object obj) { return (obj instanceof JavaxProviderWithDependencies); } } + + private static class JakartaProviderWithDependencies implements jakarta.inject.Provider { + private int value; + + @Inject + void setValue(int value) { + this.value = value; + } + + @Override + public Integer get() { + return value; + } + + @Override + public int hashCode() { + return 42; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof JakartaProviderWithDependencies); + } + } } diff --git a/core/test/com/googlecode/guice/BUILD b/core/test/com/googlecode/guice/BUILD index 6e78470a82..11574e7eed 100644 --- a/core/test/com/googlecode/guice/BUILD +++ b/core/test/com/googlecode/guice/BUILD @@ -10,24 +10,54 @@ package( java_library( name = "tests", - srcs = glob([ - "**/*.java", - ]), + srcs = glob( + ["**/*.java"], + exclude = ["*Tck.java"], + ), deps = [ "//core/src/com/google/inject", "//core/test/com/google/inject:testsupport", "//third_party/java/aopalliance", "//third_party/java/guava/testing", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", - "//third_party/java/jsr330_inject:tck", "//third_party/java/junit", "@maven//:biz_aQute_bnd", "@maven//:org_apache_felix_org_apache_felix_framework", ], ) +# We split the TCK tests out from the normal libraries, because the jakarta.inject TCK uses the same +# packages as the javax.inject TCK, so the two cannot be in the same classloader. + +java_library( + name = "jakarta_tck_test", + srcs = ["GuiceJakartaTck.java"], + deps = [ + "//core/src/com/google/inject", + "//third_party/java/jakarta_inject", + "//third_party/java/jakarta_inject_tck", + "//third_party/java/junit", + ], +) + +java_library( + name = "javax_inject_tck_test", + srcs = ["GuiceTck.java"], + deps = [ + "//core/src/com/google/inject", + "//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject:tck", + "//third_party/java/junit", + ], +) + guice_test_suites( name = "gen_tests", + srcs = glob( + ["**Test/*.java"], + exclude = ["*Tck.java"], + ), jvm_flags = [ # those 2 options are required for some tests that checks stack traces "-XX:+UnlockDiagnosticVMOptions", @@ -42,6 +72,10 @@ guice_test_suites( [guice_test_suites( name = "gen_tests_class_loading_%s" % custom_class_loading_option, + srcs = glob( + ["**Test/*.java"], + exclude = ["*Tck.java"], + ), args = [ "--guice_custom_class_loading=%s" % custom_class_loading_option, ], @@ -61,3 +95,19 @@ guice_test_suites( "CHILD", "ANONYMOUS", ]] + +guice_test_suites( + name = "gen_jakarta_tck_tests", + srcs = ["GuiceJakartaTck.java"], + sizes = ["small"], + suffix = "_jakarta_tck", + deps = [":jakarta_tck_test"], +) + +guice_test_suites( + name = "gen_javax_inject_tck_tests", + srcs = ["GuiceTck.java"], + sizes = ["small"], + suffix = "_javax_inject_tck", + deps = [":javax_inject_tck_test"], +) diff --git a/core/test/com/googlecode/guice/GuiceJakartaTck.java b/core/test/com/googlecode/guice/GuiceJakartaTck.java new file mode 100644 index 0000000000..a6d951537e --- /dev/null +++ b/core/test/com/googlecode/guice/GuiceJakartaTck.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 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.googlecode.guice; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Provides; +import jakarta.inject.Named; +import junit.framework.Test; +import junit.framework.TestCase; +import org.atinject.tck.Tck; +import org.atinject.tck.auto.Car; +import org.atinject.tck.auto.Convertible; +import org.atinject.tck.auto.Drivers; +import org.atinject.tck.auto.DriversSeat; +import org.atinject.tck.auto.Engine; +import org.atinject.tck.auto.FuelTank; +import org.atinject.tck.auto.Seat; +import org.atinject.tck.auto.Tire; +import org.atinject.tck.auto.V8Engine; +import org.atinject.tck.auto.accessories.Cupholder; +import org.atinject.tck.auto.accessories.SpareTire; + +public class GuiceJakartaTck extends TestCase { + + public static Test suite() { + return Tck.testsFor( + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(Car.class).to(Convertible.class); + bind(Seat.class).annotatedWith(Drivers.class).to(DriversSeat.class); + bind(Engine.class).to(V8Engine.class); + bind(Cupholder.class); + bind(Tire.class); + bind(FuelTank.class); + requestStaticInjection(Convertible.class, SpareTire.class); + } + + @Provides + @Named("spare") + Tire provideSpareTire(SpareTire spare) { + return spare; + } + }) + .getInstance(Car.class), + true, + true); + } +} diff --git a/core/test/com/googlecode/guice/JakartaTest.java b/core/test/com/googlecode/guice/JakartaTest.java new file mode 100644 index 0000000000..72e605429b --- /dev/null +++ b/core/test/com/googlecode/guice/JakartaTest.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2023 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.googlecode.guice; + +import static com.google.inject.Asserts.assertContains; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Scope; +import com.google.inject.Scopes; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.util.Providers; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Provider; +import jakarta.inject.Qualifier; +import jakarta.inject.Singleton; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.util.Set; +import junit.framework.TestCase; + +public class JakartaTest extends TestCase { + + private final B b = new B(); + private final C c = new C(); + private final D d = new D(); + private final E e = new E(); + + @Override + protected void setUp() throws Exception { + J.nextInstanceId = 0; + K.nextInstanceId = 0; + } + + public void testInject() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(B.class).toInstance(b); + bind(C.class).toInstance(c); + bind(D.class).toInstance(d); + bind(E.class).toInstance(e); + bind(A.class); + } + }); + + A a = injector.getInstance(A.class); + assertSame(b, a.b); + assertSame(c, a.c); + assertSame(d, a.d); + assertSame(e, a.e); + } + + public void testQualifiedInject() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(B.class).annotatedWith(Names.named("jodie")).toInstance(b); + bind(C.class).annotatedWith(Red.class).toInstance(c); + bind(D.class).annotatedWith(RED).toInstance(d); + bind(E.class).annotatedWith(Names.named("jesse")).toInstance(e); + bind(F.class); + } + }); + + F f = injector.getInstance(F.class); + assertSame(b, f.b); + assertSame(c, f.c); + assertSame(d, f.d); + assertSame(e, f.e); + } + + public void testProviderInject() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(B.class).annotatedWith(Names.named("jodie")).toInstance(b); + bind(C.class).toInstance(c); + bind(D.class).annotatedWith(RED).toInstance(d); + bind(E.class).toInstance(e); + bind(G.class); + } + }); + + G g = injector.getInstance(G.class); + assertSame(b, g.bProvider.get()); + assertSame(c, g.cProvider.get()); + assertSame(d, g.dProvider.get()); + assertSame(e, g.eProvider.get()); + } + + public void testScopeAnnotation() { + final TestScope scope = new TestScope(); + + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(B.class).in(scope); + bind(C.class).in(TestScoped.class); + bindScope(TestScoped.class, scope); + } + }); + + B b = injector.getInstance(B.class); + assertSame(b, injector.getInstance(B.class)); + assertSame(b, injector.getInstance(B.class)); + + C c = injector.getInstance(C.class); + assertSame(c, injector.getInstance(C.class)); + assertSame(c, injector.getInstance(C.class)); + + H h = injector.getInstance(H.class); + assertSame(h, injector.getInstance(H.class)); + assertSame(h, injector.getInstance(H.class)); + + scope.reset(); + + assertNotSame(b, injector.getInstance(B.class)); + assertNotSame(c, injector.getInstance(C.class)); + assertNotSame(h, injector.getInstance(H.class)); + } + + public void testSingleton() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(B.class).in(Singleton.class); + } + }); + + B b = injector.getInstance(B.class); + assertSame(b, injector.getInstance(B.class)); + assertSame(b, injector.getInstance(B.class)); + + J j = injector.getInstance(J.class); + assertSame(j, injector.getInstance(J.class)); + assertSame(j, injector.getInstance(J.class)); + } + + public void testEagerSingleton() { + Guice.createInjector( + Stage.PRODUCTION, + new AbstractModule() { + @Override + protected void configure() { + bind(J.class); + bind(K.class).in(Singleton.class); + } + }); + + assertEquals(1, J.nextInstanceId); + assertEquals(1, K.nextInstanceId); + } + + public void testScopesIsSingleton() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(J.class); + bind(K.class).in(Singleton.class); + } + }); + + assertTrue(Scopes.isSingleton(injector.getBinding(J.class))); + assertTrue(Scopes.isSingleton(injector.getBinding(K.class))); + } + + public void testInjectingFinalFieldsIsForbidden() { + try { + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(L.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "Injected field JakartaTest$L.b cannot be final."); + } + } + + public void testInjectingAbstractMethodsIsForbidden() { + try { + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(M.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains( + expected.getMessage(), + "Injected method JakartaTest$AbstractM.setB() cannot be abstract."); + } + } + + public void testInjectingMethodsWithTypeParametersIsForbidden() { + try { + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(N.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains( + expected.getMessage(), "Injected method JakartaTest$N.setB() cannot declare type "); + } + } + + public void testInjectingMethodsWithNonVoidReturnTypes() { + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(P.class); + } + }); + } + + // TODO(sameb): Uncomment this when Guice supports toProvider on jakarta.inject.Provider types. + // /** + // * This test verifies that we can compile bindings to provider instances whose compile-time + // type + // * implements jakarta.inject.Provider but not com.google.inject.Provider + // */ + // public void testBindProviderClass() { + // Injector injector = + // Guice.createInjector( + // new AbstractModule() { + // @Override + // protected void configure() { + // bind(B.class).toProvider(BProvider.class); + // bind(B.class).annotatedWith(Names.named("1")).toProvider(BProvider.class); + // + // bind(B.class).annotatedWith(Names.named("2")).toProvider(Key.get(BProvider.class)); + // bind(B.class) + // .annotatedWith(Names.named("3")) + // .toProvider(TypeLiteral.get(BProvider.class)); + // } + // }); + // + // injector.getInstance(Key.get(B.class)); + // injector.getInstance(Key.get(B.class, Names.named("1"))); + // injector.getInstance(Key.get(B.class, Names.named("2"))); + // injector.getInstance(Key.get(B.class, Names.named("3"))); + // } + + public void testGuicifyJakartaProvider() { + Provider jakartaProvider = + new Provider() { + @Override + public String get() { + return "A"; + } + + @Override + public String toString() { + return "jakartaProvider"; + } + }; + + com.google.inject.Provider guicified = Providers.guicify(jakartaProvider); + assertEquals("guicified(jakartaProvider)", guicified.toString()); + assertEquals("A", guicified.get()); + + // when you guicify the Guice-friendly, it's a no-op + assertSame(guicified, Providers.guicify(guicified)); + + assertFalse(guicified instanceof HasDependencies); + } + + public void testGuicifyWithDependencies() { + Provider jakartaProvider = + new Provider() { + @Inject double d; + int i; + + @Inject + void injectMe(int i) { + this.i = i; + } + + @Override + public String get() { + return d + "-" + i; + } + }; + + final com.google.inject.Provider guicified = Providers.guicify(jakartaProvider); + assertTrue(guicified instanceof HasDependencies); + Set> actual = ((HasDependencies) guicified).getDependencies(); + validateDependencies(actual, jakartaProvider.getClass()); + + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(String.class).toProvider(guicified); + bind(int.class).toInstance(1); + bind(double.class).toInstance(2.0d); + } + }); + + Binding binding = injector.getBinding(String.class); + assertEquals("2.0-1", binding.getProvider().get()); + validateDependencies(actual, jakartaProvider.getClass()); + } + + private void validateDependencies(Set> actual, Class owner) { + assertEquals(actual.toString(), 2, actual.size()); + Dependency dDep = null; + Dependency iDep = null; + for (Dependency dep : actual) { + if (dep.getKey().equals(Key.get(Double.class))) { + dDep = dep; + } else if (dep.getKey().equals(Key.get(Integer.class))) { + iDep = dep; + } + } + assertNotNull(dDep); + assertNotNull(iDep); + assertEquals(TypeLiteral.get(owner), dDep.getInjectionPoint().getDeclaringType()); + assertEquals("d", dDep.getInjectionPoint().getMember().getName()); + assertEquals(-1, dDep.getParameterIndex()); + + assertEquals(TypeLiteral.get(owner), iDep.getInjectionPoint().getDeclaringType()); + assertEquals("injectMe", iDep.getInjectionPoint().getMember().getName()); + assertEquals(0, iDep.getParameterIndex()); + } + + static class A { + final B b; + @Inject C c; + D d; + E e; + + @Inject + A(B b) { + this.b = b; + } + + @Inject + void injectD(D d, E e) { + this.d = d; + this.e = e; + } + } + + static class B {} + + static class C {} + + static class D {} + + static class E {} + + static class F { + final B b; + @Inject @Red C c; + D d; + E e; + + @Inject + F(@Named("jodie") B b) { + this.b = b; + } + + @Inject + void injectD(@Red D d, @Named("jesse") E e) { + this.d = d; + this.e = e; + } + } + + @Qualifier + @Retention(RUNTIME) + @interface Red {} + + public static final Red RED = + new Red() { + @Override + public Class annotationType() { + return Red.class; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Red; + } + + @Override + public int hashCode() { + return 0; + } + }; + + static class G { + final Provider bProvider; + @Inject Provider cProvider; + Provider dProvider; + Provider eProvider; + + @Inject + G(@Named("jodie") Provider bProvider) { + this.bProvider = bProvider; + } + + @Inject + void injectD(@Red Provider dProvider, Provider eProvider) { + this.dProvider = dProvider; + this.eProvider = eProvider; + } + } + + @jakarta.inject.Scope + @Retention(RUNTIME) + @interface TestScoped {} + + static class TestScope implements Scope { + private int now = 0; + + @Override + public com.google.inject.Provider scope( + Key key, final com.google.inject.Provider unscoped) { + return new com.google.inject.Provider() { + private T value; + private int snapshotTime = -1; + + @Override + public T get() { + if (snapshotTime != now) { + value = unscoped.get(); + snapshotTime = now; + } + return value; + } + }; + } + + public void reset() { + now++; + } + } + + @TestScoped + static class H {} + + @Singleton + static class J { + static int nextInstanceId = 0; + int instanceId = nextInstanceId++; + } + + static class K { + static int nextInstanceId = 0; + int instanceId = nextInstanceId++; + } + + static class L { + @SuppressWarnings("InjectJakartaInjectOnFinalField") + @Inject + final B b = null; + } + + abstract static class AbstractM { + @SuppressWarnings("JakartaInjectOnAbstractMethod") + @Inject + abstract void setB(B b); + } + + static class M extends AbstractM { + @Override + @SuppressWarnings("OverridesJakartaInjectableMethod") + void setB(B b) {} + } + + static class N { + @Inject + void setB(B b) {} + } + + static class P { + @Inject + B setB(B b) { + return b; + } + } + + static class BProvider implements Provider { + @Override + public B get() { + return new B(); + } + } +} diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/BUILD b/extensions/assistedinject/src/com/google/inject/assistedinject/BUILD index ba92f52bba..17bc7dd29d 100644 --- a/extensions/assistedinject/src/com/google/inject/assistedinject/BUILD +++ b/extensions/assistedinject/src/com/google/inject/assistedinject/BUILD @@ -24,6 +24,7 @@ java_library( "//third_party/java/error_prone:annotations", "//third_party/java/guava/base", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", ], ) diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java index 91ba86b1b0..1cb407eab8 100644 --- a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java +++ b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java @@ -304,7 +304,8 @@ public TypeLiteral getImplementationType() { Key paramKey = Annotations.getKey(param, method, paramAnnotations[p++], errors); Class underlylingType = paramKey.getTypeLiteral().getRawType(); if (underlylingType.equals(Provider.class) - || underlylingType.equals(javax.inject.Provider.class)) { + || underlylingType.equals(javax.inject.Provider.class) + || underlylingType.equals(jakarta.inject.Provider.class)) { errors.addMessage( "A Provider may not be a type in a factory method of an AssistedInject." + "\n Offending instance is parameter [%s] with key [%s] on method [%s]", diff --git a/extensions/assistedinject/test/com/google/inject/assistedinject/BUILD b/extensions/assistedinject/test/com/google/inject/assistedinject/BUILD index a05c1a11df..6d02380642 100644 --- a/extensions/assistedinject/test/com/google/inject/assistedinject/BUILD +++ b/extensions/assistedinject/test/com/google/inject/assistedinject/BUILD @@ -22,6 +22,7 @@ java_library( "//third_party/java/aopalliance", "//third_party/java/guava/base", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", "//third_party/java/junit", "//third_party/java/truth", diff --git a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java index 96cd219bda..f4b5383cf3 100644 --- a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java +++ b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java @@ -756,6 +756,56 @@ protected void configure() { } } + interface JakartaProviderBasedColoredCarFactory { + Car createCar( + jakarta.inject.Provider colorProvider, + jakarta.inject.Provider stringProvider); + + Mustang createMustang(@Assisted("color") jakarta.inject.Provider colorProvider); + } + + @Test + public void testAssistedJakartaProviderIsDisallowed() { + try { + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(JakartaProviderBasedColoredCarFactory.class) + .toProvider( + FactoryProvider.newFactory( + JakartaProviderBasedColoredCarFactory.class, Subaru.class)); + } + }); + fail(); + } catch (CreationException expected) { + assertEquals(expected.getMessage(), 4, expected.getErrorMessages().size()); + assertContains( + expected.getMessage(), + ") A Provider may not be a type in a factory method of an AssistedInject.\n" + + " Offending instance is parameter [1] with key" + + " [Provider annotated with @Assisted(" + + Annotations.memberValueString("value", "color") + + ")]" + + " on method" + + " [FactoryProvider2Test$JakartaProviderBasedColoredCarFactory.createMustang()]"); + assertContains( + expected.getMessage(), + ") A Provider may not be a type in a factory method of an AssistedInject.", + "Offending instance is parameter [1] with key [Provider] on" + + " method [FactoryProvider2Test$JakartaProviderBasedColoredCarFactory.createCar()]"); + assertContains( + expected.getMessage(), + ") A Provider may not be a type in a factory method of an AssistedInject.", + "Offending instance is parameter [2] with key [Provider] on method" + + " [FactoryProvider2Test$JakartaProviderBasedColoredCarFactory.createCar()]"); + assertContains( + expected.getMessage(), + "No implementation for FactoryProvider2Test$JakartaProviderBasedColoredCarFactory was" + + " bound."); + } + } + @Test public void testFactoryUseBeforeInitialization() { ColoredCarFactory carFactory = diff --git a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Annotations.java b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Annotations.java index d365b4c191..e92c9d0034 100644 --- a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Annotations.java +++ b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Annotations.java @@ -22,9 +22,20 @@ import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Optional; +import java.util.Set; /** Extensions for {@link Annotation}. */ final class Annotations { + static Optional getAnnotatedAnnotation( + AnnotatedElement element, Set> annotationClasses) { + return Arrays.stream(element.getAnnotations()) + .filter( + annotation -> + annotationClasses.stream() + .anyMatch(an -> annotation.annotationType().isAnnotationPresent(an))) + .collect(toOptional()); + } + static Optional getAnnotatedAnnotation( AnnotatedElement element, Class annotationClass) { return Arrays.stream(element.getAnnotations()) diff --git a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/BUILD b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/BUILD index cb87e8aaed..bd7f8ac630 100644 --- a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/BUILD +++ b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/BUILD @@ -24,6 +24,7 @@ java_library( "//third_party/java/dagger", "//third_party/java/guava/base", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", ], ) diff --git a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Keys.java b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Keys.java index ce523188ec..c2e7ad9d31 100644 --- a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Keys.java +++ b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/Keys.java @@ -18,17 +18,20 @@ import static com.google.inject.daggeradapter.Annotations.getAnnotatedAnnotation; +import com.google.common.collect.ImmutableSet; import com.google.inject.Key; import java.lang.annotation.Annotation; import java.lang.reflect.Parameter; import java.lang.reflect.Type; import java.util.Optional; -import javax.inject.Qualifier; /** Utility methods for creating {@link Key}s. */ final class Keys { + private static final ImmutableSet> QUALIFIERS = + ImmutableSet.of(javax.inject.Qualifier.class, jakarta.inject.Qualifier.class); + static Key parameterKey(Parameter parameter) { - Optional qualifier = getAnnotatedAnnotation(parameter, Qualifier.class); + Optional qualifier = getAnnotatedAnnotation(parameter, QUALIFIERS); Type type = parameter.getParameterizedType(); return qualifier.isPresent() ? Key.get(type, qualifier.get()) : Key.get(type); } diff --git a/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BUILD b/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BUILD index 91a60ec0c2..9acd27104a 100644 --- a/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BUILD +++ b/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BUILD @@ -18,6 +18,7 @@ java_library( "//third_party/java/dagger:producers", "//third_party/java/guava/base", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", "//third_party/java/junit", "//third_party/java/truth", diff --git a/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BindsTest.java b/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BindsTest.java index a5f57ef4ae..c8f38e8f66 100644 --- a/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BindsTest.java +++ b/extensions/dagger-adapter/test/com/google/inject/daggeradapter/BindsTest.java @@ -164,4 +164,43 @@ public void testQualifiers() { assertThat(qualifiedBinds).hasProvidedValueThat().isEqualTo("qualifiers"); assertThat(qualifiedBinds).hasSource(QualifiedBinds.class, "bindsToProvides", String.class); } + + @Retention(RetentionPolicy.RUNTIME) + @jakarta.inject.Qualifier + @interface JakartaProvidesQualifier {} + + @Retention(RetentionPolicy.RUNTIME) + @jakarta.inject.Qualifier + @interface JakartaBindsQualifier {} + + @Module + interface JakartaQualifiedBinds { + @Provides + @JakartaProvidesQualifier + static String provides() { + return "jakarta qualified!"; + } + + @Binds + @JakartaBindsQualifier + String bindsToProvides(@JakartaProvidesQualifier String provides); + + @Binds + String unqualifiedToBinds(@JakartaBindsQualifier String binds); + } + + public void testJakartaQualifiers() { + Injector injector = Guice.createInjector(DaggerAdapter.from(JakartaQualifiedBinds.class)); + + Binding stringBinding = injector.getBinding(String.class); + assertThat(stringBinding).hasProvidedValueThat().isEqualTo("jakarta qualified!"); + assertThat(stringBinding) + .hasSource(JakartaQualifiedBinds.class, "unqualifiedToBinds", String.class); + + Binding qualifiedBinds = + injector.getBinding(Key.get(String.class, JakartaBindsQualifier.class)); + assertThat(qualifiedBinds).hasProvidedValueThat().isEqualTo("jakarta qualified!"); + assertThat(qualifiedBinds) + .hasSource(JakartaQualifiedBinds.class, "bindsToProvides", String.class); + } } diff --git a/extensions/persist/src/com/google/inject/persist/BUILD b/extensions/persist/src/com/google/inject/persist/BUILD index b0e35d1f33..b918644afd 100644 --- a/extensions/persist/src/com/google/inject/persist/BUILD +++ b/extensions/persist/src/com/google/inject/persist/BUILD @@ -26,6 +26,7 @@ java_library( "//third_party/java/guava/annotations", "//third_party/java/guava/base", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/javax_persistence", "//third_party/java/jsr330_inject", "//third_party/java/servlet/servlet_api", diff --git a/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java b/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java index e37ddca199..a190b097df 100644 --- a/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java +++ b/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java @@ -124,6 +124,9 @@ private void bindQueryNamedParameters( } else if (annotation instanceof javax.inject.Named) { javax.inject.Named named = (javax.inject.Named) annotation; jpaQuery.setParameter(named.value(), argument); + } else if (annotation instanceof jakarta.inject.Named) { + jakarta.inject.Named named = (jakarta.inject.Named) annotation; + jpaQuery.setParameter(named.value(), argument); } else if (annotation instanceof FirstResult) { jpaQuery.setFirstResult((Integer) argument); } else if (annotation instanceof MaxResults) { @@ -182,7 +185,9 @@ private JpaFinderProxy.FinderDescriptor getFinderDescriptor(MethodInvocation inv for (Annotation annotation : annotations) { //discover the named, first or max annotations then break out Class annotationType = annotation.annotationType(); - if (Named.class.equals(annotationType) || javax.inject.Named.class.equals(annotationType)) { + if (Named.class.equals(annotationType) + || javax.inject.Named.class.equals(annotationType) + || jakarta.inject.Named.class.equals(annotationType)) { discoveredAnnotations[i] = annotation; finderDescriptor.isBindAsRawParameters = false; break; diff --git a/extensions/servlet/test/com/google/inject/servlet/ContextPathTest.java b/extensions/servlet/test/com/google/inject/servlet/ContextPathTest.java index 300462fbd5..973aa9d24c 100644 --- a/extensions/servlet/test/com/google/inject/servlet/ContextPathTest.java +++ b/extensions/servlet/test/com/google/inject/servlet/ContextPathTest.java @@ -71,7 +71,7 @@ protected void configureServlets() { .in(Scopes.SINGLETON); serve("/foo/*").with(Key.get(TestServlet.class, Names.named("foo"))); serve("/bar/*").with(Key.get(TestServlet.class, Names.named("bar"))); - // TODO - call and validate it is correct + // TODO: add a filter(..) call and validate it is correct } }); injector.injectMembers(this); diff --git a/extensions/testlib/src/com/google/inject/testing/fieldbinder/BUILD b/extensions/testlib/src/com/google/inject/testing/fieldbinder/BUILD index 4611ab65e4..7adcd2c33b 100644 --- a/extensions/testlib/src/com/google/inject/testing/fieldbinder/BUILD +++ b/extensions/testlib/src/com/google/inject/testing/fieldbinder/BUILD @@ -22,6 +22,7 @@ java_library( "//third_party/java/error_prone:annotations", "//third_party/java/guava/base", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", ], ) diff --git a/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java b/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java index d4a7845f74..e04b4c1dfb 100644 --- a/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java +++ b/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java @@ -64,11 +64,12 @@ *

  • If {@link Bind#lazy} is true, this module will delay reading the value from the field until * injection time, allowing the field's value to be reassigned during the course of a test's * execution. - *
  • If a {@link BindingAnnotation} or {@link javax.inject.Qualifier} is present on the field, - * that field will be bound using that annotation via {@link - * AnnotatedBindingBuilder#annotatedWith}. For example, {@code + *
  • If a {@link BindingAnnotation}, {@link javax.inject.Qualifier} or {@link + * jakarta.inject.Qualifier} is present on the field, that field will be bound using that + * annotation via {@link AnnotatedBindingBuilder#annotatedWith}. For example, {@code * bind(Foo.class).annotatedWith(BarAnnotation.class).toInstance(theValue)}. It is an error to - * supply more than one {@link BindingAnnotation} or {@link javax.inject.Qualifier}. + * supply more than one {@link BindingAnnotation}, {@link javax.inject.Qualifier} or {@link + * jakarta.inject.Qualifier}. *
  • If the field is of type {@link Provider}, the field's value will be bound as a {@link * Provider} using {@link LinkedBindingBuilder#toProvider} to the provider's parameterized * type. For example, {@code Provider} binds to {@link Integer}. Attempting to bind a @@ -434,7 +435,8 @@ private Optional getBoundFieldInfo( private static boolean hasInject(Field field) { return field.isAnnotationPresent(javax.inject.Inject.class) - || field.isAnnotationPresent(com.google.inject.Inject.class); + || field.isAnnotationPresent(com.google.inject.Inject.class) + || field.isAnnotationPresent(jakarta.inject.Inject.class); } /** @@ -457,7 +459,9 @@ private static boolean hasInject(Field field) { * bind those subclasses directly, enabling them to inject the providers themselves. */ private static boolean isTransparentProvider(Class clazz) { - return com.google.inject.Provider.class == clazz || javax.inject.Provider.class == clazz; + return com.google.inject.Provider.class == clazz + || javax.inject.Provider.class == clazz + || jakarta.inject.Provider.class == clazz; } private static void bindField(Binder binder, final BoundFieldInfo fieldInfo) { @@ -480,15 +484,28 @@ private static void bindField(Binder binder, final BoundFieldInfo fieldInfo) { @Override // @Nullable public Object get() { - javax.inject.Provider provider = - (javax.inject.Provider) getFieldValue(fieldInfo); - return provider.get(); + Object val = getFieldValue(fieldInfo); + if (val instanceof javax.inject.Provider) { + return ((javax.inject.Provider) val).get(); + } else if (val instanceof jakarta.inject.Provider) { + return ((jakarta.inject.Provider) val).get(); + } else { + throw new IllegalStateException( + "unexpected field value: " + val + ", of type: " + val.getClass()); + } } }); } else { - javax.inject.Provider fieldValueUnsafe = - (javax.inject.Provider) getFieldValue(fieldInfo); - binderUnsafe.toProvider(fieldValueUnsafe); + Object val = getFieldValue(fieldInfo); + if (val instanceof javax.inject.Provider) { + binderUnsafe.toProvider((javax.inject.Provider) val); + } else if (val instanceof jakarta.inject.Provider) { + // TODO(sameb): bind directly without using guicify when the API exists. + binderUnsafe.toProvider(Providers.guicify((jakarta.inject.Provider) val)); + } else { + throw new IllegalStateException( + "unexpected field value: " + val + ", of type: " + val.getClass()); + } } } else if (fieldInfo.bindAnnotation.lazy()) { binderUnsafe.toProvider( diff --git a/extensions/testlib/test/com/google/inject/testing/fieldbinder/BUILD b/extensions/testlib/test/com/google/inject/testing/fieldbinder/BUILD index c7c3db3703..b51b6f8e13 100644 --- a/extensions/testlib/test/com/google/inject/testing/fieldbinder/BUILD +++ b/extensions/testlib/test/com/google/inject/testing/fieldbinder/BUILD @@ -15,6 +15,7 @@ java_library( "//core/test/com/google/inject:testsupport", "//extensions/testlib/src/com/google/inject/testing/fieldbinder", "//third_party/java/guava/collect", + "//third_party/java/jakarta_inject", "//third_party/java/jsr330_inject", "//third_party/java/junit", ], diff --git a/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java b/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java index d39a74613b..11cfe61b01 100644 --- a/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java +++ b/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java @@ -258,6 +258,27 @@ public void testBindingWithQualifier() { assertEquals(testValue2, injector.getInstance(Key.get(Integer.class, SomeQualifier.class))); } + @jakarta.inject.Qualifier + @Retention(RUNTIME) + private static @interface SomeJakartaQualifier {} + + public void testBindingWithJakartaQualifier() { + final Integer testValue1 = 1024, testValue2 = 2048; + Object instance = + new Object() { + @Bind private Integer anInt = testValue1; + + @Bind @SomeJakartaQualifier private Integer anotherInt = testValue2; + }; + + BoundFieldModule module = BoundFieldModule.of(instance); + Injector injector = Guice.createInjector(module); + + assertEquals(testValue1, injector.getInstance(Integer.class)); + assertEquals( + testValue2, injector.getInstance(Key.get(Integer.class, SomeJakartaQualifier.class))); + } + public void testCanReuseBindingAnnotationsWithDifferentValues() { final Integer testValue1 = 1024, testValue2 = 2048; final String name1 = "foo", name2 = "bar"; @@ -441,6 +462,26 @@ public Integer get() { assertEquals(testValue, injector.getInstance(Integer.class)); } + public void testBindingJakartaProvider() { + final Integer testValue = 1024; + Object instance = + new Object() { + @Bind + private jakarta.inject.Provider anInt = + new jakarta.inject.Provider() { + @Override + public Integer get() { + return testValue; + } + }; + }; + + BoundFieldModule module = BoundFieldModule.of(instance); + Injector injector = Guice.createInjector(module); + + assertEquals(testValue, injector.getInstance(Integer.class)); + } + public void testBindingNonNullableNullField() { Object instance = new Object() { diff --git a/pom.xml b/pom.xml index 5935c59e1e..796568a3a8 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,16 @@ See the Apache License Version 2.0 for the specific language governing permissio javax.inject-tck 1 + + jakarta.inject + jakarta.inject-api + 2.0.1 + + + jakarta.inject + jakarta.inject-tck + 2.0.1 + aopalliance aopalliance diff --git a/third_party/java/jakarta_inject/BUILD b/third_party/java/jakarta_inject/BUILD new file mode 100644 index 0000000000..b38dc7046c --- /dev/null +++ b/third_party/java/jakarta_inject/BUILD @@ -0,0 +1,6 @@ +package(default_visibility = ["//:src"]) + +alias( + name = "jakarta_inject", + actual = "@maven//:jakarta_inject_jakarta_inject_api", +) diff --git a/third_party/java/jakarta_inject_tck/BUILD b/third_party/java/jakarta_inject_tck/BUILD new file mode 100644 index 0000000000..83692b7ae5 --- /dev/null +++ b/third_party/java/jakarta_inject_tck/BUILD @@ -0,0 +1,6 @@ +package(default_visibility = ["//:src"]) + +alias( + name = "jakarta_inject_tck", + actual = "@maven//:jakarta_inject_jakarta_inject_tck", +)