From 0bb5cf504bff78f49598c744f7e763b0f54896af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Thu, 7 Jan 2016 19:22:16 +0100 Subject: [PATCH] initial commit --- .gitignore | 12 + .travis.yml | 7 + LICENSE.txt | 202 +++ README.md | 32 + build.gradle | 90 ++ pom.xml | 100 ++ settings.gradle | 1 + .../com/google/inject/AbstractModule.java | 220 +++ src/main/java/com/google/inject/Binder.java | 451 ++++++ src/main/java/com/google/inject/Binding.java | 71 + .../com/google/inject/BindingAnnotation.java | 25 + .../google/inject/ConfigurationException.java | 64 + .../com/google/inject/CreationException.java | 40 + src/main/java/com/google/inject/Exposed.java | 19 + src/main/java/com/google/inject/Guice.java | 86 ++ .../java/com/google/inject/ImplementedBy.java | 20 + src/main/java/com/google/inject/Inject.java | 51 + src/main/java/com/google/inject/Injector.java | 228 +++ src/main/java/com/google/inject/Key.java | 508 +++++++ .../com/google/inject/MembersInjector.java | 22 + src/main/java/com/google/inject/Module.java | 27 + .../java/com/google/inject/PrivateBinder.java | 35 + .../java/com/google/inject/PrivateModule.java | 279 ++++ .../java/com/google/inject/ProvidedBy.java | 20 + src/main/java/com/google/inject/Provider.java | 36 + src/main/java/com/google/inject/Provides.java | 18 + .../com/google/inject/ProvisionException.java | 49 + src/main/java/com/google/inject/Scope.java | 41 + .../com/google/inject/ScopeAnnotation.java | 24 + src/main/java/com/google/inject/Scopes.java | 162 ++ .../java/com/google/inject/Singleton.java | 17 + src/main/java/com/google/inject/Stage.java | 26 + .../java/com/google/inject/TypeLiteral.java | 321 ++++ .../binder/AnnotatedBindingBuilder.java | 20 + .../AnnotatedConstantBindingBuilder.java | 20 + .../binder/AnnotatedElementBuilder.java | 19 + .../inject/binder/ConstantBindingBuilder.java | 62 + .../inject/binder/LinkedBindingBuilder.java | 79 + .../inject/binder/ScopedBindingBuilder.java | 29 + .../internal/AbstractBindingBuilder.java | 118 ++ .../internal/AbstractBindingProcessor.java | 146 ++ .../inject/internal/AbstractProcessor.java | 54 + .../google/inject/internal/Annotations.java | 334 +++++ .../inject/internal/BindingBuilder.java | 167 +++ .../google/inject/internal/BindingImpl.java | 100 ++ .../inject/internal/BindingProcessor.java | 180 +++ .../inject/internal/BoundProviderFactory.java | 66 + .../internal/CircularDependencyProxy.java | 5 + .../internal/ConstantBindingBuilderImpl.java | 115 ++ .../inject/internal/ConstantFactory.java | 24 + .../inject/internal/ConstructionContext.java | 75 + .../inject/internal/ConstructionProxy.java | 30 + .../internal/ConstructionProxyFactory.java | 12 + .../internal/ConstructorBindingImpl.java | 258 ++++ .../inject/internal/ConstructorInjector.java | 112 ++ .../internal/ConstructorInjectorStore.java | 63 + .../inject/internal/ContextualCallable.java | 5 + .../inject/internal/CreationListener.java | 12 + .../inject/internal/CycleDetectingLock.java | 308 ++++ .../DefaultConstructionProxyFactory.java | 57 + .../inject/internal/DeferredLookups.java | 45 + .../inject/internal/DelayedInitialize.java | 15 + .../internal/DelegatingInvocationHandler.java | 44 + .../google/inject/internal/EncounterImpl.java | 98 ++ .../com/google/inject/internal/Errors.java | 817 ++++++++++ .../inject/internal/ErrorsException.java | 20 + .../google/inject/internal/Exceptions.java | 46 + .../inject/internal/ExposedBindingImpl.java | 52 + .../inject/internal/ExposedKeyFactory.java | 40 + .../inject/internal/ExposureBuilder.java | 54 + .../google/inject/internal/FactoryProxy.java | 53 + .../google/inject/internal/FailableCache.java | 44 + .../inject/internal/InheritingState.java | 160 ++ .../google/inject/internal/Initializable.java | 12 + .../inject/internal/Initializables.java | 20 + .../google/inject/internal/Initializer.java | 181 +++ .../internal/InjectionRequestProcessor.java | 119 ++ .../google/inject/internal/InjectorImpl.java | 1142 ++++++++++++++ .../internal/InjectorOptionsProcessor.java | 71 + .../google/inject/internal/InjectorShell.java | 296 ++++ .../inject/internal/InstanceBindingImpl.java | 102 ++ .../inject/internal/InternalContext.java | 136 ++ .../inject/internal/InternalFactory.java | 20 + ...InternalFactoryToInitializableAdapter.java | 45 + .../InternalFactoryToProviderAdapter.java | 32 + .../google/inject/internal/InternalFlags.java | 132 ++ .../internal/InternalInjectorCreator.java | 308 ++++ .../inject/internal/LinkedBindingImpl.java | 81 + .../internal/LinkedProviderBindingImpl.java | 108 ++ .../internal/ListenerBindingProcessor.java | 26 + .../inject/internal/LookupProcessor.java | 42 + .../com/google/inject/internal/Lookups.java | 17 + .../inject/internal/MembersInjectorImpl.java | 138 ++ .../inject/internal/MembersInjectorStore.java | 124 ++ .../inject/internal/MessageProcessor.java | 32 + ...ModuleAnnotatedMethodScannerProcessor.java | 19 + .../com/google/inject/internal/MoreTypes.java | 535 +++++++ .../google/inject/internal/Nullability.java | 32 + .../internal/PrivateElementProcessor.java | 31 + .../inject/internal/PrivateElementsImpl.java | 126 ++ .../inject/internal/ProcessedBindingData.java | 36 + .../internal/ProvidedByInternalFactory.java | 73 + .../internal/ProviderInstanceBindingImpl.java | 104 ++ .../internal/ProviderInternalFactory.java | 67 + .../inject/internal/ProviderMethod.java | 220 +++ .../internal/ProviderMethodsModule.java | 281 ++++ .../ProviderToInternalFactoryAdapter.java | 41 + .../ProvisionListenerCallbackStore.java | 114 ++ .../ProvisionListenerStackCallback.java | 137 ++ .../internal/ScopeBindingProcessor.java | 46 + .../com/google/inject/internal/Scoping.java | 273 ++++ .../inject/internal/SingleFieldInjector.java | 48 + .../inject/internal/SingleMemberInjector.java | 12 + .../inject/internal/SingleMethodInjector.java | 65 + .../internal/SingleParameterInjector.java | 55 + .../inject/internal/SingletonScope.java | 341 +++++ .../com/google/inject/internal/State.java | 185 +++ .../TypeConverterBindingProcessor.java | 172 +++ .../inject/internal/UniqueAnnotations.java | 58 + .../internal/UntargettedBindingImpl.java | 64 + .../internal/UntargettedBindingProcessor.java | 52 + .../google/inject/internal/WeakKeySet.java | 137 ++ .../google/inject/internal/util/Classes.java | 64 + .../inject/internal/util/SourceProvider.java | 93 ++ .../internal/util/StackTraceElements.java | 165 +++ .../inject/internal/util/Stopwatch.java | 31 + .../inject/matcher/AbstractMatcher.java | 76 + .../com/google/inject/matcher/Matcher.java | 24 + .../com/google/inject/matcher/Matchers.java | 385 +++++ .../java/com/google/inject/name/Named.java | 19 + .../com/google/inject/name/NamedImpl.java | 40 + .../java/com/google/inject/name/Names.java | 53 + .../inject/spi/BindingScopingVisitor.java | 40 + .../inject/spi/BindingTargetVisitor.java | 66 + .../google/inject/spi/ConstructorBinding.java | 28 + .../inject/spi/ConvertedConstantBinding.java | 34 + .../spi/DefaultBindingScopingVisitor.java | 38 + .../spi/DefaultBindingTargetVisitor.java | 57 + .../inject/spi/DefaultElementVisitor.java | 84 ++ .../com/google/inject/spi/Dependency.java | 112 ++ .../inject/spi/DependencyAndSource.java | 60 + .../spi/DisableCircularProxiesOption.java | 28 + .../java/com/google/inject/spi/Element.java | 45 + .../com/google/inject/spi/ElementSource.java | 168 +++ .../com/google/inject/spi/ElementVisitor.java | 96 ++ .../java/com/google/inject/spi/Elements.java | 547 +++++++ .../com/google/inject/spi/ExposedBinding.java | 21 + .../google/inject/spi/HasDependencies.java | 19 + .../google/inject/spi/InjectionListener.java | 15 + .../com/google/inject/spi/InjectionPoint.java | 856 +++++++++++ .../google/inject/spi/InjectionRequest.java | 68 + .../google/inject/spi/InstanceBinding.java | 26 + .../google/inject/spi/LinkedKeyBinding.java | 17 + .../inject/spi/MembersInjectorLookup.java | 86 ++ .../java/com/google/inject/spi/Message.java | 100 ++ .../spi/ModuleAnnotatedMethodScanner.java | 37 + .../ModuleAnnotatedMethodScannerBinding.java | 41 + .../com/google/inject/spi/ModuleSource.java | 170 +++ .../google/inject/spi/PrivateElements.java | 42 + .../google/inject/spi/ProviderBinding.java | 19 + .../inject/spi/ProviderInstanceBinding.java | 26 + .../google/inject/spi/ProviderKeyBinding.java | 19 + .../com/google/inject/spi/ProviderLookup.java | 100 ++ .../inject/spi/ProviderWithDependencies.java | 10 + .../spi/ProviderWithExtensionVisitor.java | 44 + .../inject/spi/ProvidesMethodBinding.java | 38 + .../spi/ProvidesMethodTargetVisitor.java | 17 + .../google/inject/spi/ProvisionListener.java | 58 + .../inject/spi/ProvisionListenerBinding.java | 54 + .../RequireAtInjectOnConstructorsOption.java | 29 + .../RequireExactBindingAnnotationsOption.java | 28 + .../spi/RequireExplicitBindingsOption.java | 28 + .../com/google/inject/spi/ScopeBinding.java | 49 + .../inject/spi/StaticInjectionRequest.java | 59 + .../java/com/google/inject/spi/Toolable.java | 27 + .../com/google/inject/spi/TypeConverter.java | 14 + .../inject/spi/TypeConverterBinding.java | 55 + .../com/google/inject/spi/TypeEncounter.java | 84 ++ .../com/google/inject/spi/TypeListener.java | 24 + .../inject/spi/TypeListenerBinding.java | 53 + .../google/inject/spi/UntargettedBinding.java | 11 + .../java/com/google/inject/util/Modules.java | 355 +++++ .../com/google/inject/util/Providers.java | 56 + .../java/com/google/inject/util/Types.java | 116 ++ src/test/java/com/google/inject/Asserts.java | 191 +++ .../java/com/google/inject/BinderTest.java | 633 ++++++++ .../com/google/inject/BinderTestSuite.java | 798 ++++++++++ .../google/inject/BindingAnnotationTest.java | 169 +++ .../com/google/inject/BindingOrderTest.java | 99 ++ .../java/com/google/inject/BindingTest.java | 472 ++++++ .../inject/BoundInstanceInjectionTest.java | 119 ++ .../com/google/inject/BoundProviderTest.java | 102 ++ .../google/inject/CircularDependencyTest.java | 598 ++++++++ .../google/inject/DuplicateBindingsTest.java | 576 ++++++++ .../com/google/inject/EagerSingletonTest.java | 93 ++ .../com/google/inject/ErrorHandlingTest.java | 150 ++ .../google/inject/GenericInjectionTest.java | 187 +++ .../google/inject/ImplicitBindingTest.java | 395 +++++ .../java/com/google/inject/InjectorTest.java | 413 ++++++ .../Java8LanguageFeatureBindingTest.java | 157 ++ .../com/google/inject/JitBindingsTest.java | 731 +++++++++ src/test/java/com/google/inject/KeyTest.java | 321 ++++ .../google/inject/MembersInjectorTest.java | 287 ++++ .../java/com/google/inject/ModuleTest.java | 56 + .../java/com/google/inject/ModulesTest.java | 80 + .../inject/NullableInjectionPointTest.java | 251 ++++ .../google/inject/OptionalBindingTest.java | 298 ++++ .../com/google/inject/ParentInjectorTest.java | 305 ++++ .../com/google/inject/PrivateModuleTest.java | 564 +++++++ .../google/inject/ProviderInjectionTest.java | 187 +++ .../google/inject/ProvisionExceptionTest.java | 351 +++++ .../inject/ProvisionExceptionsTest.java | 178 +++ .../google/inject/ProvisionListenerTest.java | 796 ++++++++++ .../com/google/inject/ReflectionTest.java | 85 ++ .../google/inject/RequestInjectionTest.java | 235 +++ .../RequireAtInjectOnConstructorsTest.java | 202 +++ .../java/com/google/inject/ScopesTest.java | 1192 +++++++++++++++ .../java/com/google/inject/SuiteUtils.java | 47 + .../com/google/inject/SuperclassTest.java | 56 + .../com/google/inject/TypeConversionTest.java | 495 +++++++ .../com/google/inject/TypeListenerTest.java | 662 +++++++++ .../inject/TypeLiteralInjectionTest.java | 111 ++ .../com/google/inject/TypeLiteralTest.java | 198 +++ .../inject/TypeLiteralTypeResolutionTest.java | 331 +++++ .../ClientServiceWithDependencyInjection.java | 95 ++ .../example/ClientServiceWithFactories.java | 91 ++ .../example/ClientServiceWithGuice.java | 90 ++ .../ClientServiceWithGuiceDefaults.java | 86 ++ .../google/inject/example/JndiProvider.java | 53 + .../inject/example/JndiProviderClient.java | 44 + .../internal/CycleDetectingLockTest.java | 101 ++ .../google/inject/internal/MoreTypesTest.java | 54 + .../internal/UniqueAnnotationsTest.java | 40 + .../inject/internal/WeakKeySetTest.java | 518 +++++++ .../inject/internal/WeakKeySetUtils.java | 102 ++ .../google/inject/matcher/MatcherTest.java | 178 +++ .../inject/name/NamedEquivalanceTest.java | 258 ++++ .../com/google/inject/name/NamesTest.java | 112 ++ .../inject/spi/BindingTargetVisitorTest.java | 37 + .../google/inject/spi/ElementApplyToTest.java | 31 + .../google/inject/spi/ElementSourceTest.java | 175 +++ .../com/google/inject/spi/ElementsTest.java | 1308 +++++++++++++++++ .../spi/FailingBindingScopingVisitor.java | 42 + .../inject/spi/FailingElementVisitor.java | 25 + .../inject/spi/FailingTargetVisitor.java | 27 + .../inject/spi/HasDependenciesTest.java | 100 ++ .../google/inject/spi/InjectionPointTest.java | 366 +++++ .../google/inject/spi/InjectorSpiTest.java | 74 + .../spi/ModuleAnnotatedMethodScannerTest.java | 394 +++++ .../google/inject/spi/ModuleRewriterTest.java | 114 ++ .../google/inject/spi/ModuleSourceTest.java | 125 ++ .../inject/spi/ProviderMethodsTest.java | 905 ++++++++++++ .../google/inject/spi/SpiBindingsTest.java | 463 ++++++ .../inject/spi/ToolStageInjectorTest.java | 165 +++ .../google/inject/util/NoopOverrideTest.java | 32 + .../inject/util/OverrideModuleTest.java | 731 +++++++++ .../com/google/inject/util/ProvidersTest.java | 96 ++ .../com/google/inject/util/TypesTest.java | 215 +++ 258 files changed, 39412 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 build.gradle create mode 100644 pom.xml create mode 100644 settings.gradle create mode 100644 src/main/java/com/google/inject/AbstractModule.java create mode 100644 src/main/java/com/google/inject/Binder.java create mode 100644 src/main/java/com/google/inject/Binding.java create mode 100644 src/main/java/com/google/inject/BindingAnnotation.java create mode 100644 src/main/java/com/google/inject/ConfigurationException.java create mode 100644 src/main/java/com/google/inject/CreationException.java create mode 100644 src/main/java/com/google/inject/Exposed.java create mode 100644 src/main/java/com/google/inject/Guice.java create mode 100644 src/main/java/com/google/inject/ImplementedBy.java create mode 100644 src/main/java/com/google/inject/Inject.java create mode 100644 src/main/java/com/google/inject/Injector.java create mode 100644 src/main/java/com/google/inject/Key.java create mode 100644 src/main/java/com/google/inject/MembersInjector.java create mode 100644 src/main/java/com/google/inject/Module.java create mode 100644 src/main/java/com/google/inject/PrivateBinder.java create mode 100644 src/main/java/com/google/inject/PrivateModule.java create mode 100644 src/main/java/com/google/inject/ProvidedBy.java create mode 100644 src/main/java/com/google/inject/Provider.java create mode 100644 src/main/java/com/google/inject/Provides.java create mode 100644 src/main/java/com/google/inject/ProvisionException.java create mode 100644 src/main/java/com/google/inject/Scope.java create mode 100644 src/main/java/com/google/inject/ScopeAnnotation.java create mode 100644 src/main/java/com/google/inject/Scopes.java create mode 100644 src/main/java/com/google/inject/Singleton.java create mode 100644 src/main/java/com/google/inject/Stage.java create mode 100644 src/main/java/com/google/inject/TypeLiteral.java create mode 100644 src/main/java/com/google/inject/binder/AnnotatedBindingBuilder.java create mode 100644 src/main/java/com/google/inject/binder/AnnotatedConstantBindingBuilder.java create mode 100644 src/main/java/com/google/inject/binder/AnnotatedElementBuilder.java create mode 100644 src/main/java/com/google/inject/binder/ConstantBindingBuilder.java create mode 100644 src/main/java/com/google/inject/binder/LinkedBindingBuilder.java create mode 100644 src/main/java/com/google/inject/binder/ScopedBindingBuilder.java create mode 100644 src/main/java/com/google/inject/internal/AbstractBindingBuilder.java create mode 100644 src/main/java/com/google/inject/internal/AbstractBindingProcessor.java create mode 100644 src/main/java/com/google/inject/internal/AbstractProcessor.java create mode 100644 src/main/java/com/google/inject/internal/Annotations.java create mode 100644 src/main/java/com/google/inject/internal/BindingBuilder.java create mode 100644 src/main/java/com/google/inject/internal/BindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/BindingProcessor.java create mode 100644 src/main/java/com/google/inject/internal/BoundProviderFactory.java create mode 100644 src/main/java/com/google/inject/internal/CircularDependencyProxy.java create mode 100644 src/main/java/com/google/inject/internal/ConstantBindingBuilderImpl.java create mode 100644 src/main/java/com/google/inject/internal/ConstantFactory.java create mode 100644 src/main/java/com/google/inject/internal/ConstructionContext.java create mode 100644 src/main/java/com/google/inject/internal/ConstructionProxy.java create mode 100644 src/main/java/com/google/inject/internal/ConstructionProxyFactory.java create mode 100644 src/main/java/com/google/inject/internal/ConstructorBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/ConstructorInjector.java create mode 100644 src/main/java/com/google/inject/internal/ConstructorInjectorStore.java create mode 100644 src/main/java/com/google/inject/internal/ContextualCallable.java create mode 100644 src/main/java/com/google/inject/internal/CreationListener.java create mode 100644 src/main/java/com/google/inject/internal/CycleDetectingLock.java create mode 100644 src/main/java/com/google/inject/internal/DefaultConstructionProxyFactory.java create mode 100644 src/main/java/com/google/inject/internal/DeferredLookups.java create mode 100644 src/main/java/com/google/inject/internal/DelayedInitialize.java create mode 100644 src/main/java/com/google/inject/internal/DelegatingInvocationHandler.java create mode 100644 src/main/java/com/google/inject/internal/EncounterImpl.java create mode 100644 src/main/java/com/google/inject/internal/Errors.java create mode 100644 src/main/java/com/google/inject/internal/ErrorsException.java create mode 100644 src/main/java/com/google/inject/internal/Exceptions.java create mode 100644 src/main/java/com/google/inject/internal/ExposedBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/ExposedKeyFactory.java create mode 100644 src/main/java/com/google/inject/internal/ExposureBuilder.java create mode 100644 src/main/java/com/google/inject/internal/FactoryProxy.java create mode 100644 src/main/java/com/google/inject/internal/FailableCache.java create mode 100644 src/main/java/com/google/inject/internal/InheritingState.java create mode 100644 src/main/java/com/google/inject/internal/Initializable.java create mode 100644 src/main/java/com/google/inject/internal/Initializables.java create mode 100644 src/main/java/com/google/inject/internal/Initializer.java create mode 100644 src/main/java/com/google/inject/internal/InjectionRequestProcessor.java create mode 100644 src/main/java/com/google/inject/internal/InjectorImpl.java create mode 100644 src/main/java/com/google/inject/internal/InjectorOptionsProcessor.java create mode 100644 src/main/java/com/google/inject/internal/InjectorShell.java create mode 100644 src/main/java/com/google/inject/internal/InstanceBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/InternalContext.java create mode 100644 src/main/java/com/google/inject/internal/InternalFactory.java create mode 100644 src/main/java/com/google/inject/internal/InternalFactoryToInitializableAdapter.java create mode 100644 src/main/java/com/google/inject/internal/InternalFactoryToProviderAdapter.java create mode 100644 src/main/java/com/google/inject/internal/InternalFlags.java create mode 100644 src/main/java/com/google/inject/internal/InternalInjectorCreator.java create mode 100644 src/main/java/com/google/inject/internal/LinkedBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/LinkedProviderBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/ListenerBindingProcessor.java create mode 100644 src/main/java/com/google/inject/internal/LookupProcessor.java create mode 100644 src/main/java/com/google/inject/internal/Lookups.java create mode 100644 src/main/java/com/google/inject/internal/MembersInjectorImpl.java create mode 100644 src/main/java/com/google/inject/internal/MembersInjectorStore.java create mode 100644 src/main/java/com/google/inject/internal/MessageProcessor.java create mode 100644 src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java create mode 100644 src/main/java/com/google/inject/internal/MoreTypes.java create mode 100644 src/main/java/com/google/inject/internal/Nullability.java create mode 100644 src/main/java/com/google/inject/internal/PrivateElementProcessor.java create mode 100644 src/main/java/com/google/inject/internal/PrivateElementsImpl.java create mode 100644 src/main/java/com/google/inject/internal/ProcessedBindingData.java create mode 100644 src/main/java/com/google/inject/internal/ProvidedByInternalFactory.java create mode 100644 src/main/java/com/google/inject/internal/ProviderInstanceBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/ProviderInternalFactory.java create mode 100644 src/main/java/com/google/inject/internal/ProviderMethod.java create mode 100644 src/main/java/com/google/inject/internal/ProviderMethodsModule.java create mode 100644 src/main/java/com/google/inject/internal/ProviderToInternalFactoryAdapter.java create mode 100644 src/main/java/com/google/inject/internal/ProvisionListenerCallbackStore.java create mode 100644 src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java create mode 100644 src/main/java/com/google/inject/internal/ScopeBindingProcessor.java create mode 100644 src/main/java/com/google/inject/internal/Scoping.java create mode 100644 src/main/java/com/google/inject/internal/SingleFieldInjector.java create mode 100644 src/main/java/com/google/inject/internal/SingleMemberInjector.java create mode 100644 src/main/java/com/google/inject/internal/SingleMethodInjector.java create mode 100644 src/main/java/com/google/inject/internal/SingleParameterInjector.java create mode 100644 src/main/java/com/google/inject/internal/SingletonScope.java create mode 100644 src/main/java/com/google/inject/internal/State.java create mode 100644 src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java create mode 100644 src/main/java/com/google/inject/internal/UniqueAnnotations.java create mode 100644 src/main/java/com/google/inject/internal/UntargettedBindingImpl.java create mode 100644 src/main/java/com/google/inject/internal/UntargettedBindingProcessor.java create mode 100644 src/main/java/com/google/inject/internal/WeakKeySet.java create mode 100644 src/main/java/com/google/inject/internal/util/Classes.java create mode 100644 src/main/java/com/google/inject/internal/util/SourceProvider.java create mode 100644 src/main/java/com/google/inject/internal/util/StackTraceElements.java create mode 100644 src/main/java/com/google/inject/internal/util/Stopwatch.java create mode 100644 src/main/java/com/google/inject/matcher/AbstractMatcher.java create mode 100644 src/main/java/com/google/inject/matcher/Matcher.java create mode 100644 src/main/java/com/google/inject/matcher/Matchers.java create mode 100644 src/main/java/com/google/inject/name/Named.java create mode 100644 src/main/java/com/google/inject/name/NamedImpl.java create mode 100644 src/main/java/com/google/inject/name/Names.java create mode 100644 src/main/java/com/google/inject/spi/BindingScopingVisitor.java create mode 100644 src/main/java/com/google/inject/spi/BindingTargetVisitor.java create mode 100644 src/main/java/com/google/inject/spi/ConstructorBinding.java create mode 100644 src/main/java/com/google/inject/spi/ConvertedConstantBinding.java create mode 100644 src/main/java/com/google/inject/spi/DefaultBindingScopingVisitor.java create mode 100644 src/main/java/com/google/inject/spi/DefaultBindingTargetVisitor.java create mode 100644 src/main/java/com/google/inject/spi/DefaultElementVisitor.java create mode 100644 src/main/java/com/google/inject/spi/Dependency.java create mode 100644 src/main/java/com/google/inject/spi/DependencyAndSource.java create mode 100644 src/main/java/com/google/inject/spi/DisableCircularProxiesOption.java create mode 100644 src/main/java/com/google/inject/spi/Element.java create mode 100644 src/main/java/com/google/inject/spi/ElementSource.java create mode 100644 src/main/java/com/google/inject/spi/ElementVisitor.java create mode 100644 src/main/java/com/google/inject/spi/Elements.java create mode 100644 src/main/java/com/google/inject/spi/ExposedBinding.java create mode 100644 src/main/java/com/google/inject/spi/HasDependencies.java create mode 100644 src/main/java/com/google/inject/spi/InjectionListener.java create mode 100644 src/main/java/com/google/inject/spi/InjectionPoint.java create mode 100644 src/main/java/com/google/inject/spi/InjectionRequest.java create mode 100644 src/main/java/com/google/inject/spi/InstanceBinding.java create mode 100644 src/main/java/com/google/inject/spi/LinkedKeyBinding.java create mode 100644 src/main/java/com/google/inject/spi/MembersInjectorLookup.java create mode 100644 src/main/java/com/google/inject/spi/Message.java create mode 100644 src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScanner.java create mode 100644 src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java create mode 100644 src/main/java/com/google/inject/spi/ModuleSource.java create mode 100644 src/main/java/com/google/inject/spi/PrivateElements.java create mode 100644 src/main/java/com/google/inject/spi/ProviderBinding.java create mode 100644 src/main/java/com/google/inject/spi/ProviderInstanceBinding.java create mode 100644 src/main/java/com/google/inject/spi/ProviderKeyBinding.java create mode 100644 src/main/java/com/google/inject/spi/ProviderLookup.java create mode 100644 src/main/java/com/google/inject/spi/ProviderWithDependencies.java create mode 100644 src/main/java/com/google/inject/spi/ProviderWithExtensionVisitor.java create mode 100644 src/main/java/com/google/inject/spi/ProvidesMethodBinding.java create mode 100644 src/main/java/com/google/inject/spi/ProvidesMethodTargetVisitor.java create mode 100644 src/main/java/com/google/inject/spi/ProvisionListener.java create mode 100644 src/main/java/com/google/inject/spi/ProvisionListenerBinding.java create mode 100644 src/main/java/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java create mode 100644 src/main/java/com/google/inject/spi/RequireExactBindingAnnotationsOption.java create mode 100644 src/main/java/com/google/inject/spi/RequireExplicitBindingsOption.java create mode 100644 src/main/java/com/google/inject/spi/ScopeBinding.java create mode 100644 src/main/java/com/google/inject/spi/StaticInjectionRequest.java create mode 100644 src/main/java/com/google/inject/spi/Toolable.java create mode 100644 src/main/java/com/google/inject/spi/TypeConverter.java create mode 100644 src/main/java/com/google/inject/spi/TypeConverterBinding.java create mode 100644 src/main/java/com/google/inject/spi/TypeEncounter.java create mode 100644 src/main/java/com/google/inject/spi/TypeListener.java create mode 100644 src/main/java/com/google/inject/spi/TypeListenerBinding.java create mode 100644 src/main/java/com/google/inject/spi/UntargettedBinding.java create mode 100644 src/main/java/com/google/inject/util/Modules.java create mode 100644 src/main/java/com/google/inject/util/Providers.java create mode 100644 src/main/java/com/google/inject/util/Types.java create mode 100644 src/test/java/com/google/inject/Asserts.java create mode 100644 src/test/java/com/google/inject/BinderTest.java create mode 100644 src/test/java/com/google/inject/BinderTestSuite.java create mode 100644 src/test/java/com/google/inject/BindingAnnotationTest.java create mode 100644 src/test/java/com/google/inject/BindingOrderTest.java create mode 100644 src/test/java/com/google/inject/BindingTest.java create mode 100644 src/test/java/com/google/inject/BoundInstanceInjectionTest.java create mode 100644 src/test/java/com/google/inject/BoundProviderTest.java create mode 100644 src/test/java/com/google/inject/CircularDependencyTest.java create mode 100644 src/test/java/com/google/inject/DuplicateBindingsTest.java create mode 100644 src/test/java/com/google/inject/EagerSingletonTest.java create mode 100644 src/test/java/com/google/inject/ErrorHandlingTest.java create mode 100644 src/test/java/com/google/inject/GenericInjectionTest.java create mode 100644 src/test/java/com/google/inject/ImplicitBindingTest.java create mode 100644 src/test/java/com/google/inject/InjectorTest.java create mode 100644 src/test/java/com/google/inject/Java8LanguageFeatureBindingTest.java create mode 100644 src/test/java/com/google/inject/JitBindingsTest.java create mode 100644 src/test/java/com/google/inject/KeyTest.java create mode 100644 src/test/java/com/google/inject/MembersInjectorTest.java create mode 100644 src/test/java/com/google/inject/ModuleTest.java create mode 100644 src/test/java/com/google/inject/ModulesTest.java create mode 100644 src/test/java/com/google/inject/NullableInjectionPointTest.java create mode 100644 src/test/java/com/google/inject/OptionalBindingTest.java create mode 100644 src/test/java/com/google/inject/ParentInjectorTest.java create mode 100644 src/test/java/com/google/inject/PrivateModuleTest.java create mode 100644 src/test/java/com/google/inject/ProviderInjectionTest.java create mode 100644 src/test/java/com/google/inject/ProvisionExceptionTest.java create mode 100644 src/test/java/com/google/inject/ProvisionExceptionsTest.java create mode 100644 src/test/java/com/google/inject/ProvisionListenerTest.java create mode 100644 src/test/java/com/google/inject/ReflectionTest.java create mode 100644 src/test/java/com/google/inject/RequestInjectionTest.java create mode 100644 src/test/java/com/google/inject/RequireAtInjectOnConstructorsTest.java create mode 100644 src/test/java/com/google/inject/ScopesTest.java create mode 100644 src/test/java/com/google/inject/SuiteUtils.java create mode 100644 src/test/java/com/google/inject/SuperclassTest.java create mode 100644 src/test/java/com/google/inject/TypeConversionTest.java create mode 100644 src/test/java/com/google/inject/TypeListenerTest.java create mode 100644 src/test/java/com/google/inject/TypeLiteralInjectionTest.java create mode 100644 src/test/java/com/google/inject/TypeLiteralTest.java create mode 100644 src/test/java/com/google/inject/TypeLiteralTypeResolutionTest.java create mode 100644 src/test/java/com/google/inject/example/ClientServiceWithDependencyInjection.java create mode 100644 src/test/java/com/google/inject/example/ClientServiceWithFactories.java create mode 100644 src/test/java/com/google/inject/example/ClientServiceWithGuice.java create mode 100644 src/test/java/com/google/inject/example/ClientServiceWithGuiceDefaults.java create mode 100644 src/test/java/com/google/inject/example/JndiProvider.java create mode 100644 src/test/java/com/google/inject/example/JndiProviderClient.java create mode 100644 src/test/java/com/google/inject/internal/CycleDetectingLockTest.java create mode 100644 src/test/java/com/google/inject/internal/MoreTypesTest.java create mode 100644 src/test/java/com/google/inject/internal/UniqueAnnotationsTest.java create mode 100644 src/test/java/com/google/inject/internal/WeakKeySetTest.java create mode 100644 src/test/java/com/google/inject/internal/WeakKeySetUtils.java create mode 100644 src/test/java/com/google/inject/matcher/MatcherTest.java create mode 100644 src/test/java/com/google/inject/name/NamedEquivalanceTest.java create mode 100644 src/test/java/com/google/inject/name/NamesTest.java create mode 100644 src/test/java/com/google/inject/spi/BindingTargetVisitorTest.java create mode 100644 src/test/java/com/google/inject/spi/ElementApplyToTest.java create mode 100644 src/test/java/com/google/inject/spi/ElementSourceTest.java create mode 100644 src/test/java/com/google/inject/spi/ElementsTest.java create mode 100644 src/test/java/com/google/inject/spi/FailingBindingScopingVisitor.java create mode 100644 src/test/java/com/google/inject/spi/FailingElementVisitor.java create mode 100644 src/test/java/com/google/inject/spi/FailingTargetVisitor.java create mode 100644 src/test/java/com/google/inject/spi/HasDependenciesTest.java create mode 100644 src/test/java/com/google/inject/spi/InjectionPointTest.java create mode 100644 src/test/java/com/google/inject/spi/InjectorSpiTest.java create mode 100644 src/test/java/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java create mode 100644 src/test/java/com/google/inject/spi/ModuleRewriterTest.java create mode 100644 src/test/java/com/google/inject/spi/ModuleSourceTest.java create mode 100644 src/test/java/com/google/inject/spi/ProviderMethodsTest.java create mode 100644 src/test/java/com/google/inject/spi/SpiBindingsTest.java create mode 100644 src/test/java/com/google/inject/spi/ToolStageInjectorTest.java create mode 100644 src/test/java/com/google/inject/util/NoopOverrideTest.java create mode 100644 src/test/java/com/google/inject/util/OverrideModuleTest.java create mode 100644 src/test/java/com/google/inject/util/ProvidersTest.java create mode 100644 src/test/java/com/google/inject/util/TypesTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e42bcc --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +*.iml +/.settings +/.classpath +/.project +/.gradle +/build \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57000f7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +sudo: false +language: java +jdk: + - oraclejdk8 +cache: + directories: + - $HOME/.m2 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fada35 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ += Guice + +This is a xbib Guice, a build of Guice with the following differences to the +original [Google Guice Core Library](https://github.com/google/guice): + +- only external dependencies are +-- javax.inject:javax.inject:1 +-- javax.annotation:javax.annotation-api:1.2 +-- com.google.guava:guava:19.0 +- the extra AOP stuff was removed (aopalliance/cglib), no intercepting any more +- removed all logging, uses of java.util.logger.* stuff and logger default binding +- removed all java.io.Serializable dependent stuff +- reformatted source by IntelliJ and cleaned up source of @author and @since tags +- target of compilation is Java 8, no support for Java 6/7 +- introduced Gradle as build system +- junit tests made compatible to junit 4.12 + +All credits belong to the original authors, especially Bob Lee http://blog.crazybob.org/ + += License + +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. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..71d0020 --- /dev/null +++ b/build.gradle @@ -0,0 +1,90 @@ + +def xbibGroup = 'org.xbib' +def xbibVersion = '4.0' + +group = xbibGroup +version = xbibVersion + +println "Current JVM: " + org.gradle.internal.jvm.Jvm.current() + +apply plugin: 'java' +apply plugin: 'maven' + +repositories { + mavenCentral() + mavenLocal() + jcenter() + maven { + url "http://xbib.org/repository" + } +} + +configurations { + wagon +} + +dependencies { + compile "javax.inject:javax.inject:1" + compile 'javax.annotation:javax.annotation-api:1.2' + compile "com.google.guava:guava:19.0" + testCompile "junit:junit:4.12" + testCompile "org.apache.logging.log4j:log4j-slf4j-impl:2.5" + testCompile "org.apache.logging.log4j:log4j-core:2.5" + testCompile "javax.inject:javax.inject-tck:1" + testCompile "com.google.guava:guava-testlib:19.0" + wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +[compileJava, compileTestJava]*.options.collect { options -> + options.encoding = 'UTF-8' + options.compilerArgs << '-Xlint:-serial,-path,-rawtypes,-unchecked' +} + +test { + exclude '*$*' + exclude '**/ErrorHandlingTest*' + exclude '**/OSGiContainerTest*' + exclude '**/ScopesTest*' + exclude '**/TypeConversionTest*' + testLogging { + showStandardStreams = false + exceptionFormat = 'full' + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + classifier 'sources' +} + +artifacts { + archives sourcesJar +} + +uploadArchives { + repositories { + if (project.hasProperty("xbibUsername")) { + mavenDeployer { + configuration = configurations.wagon + repository( + id: 'xbib.org', + url: uri('scpexe://xbib.org/repository'), + authentication: [userName: xbibUsername, privateKey: xbibPrivateKey] + ) + pom.project { + inceptionYear '2016' + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + } + } + } + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..722622c --- /dev/null +++ b/pom.xml @@ -0,0 +1,100 @@ + + + + 4.0.0 + + org.xbib + guice + 4.0 + + Guice + + + + javax.inject + javax.inject + 1 + + + javax.annotation + javax.annotation-api + 1.2 + + + com.google.guava + guava + 19.0 + + + javax.inject + javax.inject-tck + 1 + test + + + com.google.guava + guava-testlib + 19.0 + test + + + + + + + maven-compiler-plugin + 3.3 + + UTF-8 + 1.8 + 1.8 + true + true + true + true + -Xlint:all,-serial,-path,-rawtypes,-unchecked + + + + maven-surefire-plugin + 2.19.1 + + + **/*$* + **/ErrorHandlingTest* + **/OSGiContainerTest* + **/ScopesTest* + **/TypeConversionTest* + + + + + maven-jar-plugin + 2.6 + + + LICENSE + NOTICE + + + + + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + maven-javadoc-plugin + 2.10.3 + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..cbf896b --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'guice' diff --git a/src/main/java/com/google/inject/AbstractModule.java b/src/main/java/com/google/inject/AbstractModule.java new file mode 100644 index 0000000..e00957e --- /dev/null +++ b/src/main/java/com/google/inject/AbstractModule.java @@ -0,0 +1,220 @@ +package com.google.inject; + +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.AnnotatedConstantBindingBuilder; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.matcher.Matcher; +import com.google.inject.spi.Message; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.spi.TypeConverter; +import com.google.inject.spi.TypeListener; + +import java.lang.annotation.Annotation; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * A support class for {@link Module}s which reduces repetition and results in + * a more readable configuration. Simply extend this class, implement {@link + * #configure()}, and call the inherited methods which mirror those found in + * {@link Binder}. For example: + * + *
+ * public class MyModule extends AbstractModule {
+ *   protected void configure() {
+ *     bind(Service.class).to(ServiceImpl.class).in(Singleton.class);
+ *     bind(CreditCardPaymentService.class);
+ *     bind(PaymentService.class).to(CreditCardPaymentService.class);
+ *     bindConstant().annotatedWith(Names.named("port")).to(8080);
+ *   }
+ * }
+ * 
+ * + */ +public abstract class AbstractModule implements Module { + + Binder binder; + + public final synchronized void configure(Binder builder) { + checkState(this.binder == null, "Re-entry is not allowed."); + + this.binder = checkNotNull(builder, "builder"); + try { + configure(); + } finally { + this.binder = null; + } + } + + /** + * Configures a {@link Binder} via the exposed methods. + */ + protected abstract void configure(); + + /** + * Gets direct access to the underlying {@code Binder}. + */ + protected Binder binder() { + checkState(binder != null, "The binder can only be used inside configure()"); + return binder; + } + + /** + * @see Binder#bindScope(Class, Scope) + */ + protected void bindScope(Class scopeAnnotation, + Scope scope) { + binder().bindScope(scopeAnnotation, scope); + } + + /** + * @see Binder#bind(Key) + */ + protected LinkedBindingBuilder bind(Key key) { + return binder().bind(key); + } + + /** + * @see Binder#bind(TypeLiteral) + */ + protected AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) { + return binder().bind(typeLiteral); + } + + /** + * @see Binder#bind(Class) + */ + protected AnnotatedBindingBuilder bind(Class clazz) { + return binder().bind(clazz); + } + + /** + * @see Binder#bindConstant() + */ + protected AnnotatedConstantBindingBuilder bindConstant() { + return binder().bindConstant(); + } + + /** + * @see Binder#install(Module) + */ + protected void install(Module module) { + binder().install(module); + } + + /** + * @see Binder#addError(String, Object[]) + */ + protected void addError(String message, Object... arguments) { + binder().addError(message, arguments); + } + + /** + * @see Binder#addError(Throwable) + */ + protected void addError(Throwable t) { + binder().addError(t); + } + + /** + * @see Binder#addError(Message) + */ + protected void addError(Message message) { + binder().addError(message); + } + + /** + * @see Binder#requestInjection(Object) + */ + protected void requestInjection(Object instance) { + binder().requestInjection(instance); + } + + /** + * @see Binder#requestStaticInjection(Class[]) + */ + protected void requestStaticInjection(Class... types) { + binder().requestStaticInjection(types); + } + + /** + * Adds a dependency from this module to {@code key}. When the injector is + * created, Guice will report an error if {@code key} cannot be injected. + * Note that this requirement may be satisfied by implicit binding, such as + * a public no-arguments constructor. + */ + protected void requireBinding(Key key) { + binder().getProvider(key); + } + + /** + * Adds a dependency from this module to {@code type}. When the injector is + * created, Guice will report an error if {@code type} cannot be injected. + * Note that this requirement may be satisfied by implicit binding, such as + * a public no-arguments constructor. + */ + protected void requireBinding(Class type) { + binder().getProvider(type); + } + + /** + * @see Binder#getProvider(Key) + */ + protected Provider getProvider(Key key) { + return binder().getProvider(key); + } + + /** + * @see Binder#getProvider(Class) + */ + protected Provider getProvider(Class type) { + return binder().getProvider(type); + } + + /** + * @see Binder#convertToTypes + */ + protected void convertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + binder().convertToTypes(typeMatcher, converter); + } + + /** + * @see Binder#currentStage() + */ + protected Stage currentStage() { + return binder().currentStage(); + } + + /** + * @see Binder#getMembersInjector(Class) + */ + protected MembersInjector getMembersInjector(Class type) { + return binder().getMembersInjector(type); + } + + /** + * @see Binder#getMembersInjector(TypeLiteral) + */ + protected MembersInjector getMembersInjector(TypeLiteral type) { + return binder().getMembersInjector(type); + } + + /** + * @see Binder#bindListener(com.google.inject.matcher.Matcher, + * com.google.inject.spi.TypeListener) + */ + protected void bindListener(Matcher> typeMatcher, + TypeListener listener) { + binder().bindListener(typeMatcher, listener); + } + + /** + * @see Binder#bindListener(Matcher, ProvisionListener...) + */ + protected void bindListener(Matcher> bindingMatcher, + ProvisionListener... listener) { + binder().bindListener(bindingMatcher, listener); + } +} diff --git a/src/main/java/com/google/inject/Binder.java b/src/main/java/com/google/inject/Binder.java new file mode 100644 index 0000000..f4242d9 --- /dev/null +++ b/src/main/java/com/google/inject/Binder.java @@ -0,0 +1,451 @@ +package com.google.inject; + +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.AnnotatedConstantBindingBuilder; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.matcher.Matcher; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.Message; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.spi.TypeConverter; +import com.google.inject.spi.TypeListener; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Proxy; + +/** + * Collects configuration information (primarily bindings) which will be + * used to create an {@link Injector}. Guice provides this object to your + * application's {@link Module} implementors so they may each contribute + * their own bindings and other registrations. + * + *

The Guice Binding EDSL

+ * + * Guice uses an embedded domain-specific language, or EDSL, to help you + * create bindings simply and readably. This approach is great for overall + * usability, but it does come with a small cost: it is difficult to + * learn how to use the Binding EDSL by reading + * method-level javadocs. Instead, you should consult the series of + * examples below. To save space, these examples omit the opening + * {@code binder}, just as you will if your module extends + * {@link AbstractModule}. + * + *
+ *     bind(ServiceImpl.class);
+ * + * This statement does essentially nothing; it "binds the {@code ServiceImpl} + * class to itself" and does not change Guice's default behavior. You may still + * want to use this if you prefer your {@link Module} class to serve as an + * explicit manifest for the services it provides. Also, in rare cases, + * Guice may be unable to validate a binding at injector creation time unless it + * is given explicitly. + * + *
+ *     bind(Service.class).to(ServiceImpl.class);
+ * + * Specifies that a request for a {@code Service} instance with no binding + * annotations should be treated as if it were a request for a + * {@code ServiceImpl} instance. This overrides the function of any + * {@link ImplementedBy @ImplementedBy} or {@link ProvidedBy @ProvidedBy} + * annotations found on {@code Service}, since Guice will have already + * "moved on" to {@code ServiceImpl} before it reaches the point when it starts + * looking for these annotations. + * + *
+ *     bind(Service.class).toProvider(ServiceProvider.class);
+ * + * In this example, {@code ServiceProvider} must extend or implement + * {@code Provider}. This binding specifies that Guice should resolve + * an unannotated injection request for {@code Service} by first resolving an + * instance of {@code ServiceProvider} in the regular way, then calling + * {@link Provider#get get()} on the resulting Provider instance to obtain the + * {@code Service} instance. + * + *

The {@link Provider} you use here does not have to be a "factory"; that + * is, a provider which always creates each instance it provides. + * However, this is generally a good practice to follow. You can then use + * Guice's concept of {@link Scope scopes} to guide when creation should happen + * -- "letting Guice work for you". + * + *

+ *     bind(Service.class).annotatedWith(Red.class).to(ServiceImpl.class);
+ * + * Like the previous example, but only applies to injection requests that use + * the binding annotation {@code @Red}. If your module also includes bindings + * for particular values of the {@code @Red} annotation (see below), + * then this binding will serve as a "catch-all" for any values of {@code @Red} + * that have no exact match in the bindings. + * + *
+ *     bind(ServiceImpl.class).in(Singleton.class);
+ *     // or, alternatively
+ *     bind(ServiceImpl.class).in(Scopes.SINGLETON);
+ * + * Either of these statements places the {@code ServiceImpl} class into + * singleton scope. Guice will create only one instance of {@code ServiceImpl} + * and will reuse it for all injection requests of this type. Note that it is + * still possible to bind another instance of {@code ServiceImpl} if the second + * binding is qualified by an annotation as in the previous example. Guice is + * not overly concerned with preventing you from creating multiple + * instances of your "singletons", only with enabling your application to + * share only one instance if that's all you tell Guice you need. + * + *

Note: a scope specified in this way overrides any scope that + * was specified with an annotation on the {@code ServiceImpl} class. + * + *

Besides {@link Singleton}/{@link Scopes#SINGLETON}, there are + * servlet-specific scopes available in + * {@code com.google.inject.servlet.ServletScopes}, and your Modules can + * contribute their own custom scopes for use here as well. + * + *

+ *     bind(new TypeLiteral<PaymentService<CreditCard>>() {})
+ *         .to(CreditCardPaymentService.class);
+ * + * This admittedly odd construct is the way to bind a parameterized type. It + * tells Guice how to honor an injection request for an element of type + * {@code PaymentService}. The class + * {@code CreditCardPaymentService} must implement the + * {@code PaymentService} interface. Guice cannot currently bind or + * inject a generic type, such as {@code Set}; all type parameters must be + * fully specified. + * + *
+ *     bind(Service.class).toInstance(new ServiceImpl());
+ *     // or, alternatively
+ *     bind(Service.class).toInstance(SomeLegacyRegistry.getService());
+ * + * In this example, your module itself, not Guice, takes responsibility + * for obtaining a {@code ServiceImpl} instance, then asks Guice to always use + * this single instance to fulfill all {@code Service} injection requests. When + * the {@link Injector} is created, it will automatically perform field + * and method injection for this instance, but any injectable constructor on + * {@code ServiceImpl} is simply ignored. Note that using this approach results + * in "eager loading" behavior that you can't control. + * + *
+ *     bindConstant().annotatedWith(ServerHost.class).to(args[0]);
+ * + * Sets up a constant binding. Constant injections must always be annotated. + * When a constant binding's value is a string, it is eligile for conversion to + * all primitive types, to {@link Enum#valueOf(Class, String) all enums}, and to + * {@link Class#forName class literals}. Conversions for other types can be + * configured using {@link #convertToTypes(Matcher, TypeConverter) + * convertToTypes()}. + * + *
+ *   {@literal @}Color("red") Color red; // A member variable (field)
+ *    . . .
+ *     red = MyModule.class.getDeclaredField("red").getAnnotation(Color.class);
+ *     bind(Service.class).annotatedWith(red).to(RedService.class);
+ * + * If your binding annotation has parameters you can apply different bindings to + * different specific values of your annotation. Getting your hands on the + * right instance of the annotation is a bit of a pain -- one approach, shown + * above, is to apply a prototype annotation to a field in your module class, so + * that you can read this annotation instance and give it to Guice. + * + *
+ *     bind(Service.class)
+ *         .annotatedWith(Names.named("blue"))
+ *         .to(BlueService.class);
+ * + * Differentiating by names is a common enough use case that we provided a + * standard annotation, {@link com.google.inject.name.Named @Named}. Because of + * Guice's library support, binding by name is quite easier than in the + * arbitrary binding annotation case we just saw. However, remember that these + * names will live in a single flat namespace with all the other names used in + * your application. + * + *
+ *     Constructor loneCtor = getLoneCtorFromServiceImplViaReflection();
+ *     bind(ServiceImpl.class)
+ *         .toConstructor(loneCtor);
+ * + * In this example, we directly tell Guice which constructor to use in a concrete + * class implementation. It means that we do not need to place {@literal @}Inject + * on any of the constructors and that Guice treats the provided constructor as though + * it were annotated so. It is useful for cases where you cannot modify existing + * classes and is a bit simpler than using a {@link Provider}. + * + *

The above list of examples is far from exhaustive. If you can think of + * how the concepts of one example might coexist with the concepts from another, + * you can most likely weave the two together. If the two concepts make no + * sense with each other, you most likely won't be able to do it. In a few + * cases Guice will let something bogus slip by, and will then inform you of + * the problems at runtime, as soon as you try to create your Injector. + * + *

The other methods of Binder such as {@link #bindScope}, + * {@link #install}, {@link #requestStaticInjection}, + * {@link #addError} and {@link #currentStage} are not part of the Binding EDSL; + * you can learn how to use these in the usual way, from the method + * documentation. + */ +public interface Binder { + + /** + * Binds a scope to an annotation. + */ + void bindScope(Class annotationType, Scope scope); + + /** + * See the EDSL examples at {@link Binder}. + */ + LinkedBindingBuilder bind(Key key); + + /** + * See the EDSL examples at {@link Binder}. + */ + AnnotatedBindingBuilder bind(TypeLiteral typeLiteral); + + /** + * See the EDSL examples at {@link Binder}. + */ + AnnotatedBindingBuilder bind(Class type); + + /** + * See the EDSL examples at {@link Binder}. + */ + AnnotatedConstantBindingBuilder bindConstant(); + + /** + * Upon successful creation, the {@link Injector} will inject instance fields + * and methods of the given object. + * + * @param type of instance + * @param instance for which members will be injected + */ + void requestInjection(TypeLiteral type, T instance); + + /** + * Upon successful creation, the {@link Injector} will inject instance fields + * and methods of the given object. + * + * @param instance for which members will be injected + */ + void requestInjection(Object instance); + + /** + * Upon successful creation, the {@link Injector} will inject static fields + * and methods in the given classes. + * + * @param types for which static members will be injected + */ + void requestStaticInjection(Class... types); + + /** + * Uses the given module to configure more bindings. + */ + void install(Module module); + + /** + * Gets the current stage. + */ + Stage currentStage(); + + /** + * Records an error message which will be presented to the user at a later + * time. Unlike throwing an exception, this enable us to continue + * configuring the Injector and discover more errors. Uses {@link + * String#format(String, Object[])} to insert the arguments into the + * message. + */ + void addError(String message, Object... arguments); + + /** + * Records an exception, the full details of which will be logged, and the + * message of which will be presented to the user at a later + * time. If your Module calls something that you worry may fail, you should + * catch the exception and pass it into this. + */ + void addError(Throwable t); + + /** + * Records an error message to be presented to the user at a later time. + */ + void addError(Message message); + + /** + * Returns the provider used to obtain instances for the given injection key. + * The returned provider will not be valid until the {@link Injector} has been + * created. The provider will throw an {@code IllegalStateException} if you + * try to use it beforehand. + * + */ + Provider getProvider(Key key); + + /** + * Returns the provider used to obtain instances for the given injection key. + * The returned provider will be attached to the injection point and will + * follow the nullability specified in the dependency. + * Additionally, the returned provider will not be valid until the {@link Injector} + * has been created. The provider will throw an {@code IllegalStateException} if you + * try to use it beforehand. + */ + Provider getProvider(Dependency dependency); + + /** + * Returns the provider used to obtain instances for the given injection type. + * The returned provider will not be valid until the {@link Injector} has been + * created. The provider will throw an {@code IllegalStateException} if you + * try to use it beforehand. + */ + Provider getProvider(Class type); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * {@link Injector} has been created. The members injector will throw an {@code + * IllegalStateException} if you try to use it beforehand. + * + * @param typeLiteral type to get members injector for + */ + MembersInjector getMembersInjector(TypeLiteral typeLiteral); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * {@link Injector} has been created. The members injector will throw an {@code + * IllegalStateException} if you try to use it beforehand. + * + * @param type type to get members injector for + */ + MembersInjector getMembersInjector(Class type); + + /** + * Binds a type converter. The injector will use the given converter to + * convert string constants to matching types as needed. + * + * @param typeMatcher matches types the converter can handle + * @param converter converts values + */ + void convertToTypes(Matcher> typeMatcher, + TypeConverter converter); + + /** + * Registers a listener for injectable types. Guice will notify the listener when it encounters + * injectable types matched by the given type matcher. + * + * @param typeMatcher that matches injectable types the listener should be notified of + * @param listener for injectable types matched by typeMatcher + */ + void bindListener(Matcher> typeMatcher, + TypeListener listener); + + /** + * Registers listeners for provisioned objects. Guice will notify the + * listeners just before and after the object is provisioned. Provisioned + * objects that are also injectable (everything except objects provided + * through Providers) can also be notified through TypeListeners registered in + * {@link #bindListener}. + * + * @param bindingMatcher that matches bindings of provisioned objects the listener + * should be notified of + * @param listeners for provisioned objects matched by bindingMatcher + */ + void bindListener(Matcher> bindingMatcher, ProvisionListener... listeners); + + /** + * Returns a binder that uses {@code source} as the reference location for + * configuration errors. This is typically a {@link StackTraceElement} + * for {@code .java} source but it could any binding source, such as the + * path to a {@code .properties} file. + * + * @param source any object representing the source location and has a + * concise {@link Object#toString() toString()} value + * @return a binder that shares its configuration with this binder + */ + Binder withSource(Object source); + + /** + * Returns a binder that skips {@code classesToSkip} when identify the + * calling code. The caller's {@link StackTraceElement} is used to locate + * the source of configuration errors. + * + * @param classesToSkip library classes that create bindings on behalf of + * their clients. + * @return a binder that shares its configuration with this binder. + */ + Binder skipSources(Class... classesToSkip); + + /** + * Creates a new private child environment for bindings and other configuration. The returned + * binder can be used to add and configuration information in this environment. See {@link + * PrivateModule} for details. + * + * @return a binder that inherits configuration from this binder. Only exposed configuration on + * the returned binder will be visible to this binder. + */ + PrivateBinder newPrivateBinder(); + + /** + * Instructs the Injector that bindings must be listed in a Module in order to + * be injected. Classes that are not explicitly bound in a module cannot be + * injected. Bindings created through a linked binding + * (bind(Foo.class).to(FooImpl.class)) are allowed, but the + * implicit binding (FooImpl) cannot be directly injected unless + * it is also explicitly bound (bind(FooImpl.class)). + *

+ * Tools can still retrieve bindings for implicit bindings (bindings created + * through a linked binding) if explicit bindings are required, however + * {@link Binding#getProvider} will fail. + *

+ * By default, explicit bindings are not required. + *

+ * If a parent injector requires explicit bindings, then all child injectors + * (and private modules within that injector) also require explicit bindings. + * If a parent does not require explicit bindings, a child injector or private + * module may optionally declare itself as requiring explicit bindings. If it + * does, the behavior is limited only to that child or any grandchildren. No + * siblings of the child will require explicit bindings. + *

+ * In the absence of an explicit binding for the target, linked bindings in + * child injectors create a binding for the target in the parent. Since this + * behavior can be surprising, it causes an error instead if explicit bindings + * are required. To avoid this error, add an explicit binding for the target, + * either in the child or the parent. + */ + void requireExplicitBindings(); + + /** + * Prevents Guice from constructing a {@link Proxy} when a circular dependency + * is found. By default, circular proxies are not disabled. + *

+ * If a parent injector disables circular proxies, then all child injectors + * (and private modules within that injector) also disable circular proxies. + * If a parent does not disable circular proxies, a child injector or private + * module may optionally declare itself as disabling circular proxies. If it + * does, the behavior is limited only to that child or any grandchildren. No + * siblings of the child will disable circular proxies. + */ + void disableCircularProxies(); + + /** + * Requires that a {@literal @}{@link Inject} annotation exists on a constructor in order for + * Guice to consider it an eligible injectable class. By default, Guice will inject classes that + * have a no-args constructor if no {@literal @}{@link Inject} annotation exists on any + * constructor. + *

+ * If the class is bound using {@link LinkedBindingBuilder#toConstructor}, Guice will still inject + * that constructor regardless of annotations. + */ + void requireAtInjectOnConstructors(); + + /** + * Requires that Guice finds an exactly matching binding annotation. This disables the + * error-prone feature in Guice where it can substitute a binding for + * {@literal @}Named Foo when attempting to inject + * {@literal @}Named("foo") Foo. + */ + void requireExactBindingAnnotations(); + + /** + * Adds a scanner that will look in all installed modules for annotations the scanner can parse, + * and binds them like {@literal @}Provides methods. Scanners apply to all modules installed in + * the injector. Scanners installed in child injectors or private modules do not impact modules in + * siblings or parents, however scanners installed in parents do apply to all child injectors and + * private modules. + */ + void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner); +} diff --git a/src/main/java/com/google/inject/Binding.java b/src/main/java/com/google/inject/Binding.java new file mode 100644 index 0000000..8b5fd29 --- /dev/null +++ b/src/main/java/com/google/inject/Binding.java @@ -0,0 +1,71 @@ +package com.google.inject; + +import com.google.inject.spi.BindingScopingVisitor; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Element; + +/** + * A mapping from a key (type and optional annotation) to the strategy for getting instances of the + * type. This interface is part of the introspection API and is intended primarily for use by + * tools. + * + *

Bindings are created in several ways: + *

    + *
  • Explicitly in a module, via {@code bind()} and {@code bindConstant()} + * statements: + *
    + *     bind(Service.class).annotatedWith(Red.class).to(ServiceImpl.class);
    + *     bindConstant().annotatedWith(ServerHost.class).to(args[0]);
  • + *
  • Implicitly by the Injector by following a type's {@link ImplementedBy + * pointer} {@link ProvidedBy annotations} or by using its {@link Inject annotated} or + * default constructor.
  • + *
  • By converting a bound instance to a different type.
  • + *
  • For {@link Provider providers}, by delegating to the binding for the provided type.
  • + *
+ * + * + *

They exist on both modules and on injectors, and their behaviour is different for each: + *

    + *
  • Module bindings are incomplete and cannot be used to provide instances. + * This is because the applicable scopes and interceptors may not be known until an injector + * is created. From a tool's perspective, module bindings are like the injector's source + * code. They can be inspected or rewritten, but this analysis must be done statically.
  • + *
  • Injector bindings are complete and valid and can be used to provide + * instances. From a tools' perspective, injector bindings are like reflection for an + * injector. They have full runtime information, including the complete graph of injections + * necessary to satisfy a binding.
  • + *
+ * + * @param the bound type. The injected is always assignable to this type. + */ +public interface Binding extends Element { + + /** + * Returns the key for this binding. + */ + Key getKey(); + + /** + * Returns the scoped provider guice uses to fulfill requests for this + * binding. + * + * @throws UnsupportedOperationException when invoked on a {@link Binding} + * created via {@link com.google.inject.spi.Elements#getElements}. This + * method is only supported on {@link Binding}s returned from an injector. + */ + Provider getProvider(); + + /** + * Accepts a target visitor. Invokes the visitor method specific to this binding's target. + * + * @param visitor to call back on + */ + V acceptTargetVisitor(BindingTargetVisitor visitor); + + /** + * Accepts a scoping visitor. Invokes the visitor method specific to this binding's scoping. + * + * @param visitor to call back on + */ + V acceptScopingVisitor(BindingScopingVisitor visitor); +} diff --git a/src/main/java/com/google/inject/BindingAnnotation.java b/src/main/java/com/google/inject/BindingAnnotation.java new file mode 100644 index 0000000..8160d96 --- /dev/null +++ b/src/main/java/com/google/inject/BindingAnnotation.java @@ -0,0 +1,25 @@ +package com.google.inject; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotates annotations which are used for binding. Only one such annotation + * may apply to a single injection point. You must also annotate binder + * annotations with {@code @Retention(RUNTIME)}. For example: + * + *
+ *   {@code @}Retention(RUNTIME)
+ *   {@code @}Target({ FIELD, PARAMETER, METHOD })
+ *   {@code @}BindingAnnotation
+ *   public {@code @}interface Transactional {}
+ * 
+ * + */ +@Target(ANNOTATION_TYPE) +@Retention(RUNTIME) +public @interface BindingAnnotation { +} diff --git a/src/main/java/com/google/inject/ConfigurationException.java b/src/main/java/com/google/inject/ConfigurationException.java new file mode 100644 index 0000000..c5ce072 --- /dev/null +++ b/src/main/java/com/google/inject/ConfigurationException.java @@ -0,0 +1,64 @@ +package com.google.inject; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.internal.Errors; +import com.google.inject.spi.Message; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkState; + +/** + * Thrown when a programming error such as a misplaced annotation, illegal binding, or unsupported + * scope is found. Clients should catch this exception, log it, and stop execution. + * + */ +@SuppressWarnings("serial") +public final class ConfigurationException extends RuntimeException { + + private final ImmutableSet messages; + private Object partialValue = null; + + /** + * Creates a ConfigurationException containing {@code messages}. + */ + public ConfigurationException(Iterable messages) { + this.messages = ImmutableSet.copyOf(messages); + initCause(Errors.getOnlyCause(this.messages)); + } + + /** + * Returns a copy of this configuration exception with the specified partial value. + */ + public ConfigurationException withPartialValue(Object partialValue) { + checkState(this.partialValue == null, + "Can't clobber existing partial value %s with %s", this.partialValue, partialValue); + ConfigurationException result = new ConfigurationException(messages); + result.partialValue = partialValue; + return result; + } + + /** + * Returns messages for the errors that caused this exception. + */ + public Collection getErrorMessages() { + return messages; + } + + /** + * Returns a value that was only partially computed due to this exception. The caller can use + * this while collecting additional configuration problems. + * + * @return the partial value, or {@code null} if none was set. The type of the partial value is + * specified by the throwing method. + */ + @SuppressWarnings("unchecked") // this is *extremely* unsafe. We trust the caller here. + public E getPartialValue() { + return (E) partialValue; + } + + @Override + public String getMessage() { + return Errors.format("Guice configuration errors", messages); + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/CreationException.java b/src/main/java/com/google/inject/CreationException.java new file mode 100644 index 0000000..d764818 --- /dev/null +++ b/src/main/java/com/google/inject/CreationException.java @@ -0,0 +1,40 @@ +package com.google.inject; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.internal.Errors; +import com.google.inject.spi.Message; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Thrown when errors occur while creating a {@link Injector}. Includes a list of encountered + * errors. Clients should catch this exception, log it, and stop execution. + */ +public class CreationException extends RuntimeException { + + private static final long serialVersionUID = 0; + private final ImmutableSet messages; + + /** + * Creates a CreationException containing {@code messages}. + */ + public CreationException(Collection messages) { + this.messages = ImmutableSet.copyOf(messages); + checkArgument(!this.messages.isEmpty()); + initCause(Errors.getOnlyCause(this.messages)); + } + + /** + * Returns messages for the errors that caused this exception. + */ + public Collection getErrorMessages() { + return messages; + } + + @Override + public String getMessage() { + return Errors.format("Unable to create injector, see the following errors", messages); + } +} diff --git a/src/main/java/com/google/inject/Exposed.java b/src/main/java/com/google/inject/Exposed.java new file mode 100644 index 0000000..5292e24 --- /dev/null +++ b/src/main/java/com/google/inject/Exposed.java @@ -0,0 +1,19 @@ +package com.google.inject; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Acccompanies a {@literal @}{@link com.google.inject.Provides Provides} method annotation in a + * private module to indicate that the provided binding is exposed. + * + */ +@Target(ElementType.METHOD) +@Retention(RUNTIME) +@Documented +public @interface Exposed { +} diff --git a/src/main/java/com/google/inject/Guice.java b/src/main/java/com/google/inject/Guice.java new file mode 100644 index 0000000..c8c0259 --- /dev/null +++ b/src/main/java/com/google/inject/Guice.java @@ -0,0 +1,86 @@ +package com.google.inject; + +import com.google.inject.internal.InternalInjectorCreator; + +import java.util.Arrays; + +/** + * The entry point to the Guice framework. Creates {@link Injector}s from + * {@link Module}s. + * + *

Guice supports a model of development that draws clear boundaries between + * APIs, Implementations of these APIs, Modules which configure these + * implementations, and finally Applications which consist of a collection of + * Modules. It is the Application, which typically defines your {@code main()} + * method, that bootstraps the Guice Injector using the {@code Guice} class, as + * in this example: + *

+ *     public class FooApplication {
+ *       public static void main(String[] args) {
+ *         Injector injector = Guice.createInjector(
+ *             new ModuleA(),
+ *             new ModuleB(),
+ *             . . .
+ *             new FooApplicationFlagsModule(args)
+ *         );
+ *
+ *         // Now just bootstrap the application and you're done
+ *         FooStarter starter = injector.getInstance(FooStarter.class);
+ *         starter.runApplication();
+ *       }
+ *     }
+ * 
+ */ +public final class Guice { + + private Guice() { + } + + /** + * Creates an injector for the given set of modules. This is equivalent to + * calling {@link #createInjector(Stage, Module...)} with Stage.DEVELOPMENT. + * + * @throws CreationException if one or more errors occur during injector + * construction + */ + public static Injector createInjector(Module... modules) { + return createInjector(Arrays.asList(modules)); + } + + /** + * Creates an injector for the given set of modules. This is equivalent to + * calling {@link #createInjector(Stage, Iterable)} with Stage.DEVELOPMENT. + * + * @throws CreationException if one or more errors occur during injector + * creation + */ + public static Injector createInjector(Iterable modules) { + return createInjector(Stage.DEVELOPMENT, modules); + } + + /** + * Creates an injector for the given set of modules, in a given development + * stage. + * + * @throws CreationException if one or more errors occur during injector + * creation. + */ + public static Injector createInjector(Stage stage, Module... modules) { + return createInjector(stage, Arrays.asList(modules)); + } + + /** + * Creates an injector for the given set of modules, in a given development + * stage. + * + * @throws CreationException if one or more errors occur during injector + * construction + */ + public static Injector createInjector(Stage stage, + Iterable modules) { + return new InternalInjectorCreator() + .stage(stage) + .addModules(modules) + .build(); + } +} diff --git a/src/main/java/com/google/inject/ImplementedBy.java b/src/main/java/com/google/inject/ImplementedBy.java new file mode 100644 index 0000000..be636f4 --- /dev/null +++ b/src/main/java/com/google/inject/ImplementedBy.java @@ -0,0 +1,20 @@ +package com.google.inject; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A pointer to the default implementation of a type. + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface ImplementedBy { + + /** + * The implementation type. + */ + Class value(); +} diff --git a/src/main/java/com/google/inject/Inject.java b/src/main/java/com/google/inject/Inject.java new file mode 100644 index 0000000..ff71d06 --- /dev/null +++ b/src/main/java/com/google/inject/Inject.java @@ -0,0 +1,51 @@ +package com.google.inject; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotates members of your implementation class (constructors, methods + * and fields) into which the {@link Injector} should inject values. + * The Injector fulfills injection requests for: + * + *
    + *
  • Every instance it constructs. The class being constructed must have + * exactly one of its constructors marked with {@code @Inject} or must have a + * constructor taking no parameters. The Injector then proceeds to perform + * field and method injections. + * + *
  • Pre-constructed instances passed to {@link Injector#injectMembers}, + * {@link com.google.inject.binder.LinkedBindingBuilder#toInstance(Object)} and + * {@link com.google.inject.binder.LinkedBindingBuilder#toProvider(javax.inject.Provider)}. + * In this case all constructors are, of course, ignored. + * + *
  • Static fields and methods of classes which any {@link Module} has + * specifically requested static injection for, using + * {@link Binder#requestStaticInjection}. + *
+ * + * In all cases, a member can be injected regardless of its Java access + * specifier (private, default, protected, public). + */ +@Target({METHOD, CONSTRUCTOR, FIELD}) +@Retention(RUNTIME) +@Documented +public @interface Inject { + + /** + * If true, and the appropriate binding is not found, + * the Injector will skip injection of this method or field rather than + * produce an error. When applied to a field, any default value already + * assigned to the field will remain (guice will not actively null out the + * field). When applied to a method, the method will only be invoked if + * bindings for all parameters are found. When applied to a + * constructor, an error will result upon Injector creation. + */ + boolean optional() default false; +} diff --git a/src/main/java/com/google/inject/Injector.java b/src/main/java/com/google/inject/Injector.java new file mode 100644 index 0000000..b9df905 --- /dev/null +++ b/src/main/java/com/google/inject/Injector.java @@ -0,0 +1,228 @@ +package com.google.inject; + +import com.google.inject.spi.TypeConverterBinding; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Builds the graphs of objects that make up your application. The injector tracks the dependencies + * for each type and uses bindings to inject them. This is the core of Guice, although you rarely + * interact with it directly. This "behind-the-scenes" operation is what distinguishes dependency + * injection from its cousin, the service locator pattern. + * + *

Contains several default bindings: + * + *

    + *
  • This {@link Injector} instance itself + *
  • A {@code Provider} for each binding of type {@code T} + *
  • The {@link java.util.logging.Logger} for the class being injected + *
  • The {@link Stage} in which the Injector was created + *
+ * + * Injectors are created using the facade class {@link Guice}. + * + *

An injector can also {@link #injectMembers(Object) inject the dependencies} of + * already-constructed instances. This can be used to interoperate with objects created by other + * frameworks or services. + * + *

Injectors can be {@link #createChildInjector(Iterable) hierarchical}. Child injectors inherit + * the configuration of their parent injectors, but the converse does not hold. + * + *

The injector's {@link #getBindings() internal bindings} are available for introspection. This + * enables tools and extensions to operate on an injector reflectively. + */ +public interface Injector { + + /** + * Injects dependencies into the fields and methods of {@code instance}. Ignores the presence or + * absence of an injectable constructor. + * + *

Whenever Guice creates an instance, it performs this injection automatically (after first + * performing constructor injection), so if you're able to let Guice create all your objects for + * you, you'll never need to use this method. + * + * @param instance to inject members on + * @see Binder#getMembersInjector(Class) for a preferred alternative that supports checks before + * run time + */ + void injectMembers(Object instance); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. + * + * @param typeLiteral type to get members injector for + * @see Binder#getMembersInjector(TypeLiteral) for an alternative that offers up front error + * detection + */ + MembersInjector getMembersInjector(TypeLiteral typeLiteral); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. When feasible, use {@link Binder#getMembersInjector(TypeLiteral)} + * instead to get increased up front error detection. + * + * @param type type to get members injector for + * @see Binder#getMembersInjector(Class) for an alternative that offers up front error + * detection + */ + MembersInjector getMembersInjector(Class type); + + /** + * Returns this injector's explicit bindings. + * + *

The returned map does not include bindings inherited from a {@link #getParent() parent + * injector}, should one exist. The returned map is guaranteed to iterate (for example, with + * its {@link Map#entrySet()} iterator) in the order of insertion. In other words, the order in + * which bindings appear in user Modules. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + Map, Binding> getBindings(); + + /** + * Returns a snapshot of this injector's bindings, both explicit and + * just-in-time. The returned map is immutable; it contains only the bindings that were + * present when {@code getAllBindings()} was invoked. Subsequent calls may return a map with + * additional just-in-time bindings. + * + *

The returned map does not include bindings inherited from a {@link #getParent() parent + * injector}, should one exist. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + Map, Binding> getAllBindings(); + + /** + * Returns the binding for the given injection key. This will be an explicit bindings if the key + * was bound explicitly by a module, or an implicit binding otherwise. The implicit binding will + * be created if necessary. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + * + * @throws ConfigurationException if this injector cannot find or create the binding. + */ + Binding getBinding(Key key); + + /** + * Returns the binding for the given type. This will be an explicit bindings if the injection key + * was bound explicitly by a module, or an implicit binding otherwise. The implicit binding will + * be created if necessary. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + * + * @throws ConfigurationException if this injector cannot find or create the binding. + */ + Binding getBinding(Class type); + + /** + * Returns the binding if it already exists, or null if does not exist. Unlike + * {@link #getBinding(Key)}, this does not attempt to create just-in-time bindings + * for keys that aren't bound. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + Binding getExistingBinding(Key key); + + /** + * Returns all explicit bindings for {@code type}. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + List> findBindingsByType(TypeLiteral type); + + /** + * Returns the provider used to obtain instances for the given injection key. When feasible, avoid + * using this method, in favor of having Guice inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @see Binder#getProvider(Key) for an alternative that offers up front error detection + */ + Provider getProvider(Key key); + + /** + * Returns the provider used to obtain instances for the given type. When feasible, avoid + * using this method, in favor of having Guice inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @see Binder#getProvider(Class) for an alternative that offers up front error detection + */ + Provider getProvider(Class type); + + /** + * Returns the appropriate instance for the given injection key; equivalent to {@code + * getProvider(key).get()}. When feasible, avoid using this method, in favor of having Guice + * inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @throws ProvisionException if there was a runtime failure while providing an instance. + */ + T getInstance(Key key); + + /** + * Returns the appropriate instance for the given injection type; equivalent to {@code + * getProvider(type).get()}. When feasible, avoid using this method, in favor of having Guice + * inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @throws ProvisionException if there was a runtime failure while providing an instance. + */ + T getInstance(Class type); + + /** + * Returns this injector's parent, or {@code null} if this is a top-level injector. + */ + Injector getParent(); + + /** + * Returns a new injector that inherits all state from this injector. All bindings, scopes, + * interceptors and type converters are inherited -- they are visible to the child injector. + * Elements of the child injector are not visible to its parent. + * + *

Just-in-time bindings created for child injectors will be created in an ancestor injector + * whenever possible. This allows for scoped instances to be shared between injectors. Use + * explicit bindings to prevent bindings from being shared with the parent injector. Optional + * injections in just-in-time bindings (created in the parent injector) may be silently + * ignored if the optional dependencies are from the child injector. + * + *

No key may be bound by both an injector and one of its ancestors. This includes just-in-time + * bindings. The lone exception is the key for {@code Injector.class}, which is bound by each + * injector to itself. + */ + Injector createChildInjector(Iterable modules); + + /** + * Returns a new injector that inherits all state from this injector. All bindings, scopes, + * interceptors and type converters are inherited -- they are visible to the child injector. + * Elements of the child injector are not visible to its parent. + * + *

Just-in-time bindings created for child injectors will be created in an ancestor injector + * whenever possible. This allows for scoped instances to be shared between injectors. Use + * explicit bindings to prevent bindings from being shared with the parent injector. + * + *

No key may be bound by both an injector and one of its ancestors. This includes just-in-time + * bindings. The lone exception is the key for {@code Injector.class}, which is bound by each + * injector to itself. + */ + Injector createChildInjector(Module... modules); + + /** + * Returns a map containing all scopes in the injector. The maps keys are scoping annotations + * like {@code Singleton.class}, and the values are scope instances, such as {@code + * Scopes.SINGLETON}. The returned map is immutable. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + Map, Scope> getScopeBindings(); + + /** + * Returns a set containing all type converter bindings in the injector. The returned set is + * immutable. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + Set getTypeConverterBindings(); +} diff --git a/src/main/java/com/google/inject/Key.java b/src/main/java/com/google/inject/Key.java new file mode 100644 index 0000000..6b34a11 --- /dev/null +++ b/src/main/java/com/google/inject/Key.java @@ -0,0 +1,508 @@ +package com.google.inject; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.inject.internal.Annotations; +import com.google.inject.internal.MoreTypes; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.inject.internal.Annotations.generateAnnotation; +import static com.google.inject.internal.Annotations.isAllDefaultMethods; + +/** + * Binding key consisting of an injection type and an optional annotation. + * Matches the type and annotation at a point of injection. + * + *

For example, {@code Key.get(Service.class, Transactional.class)} will + * match: + * + *

+ *   {@literal @}Inject
+ *   public void setService({@literal @}Transactional Service service) {
+ *     ...
+ *   }
+ * 
+ * + *

{@code Key} supports generic types via subclassing just like {@link + * TypeLiteral}. + * + *

Keys do not differentiate between primitive types (int, char, etc.) and + * their corresponding wrapper types (Integer, Character, etc.). Primitive + * types will be replaced with their wrapper types when keys are created. + */ +public class Key { + + private final AnnotationStrategy annotationStrategy; + + private final TypeLiteral typeLiteral; + private final int hashCode; + private final Supplier toStringSupplier; + + /** + * Constructs a new key. Derives the type from this class's type parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + * + *

Example usage for a binding of type {@code Foo} annotated with + * {@code @Bar}: + * + *

{@code new Key(Bar.class) {}}. + */ + @SuppressWarnings("unchecked") + protected Key(Class annotationType) { + this.annotationStrategy = strategyFor(annotationType); + this.typeLiteral = MoreTypes.canonicalizeForKey( + (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); + this.hashCode = computeHashCode(); + this.toStringSupplier = createToStringSupplier(); + } + + /** + * Constructs a new key. Derives the type from this class's type parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + * + *

Example usage for a binding of type {@code Foo} annotated with + * {@code @Bar}: + * + *

{@code new Key(new Bar()) {}}. + */ + @SuppressWarnings("unchecked") + protected Key(Annotation annotation) { + // no usages, not test-covered + this.annotationStrategy = strategyFor(annotation); + this.typeLiteral = MoreTypes.canonicalizeForKey( + (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); + this.hashCode = computeHashCode(); + this.toStringSupplier = createToStringSupplier(); + } + + /** + * Constructs a new key. Derives the type from this class's type parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + * + *

Example usage for a binding of type {@code Foo}: + * + *

{@code new Key() {}}. + */ + @SuppressWarnings("unchecked") + protected Key() { + this.annotationStrategy = NullAnnotationStrategy.INSTANCE; + this.typeLiteral = MoreTypes.canonicalizeForKey( + (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); + this.hashCode = computeHashCode(); + this.toStringSupplier = createToStringSupplier(); + } + + /** + * Unsafe. Constructs a key from a manually specified type. + */ + @SuppressWarnings("unchecked") + private Key(Type type, AnnotationStrategy annotationStrategy) { + this.annotationStrategy = annotationStrategy; + this.typeLiteral = MoreTypes.canonicalizeForKey((TypeLiteral) TypeLiteral.get(type)); + this.hashCode = computeHashCode(); + this.toStringSupplier = createToStringSupplier(); + } + + /** + * Constructs a key from a manually specified type. + */ + private Key(TypeLiteral typeLiteral, AnnotationStrategy annotationStrategy) { + this.annotationStrategy = annotationStrategy; + this.typeLiteral = MoreTypes.canonicalizeForKey(typeLiteral); + this.hashCode = computeHashCode(); + this.toStringSupplier = createToStringSupplier(); + } + + /** + * Gets a key for an injection type and an annotation strategy. + */ + static Key get(Class type, + AnnotationStrategy annotationStrategy) { + return new Key(type, annotationStrategy); + } + + /** + * Gets a key for an injection type. + */ + public static Key get(Class type) { + return new Key(type, NullAnnotationStrategy.INSTANCE); + } + + /** + * Gets a key for an injection type and an annotation type. + */ + public static Key get(Class type, + Class annotationType) { + return new Key(type, strategyFor(annotationType)); + } + + /** + * Gets a key for an injection type and an annotation. + */ + public static Key get(Class type, Annotation annotation) { + return new Key(type, strategyFor(annotation)); + } + + /** + * Gets a key for an injection type. + */ + public static Key get(Type type) { + return new Key(type, NullAnnotationStrategy.INSTANCE); + } + + /** + * Gets a key for an injection type and an annotation type. + */ + public static Key get(Type type, + Class annotationType) { + return new Key(type, strategyFor(annotationType)); + } + + /** + * Gets a key for an injection type and an annotation. + */ + public static Key get(Type type, Annotation annotation) { + return new Key(type, strategyFor(annotation)); + } + + /** + * Gets a key for an injection type. + */ + public static Key get(TypeLiteral typeLiteral) { + return new Key(typeLiteral, NullAnnotationStrategy.INSTANCE); + } + + /** + * Gets a key for an injection type and an annotation type. + */ + public static Key get(TypeLiteral typeLiteral, + Class annotationType) { + return new Key(typeLiteral, strategyFor(annotationType)); + } + + /** + * Gets a key for an injection type and an annotation. + */ + public static Key get(TypeLiteral typeLiteral, + Annotation annotation) { + return new Key(typeLiteral, strategyFor(annotation)); + } + + /** + * Gets the strategy for an annotation. + */ + static AnnotationStrategy strategyFor(Annotation annotation) { + checkNotNull(annotation, "annotation"); + Class annotationType = annotation.annotationType(); + ensureRetainedAtRuntime(annotationType); + ensureIsBindingAnnotation(annotationType); + + if (Annotations.isMarker(annotationType)) { + return new AnnotationTypeStrategy(annotationType, annotation); + } + + return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation)); + } + + /** + * Gets the strategy for an annotation type. + */ + static AnnotationStrategy strategyFor(Class annotationType) { + annotationType = Annotations.canonicalizeIfNamed(annotationType); + if (isAllDefaultMethods(annotationType)) { + return strategyFor(generateAnnotation(annotationType)); + } + + checkNotNull(annotationType, "annotation type"); + ensureRetainedAtRuntime(annotationType); + ensureIsBindingAnnotation(annotationType); + return new AnnotationTypeStrategy(annotationType, null); + + } + + private static void ensureRetainedAtRuntime( + Class annotationType) { + checkArgument(Annotations.isRetainedAtRuntime(annotationType), + "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).", + annotationType.getName()); + } + + private static void ensureIsBindingAnnotation(Class annotationType) { + checkArgument(Annotations.isBindingAnnotation(annotationType), + "%s is not a binding annotation. Please annotate it with @BindingAnnotation.", + annotationType.getName()); + } + + /** + * Computes the hash code for this key. + */ + private int computeHashCode() { + return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); + } + + /** + * @return a {@link Supplier} which memoizes the value for lazy initialization. + */ + private Supplier createToStringSupplier() { + // The performance hit on access is acceptable since the intended use is for non-performance- + // critical applications. + return Suppliers.memoize(new Supplier() { + @Override + public String get() { + return "Key[type=" + typeLiteral + ", annotation=" + annotationStrategy + "]"; + } + }); + } + + /** + * Gets the key type. + */ + public final TypeLiteral getTypeLiteral() { + return typeLiteral; + } + + /** + * Gets the annotation type. + */ + public final Class getAnnotationType() { + return annotationStrategy.getAnnotationType(); + } + + /** + * Gets the annotation. + */ + public final Annotation getAnnotation() { + return annotationStrategy.getAnnotation(); + } + + boolean hasAnnotationType() { + return annotationStrategy.getAnnotationType() != null; + } + + String getAnnotationName() { + Annotation annotation = annotationStrategy.getAnnotation(); + if (annotation != null) { + return annotation.toString(); + } + + // not test-covered + return annotationStrategy.getAnnotationType().toString(); + } + + Class getRawType() { + return typeLiteral.getRawType(); + } + + /** + * Gets the key of this key's provider. + */ + Key> providerKey() { + return ofType(typeLiteral.providerType()); + } + + @Override + public final boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Key)) { + return false; + } + Key other = (Key) o; + return annotationStrategy.equals(other.annotationStrategy) + && typeLiteral.equals(other.typeLiteral); + } + + @Override + public final int hashCode() { + return this.hashCode; + } + + @Override + public final String toString() { + return toStringSupplier.get(); + } + + /** + * Returns a new key of the specified type with the same annotation as this + * key. + */ + public Key ofType(Class type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns a new key of the specified type with the same annotation as this + * key. + */ + public Key ofType(Type type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns a new key of the specified type with the same annotation as this + * key. + */ + public Key ofType(TypeLiteral type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns true if this key has annotation attributes. + */ + public boolean hasAttributes() { + return annotationStrategy.hasAttributes(); + } + + /** + * Returns this key without annotation attributes, i.e. with only the + * annotation type. + */ + public Key withoutAttributes() { + return new Key(typeLiteral, annotationStrategy.withoutAttributes()); + } + + static enum NullAnnotationStrategy implements AnnotationStrategy { + INSTANCE; + + public boolean hasAttributes() { + return false; + } + + public AnnotationStrategy withoutAttributes() { + throw new UnsupportedOperationException("Key already has no attributes."); + } + + public Annotation getAnnotation() { + return null; + } + + public Class getAnnotationType() { + return null; + } + + @Override + public String toString() { + return "[none]"; + } + } + + interface AnnotationStrategy { + Annotation getAnnotation(); + + Class getAnnotationType(); + + boolean hasAttributes(); + + AnnotationStrategy withoutAttributes(); + } + + // this class not test-covered + static class AnnotationInstanceStrategy implements AnnotationStrategy { + + final Annotation annotation; + + AnnotationInstanceStrategy(Annotation annotation) { + this.annotation = checkNotNull(annotation, "annotation"); + } + + public boolean hasAttributes() { + return true; + } + + public AnnotationStrategy withoutAttributes() { + return new AnnotationTypeStrategy(getAnnotationType(), annotation); + } + + public Annotation getAnnotation() { + return annotation; + } + + public Class getAnnotationType() { + return annotation.annotationType(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AnnotationInstanceStrategy)) { + return false; + } + + AnnotationInstanceStrategy other = (AnnotationInstanceStrategy) o; + return annotation.equals(other.annotation); + } + + @Override + public int hashCode() { + return annotation.hashCode(); + } + + @Override + public String toString() { + return annotation.toString(); + } + } + + static class AnnotationTypeStrategy implements AnnotationStrategy { + + final Class annotationType; + + // Keep the instance around if we have it so the client can request it. + final Annotation annotation; + + AnnotationTypeStrategy(Class annotationType, + Annotation annotation) { + this.annotationType = checkNotNull(annotationType, "annotation type"); + this.annotation = annotation; + } + + public boolean hasAttributes() { + return false; + } + + public AnnotationStrategy withoutAttributes() { + throw new UnsupportedOperationException("Key already has no attributes."); + } + + public Annotation getAnnotation() { + return annotation; + } + + public Class getAnnotationType() { + return annotationType; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AnnotationTypeStrategy)) { + return false; + } + + AnnotationTypeStrategy other = (AnnotationTypeStrategy) o; + return annotationType.equals(other.annotationType); + } + + @Override + public int hashCode() { + return annotationType.hashCode(); + } + + @Override + public String toString() { + return "@" + annotationType.getName(); + } + } +} diff --git a/src/main/java/com/google/inject/MembersInjector.java b/src/main/java/com/google/inject/MembersInjector.java new file mode 100644 index 0000000..f9fa7b9 --- /dev/null +++ b/src/main/java/com/google/inject/MembersInjector.java @@ -0,0 +1,22 @@ +package com.google.inject; + +/** + * Injects dependencies into the fields and methods on instances of type {@code T}. Ignores the + * presence or absence of an injectable constructor. + * + * @param type to inject members of + */ +public interface MembersInjector { + + /** + * Injects dependencies into the fields and methods of {@code instance}. Ignores the presence or + * absence of an injectable constructor. + * + *

Whenever Guice creates an instance, it performs this injection automatically (after first + * performing constructor injection), so if you're able to let Guice create all your objects for + * you, you'll never need to use this method. + * + * @param instance to inject members on. May be {@code null}. + */ + void injectMembers(T instance); +} diff --git a/src/main/java/com/google/inject/Module.java b/src/main/java/com/google/inject/Module.java new file mode 100644 index 0000000..5a9634a --- /dev/null +++ b/src/main/java/com/google/inject/Module.java @@ -0,0 +1,27 @@ +package com.google.inject; + +/** + * A module contributes configuration information, typically interface + * bindings, which will be used to create an {@link Injector}. A Guice-based + * application is ultimately composed of little more than a set of + * {@code Module}s and some bootstrapping code. + * + *

Your Module classes can use a more streamlined syntax by extending + * {@link AbstractModule} rather than implementing this interface directly. + * + *

In addition to the bindings configured via {@link #configure}, bindings + * will be created for all methods annotated with {@literal @}{@link Provides}. + * Use scope and binding annotations on these methods to configure the + * bindings. + */ +public interface Module { + + /** + * Contributes bindings and other configurations for this module to {@code binder}. + * + *

Do not invoke this method directly to install submodules. Instead use + * {@link Binder#install(Module)}, which ensures that {@link Provides provider methods} are + * discovered. + */ + void configure(Binder binder); +} diff --git a/src/main/java/com/google/inject/PrivateBinder.java b/src/main/java/com/google/inject/PrivateBinder.java new file mode 100644 index 0000000..cddf1a1 --- /dev/null +++ b/src/main/java/com/google/inject/PrivateBinder.java @@ -0,0 +1,35 @@ +package com.google.inject; + +import com.google.inject.binder.AnnotatedElementBuilder; + +/** + * Returns a binder whose configuration information is hidden from its environment by default. See + * {@link com.google.inject.PrivateModule PrivateModule} for details. + * + */ +public interface PrivateBinder extends Binder { + + /** + * Makes the binding for {@code key} available to the enclosing environment + */ + void expose(Key key); + + /** + * Makes a binding for {@code type} available to the enclosing environment. Use {@link + * com.google.inject.binder.AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} + * with a + * binding annotation. + */ + AnnotatedElementBuilder expose(Class type); + + /** + * Makes a binding for {@code type} available to the enclosing environment. Use {@link + * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + AnnotatedElementBuilder expose(TypeLiteral type); + + PrivateBinder withSource(Object source); + + PrivateBinder skipSources(Class... classesToSkip); +} diff --git a/src/main/java/com/google/inject/PrivateModule.java b/src/main/java/com/google/inject/PrivateModule.java new file mode 100644 index 0000000..f993439 --- /dev/null +++ b/src/main/java/com/google/inject/PrivateModule.java @@ -0,0 +1,279 @@ +package com.google.inject; + +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.AnnotatedConstantBindingBuilder; +import com.google.inject.binder.AnnotatedElementBuilder; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.matcher.Matcher; +import com.google.inject.spi.Message; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.spi.TypeConverter; +import com.google.inject.spi.TypeListener; + +import java.lang.annotation.Annotation; + +import static com.google.common.base.Preconditions.checkState; + +/** + * A module whose configuration information is hidden from its environment by default. Only bindings + * that are explicitly exposed will be available to other modules and to the users of the injector. + * This module may expose the bindings it creates and the bindings of the modules it installs. + * + *

A private module can be nested within a regular module or within another private module using + * {@link Binder#install install()}. Its bindings live in a new environment that inherits bindings, + * type converters, scopes, and interceptors from the surrounding ("parent") environment. When you + * nest multiple private modules, the result is a tree of environments where the injector's + * environment is the root. + * + *

Guice EDSL bindings can be exposed with {@link #expose(Class) expose()}. {@literal @}{@link + * com.google.inject.Provides Provides} bindings can be exposed with the {@literal @}{@link + * Exposed} annotation: + * + *

+ * public class FooBarBazModule extends PrivateModule {
+ *   protected void configure() {
+ *     bind(Foo.class).to(RealFoo.class);
+ *     expose(Foo.class);
+ *
+ *     install(new TransactionalBarModule());
+ *     expose(Bar.class).annotatedWith(Transactional.class);
+ *
+ *     bind(SomeImplementationDetail.class);
+ *     install(new MoreImplementationDetailsModule());
+ *   }
+ *
+ *   {@literal @}Provides {@literal @}Exposed
+ *   public Baz provideBaz() {
+ *     return new SuperBaz();
+ *   }
+ * }
+ * 
+ * + *

Private modules are implemented using {@link Injector#createChildInjector(Module[]) parent + * injectors}. When it can satisfy their dependencies, just-in-time bindings will be created in the + * root environment. Such bindings are shared among all environments in the tree. + * + *

The scope of a binding is constrained to its environment. A singleton bound in a private + * module will be unique to its environment. But a binding for the same type in a different private + * module will yield a different instance. + * + *

A shared binding that injects the {@code Injector} gets the root injector, which only has + * access to bindings in the root environment. An explicit binding that injects the {@code Injector} + * gets access to all bindings in the child environment. + * + *

To promote a just-in-time binding to an explicit binding, bind it: + *

+ *   bind(FooImpl.class);
+ * 
+ * + */ +public abstract class PrivateModule implements Module { + + /** + * Like abstract module, the binder of the current private module + */ + private PrivateBinder binder; + + public final synchronized void configure(Binder binder) { + checkState(this.binder == null, "Re-entry is not allowed."); + + // Guice treats PrivateModules specially and passes in a PrivateBinder automatically. + this.binder = (PrivateBinder) binder.skipSources(PrivateModule.class); + try { + configure(); + } finally { + this.binder = null; + } + } + + /** + * Creates bindings and other configurations private to this module. Use {@link #expose(Class) + * expose()} to make the bindings in this module available externally. + */ + protected abstract void configure(); + + /** + * Makes the binding for {@code key} available to other modules and the injector. + */ + protected final void expose(Key key) { + binder().expose(key); + } + + /** + * Makes a binding for {@code type} available to other modules and the injector. Use {@link + * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + protected final AnnotatedElementBuilder expose(Class type) { + return binder().expose(type); + } + + /** + * Makes a binding for {@code type} available to other modules and the injector. Use {@link + * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + protected final AnnotatedElementBuilder expose(TypeLiteral type) { + return binder().expose(type); + } + + // everything below is copied from AbstractModule + + /** + * Returns the current binder. + */ + protected final PrivateBinder binder() { + checkState(binder != null, "The binder can only be used inside configure()"); + return binder; + } + + /** + * @see Binder#bindScope(Class, Scope) + */ + protected final void bindScope(Class scopeAnnotation, Scope scope) { + binder().bindScope(scopeAnnotation, scope); + } + + /** + * @see Binder#bind(Key) + */ + protected final LinkedBindingBuilder bind(Key key) { + return binder().bind(key); + } + + /** + * @see Binder#bind(TypeLiteral) + */ + protected final AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) { + return binder().bind(typeLiteral); + } + + /** + * @see Binder#bind(Class) + */ + protected final AnnotatedBindingBuilder bind(Class clazz) { + return binder().bind(clazz); + } + + /** + * @see Binder#bindConstant() + */ + protected final AnnotatedConstantBindingBuilder bindConstant() { + return binder().bindConstant(); + } + + /** + * @see Binder#install(Module) + */ + protected final void install(Module module) { + binder().install(module); + } + + /** + * @see Binder#addError(String, Object[]) + */ + protected final void addError(String message, Object... arguments) { + binder().addError(message, arguments); + } + + /** + * @see Binder#addError(Throwable) + */ + protected final void addError(Throwable t) { + binder().addError(t); + } + + /** + * @see Binder#addError(Message) + */ + protected final void addError(Message message) { + binder().addError(message); + } + + /** + * @see Binder#requestInjection(Object) + */ + protected final void requestInjection(Object instance) { + binder().requestInjection(instance); + } + + /** + * @see Binder#requestStaticInjection(Class[]) + */ + protected final void requestStaticInjection(Class... types) { + binder().requestStaticInjection(types); + } + + /** + * Instructs Guice to require a binding to the given key. + */ + protected final void requireBinding(Key key) { + binder().getProvider(key); + } + + /** + * Instructs Guice to require a binding to the given type. + */ + protected final void requireBinding(Class type) { + binder().getProvider(type); + } + + /** + * @see Binder#getProvider(Key) + */ + protected final Provider getProvider(Key key) { + return binder().getProvider(key); + } + + /** + * @see Binder#getProvider(Class) + */ + protected final Provider getProvider(Class type) { + return binder().getProvider(type); + } + + /** + * @see Binder#convertToTypes(com.google.inject.matcher.Matcher, com.google.inject.spi.TypeConverter) + */ + protected final void convertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + binder().convertToTypes(typeMatcher, converter); + } + + /** + * @see Binder#currentStage() + */ + protected final Stage currentStage() { + return binder().currentStage(); + } + + /** + * @see Binder#getMembersInjector(Class) + */ + protected MembersInjector getMembersInjector(Class type) { + return binder().getMembersInjector(type); + } + + /** + * @see Binder#getMembersInjector(TypeLiteral) + */ + protected MembersInjector getMembersInjector(TypeLiteral type) { + return binder().getMembersInjector(type); + } + + /** + * @see Binder#bindListener(com.google.inject.matcher.Matcher, com.google.inject.spi.TypeListener) + */ + protected void bindListener(Matcher> typeMatcher, + TypeListener listener) { + binder().bindListener(typeMatcher, listener); + } + + /** + * @see Binder#bindListener(Matcher, ProvisionListener...) + */ + protected void bindListener(Matcher> bindingMatcher, + ProvisionListener... listeners) { + binder().bindListener(bindingMatcher, listeners); + } +} diff --git a/src/main/java/com/google/inject/ProvidedBy.java b/src/main/java/com/google/inject/ProvidedBy.java new file mode 100644 index 0000000..29b1a32 --- /dev/null +++ b/src/main/java/com/google/inject/ProvidedBy.java @@ -0,0 +1,20 @@ +package com.google.inject; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A pointer to the default provider type for a type. + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface ProvidedBy { + + /** + * The implementation type. + */ + Class> value(); +} diff --git a/src/main/java/com/google/inject/Provider.java b/src/main/java/com/google/inject/Provider.java new file mode 100644 index 0000000..806cad8 --- /dev/null +++ b/src/main/java/com/google/inject/Provider.java @@ -0,0 +1,36 @@ +package com.google.inject; + +/** + * An object capable of providing instances of type {@code T}. Providers are used in numerous ways + * 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. + *
+ * + * @param the type of object this provides + */ +public interface Provider extends javax.inject.Provider { + + /** + * Provides an instance of {@code T}. Must never return {@code null}. + * + * @throws ProvisionException if an instance cannot be provided. Such exceptions include messages + * and throwables to describe why provision failed. + */ + T get(); +} diff --git a/src/main/java/com/google/inject/Provides.java b/src/main/java/com/google/inject/Provides.java new file mode 100644 index 0000000..57d0af4 --- /dev/null +++ b/src/main/java/com/google/inject/Provides.java @@ -0,0 +1,18 @@ +package com.google.inject; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotates methods of a {@link Module} to create a provider method binding. The method's return + * type is bound to its returned value. Guice will pass dependencies to the method as parameters. + */ +@Documented +@Target(METHOD) +@Retention(RUNTIME) +public @interface Provides { +} diff --git a/src/main/java/com/google/inject/ProvisionException.java b/src/main/java/com/google/inject/ProvisionException.java new file mode 100644 index 0000000..79fc547 --- /dev/null +++ b/src/main/java/com/google/inject/ProvisionException.java @@ -0,0 +1,49 @@ +package com.google.inject; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.internal.Errors; +import com.google.inject.spi.Message; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Indicates that there was a runtime failure while providing an instance. + * + */ +public final class ProvisionException extends RuntimeException { + + private static final long serialVersionUID = 0; + private final ImmutableSet messages; + + /** + * Creates a ProvisionException containing {@code messages}. + */ + public ProvisionException(Iterable messages) { + this.messages = ImmutableSet.copyOf(messages); + checkArgument(!this.messages.isEmpty()); + initCause(Errors.getOnlyCause(this.messages)); + } + + public ProvisionException(String message, Throwable cause) { + super(cause); + this.messages = ImmutableSet.of(new Message(message, cause)); + } + + public ProvisionException(String message) { + this.messages = ImmutableSet.of(new Message(message)); + } + + /** + * Returns messages for the errors that caused this exception. + */ + public Collection getErrorMessages() { + return messages; + } + + @Override + public String getMessage() { + return Errors.format("Unable to provision, see the following errors", messages); + } +} diff --git a/src/main/java/com/google/inject/Scope.java b/src/main/java/com/google/inject/Scope.java new file mode 100644 index 0000000..aa01e79 --- /dev/null +++ b/src/main/java/com/google/inject/Scope.java @@ -0,0 +1,41 @@ +package com.google.inject; + +/** + * A scope is a level of visibility that instances provided by Guice may have. + * By default, an instance created by the {@link Injector} has no scope, + * meaning it has no state from the framework's perspective -- the + * {@code Injector} creates it, injects it once into the class that required it, + * and then immediately forgets it. Associating a scope with a particular + * binding allows the created instance to be "remembered" and possibly used + * again for other injections. + * + *

An example of a scope is {@link Scopes#SINGLETON}. + */ +public interface Scope { + + /** + * Scopes a provider. The returned provider returns objects from this scope. + * If an object does not exist in this scope, the provider can use the given + * unscoped provider to retrieve one. + * + *

Scope implementations are strongly encouraged to override + * {@link Object#toString} in the returned provider and include the backing + * provider's {@code toString()} output. + * + * @param key binding key + * @param unscoped locates an instance when one doesn't already exist in this + * scope. + * @return a new provider which only delegates to the given unscoped provider + * when an instance of the requested object doesn't already exist in this + * scope + */ + public Provider scope(Key key, Provider unscoped); + + /** + * A short but useful description of this scope. For comparison, the standard + * scopes that ship with guice use the descriptions + * {@code "Scopes.SINGLETON"}, {@code "ServletScopes.SESSION"} and + * {@code "ServletScopes.REQUEST"}. + */ + String toString(); +} diff --git a/src/main/java/com/google/inject/ScopeAnnotation.java b/src/main/java/com/google/inject/ScopeAnnotation.java new file mode 100644 index 0000000..265ecb4 --- /dev/null +++ b/src/main/java/com/google/inject/ScopeAnnotation.java @@ -0,0 +1,24 @@ +package com.google.inject; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotates annotations which are used for scoping. Only one such annotation + * may apply to a single implementation class. You must also annotate scope + * annotations with {@code @Retention(RUNTIME)}. For example: + * + *

+ *   {@code @}Retention(RUNTIME)
+ *   {@code @}Target(TYPE, METHOD)
+ *   {@code @}ScopeAnnotation
+ *   public {@code @}interface SessionScoped {}
+ * 
+ */ +@Target(ANNOTATION_TYPE) +@Retention(RUNTIME) +public @interface ScopeAnnotation { +} diff --git a/src/main/java/com/google/inject/Scopes.java b/src/main/java/com/google/inject/Scopes.java new file mode 100644 index 0000000..70f957f --- /dev/null +++ b/src/main/java/com/google/inject/Scopes.java @@ -0,0 +1,162 @@ +package com.google.inject; + +import com.google.inject.internal.CircularDependencyProxy; +import com.google.inject.internal.LinkedBindingImpl; +import com.google.inject.internal.SingletonScope; +import com.google.inject.spi.BindingScopingVisitor; +import com.google.inject.spi.ExposedBinding; + +import java.lang.annotation.Annotation; + +/** + * Built-in scope implementations. + */ +public class Scopes { + + /** + * One instance per {@link Injector}. Also see {@code @}{@link Singleton}. + */ + public static final Scope SINGLETON = new SingletonScope(); + /** + * No scope; the same as not applying any scope at all. Each time the + * Injector obtains an instance of an object with "no scope", it injects this + * instance then immediately forgets it. When the next request for the same + * binding arrives it will need to obtain the instance over again. + * + *

This exists only in case a class has been annotated with a scope + * annotation such as {@link Singleton @Singleton}, and you need to override + * this to "no scope" in your binding. + */ + public static final Scope NO_SCOPE = new Scope() { + public Provider scope(Key key, Provider unscoped) { + return unscoped; + } + + @Override + public String toString() { + return "Scopes.NO_SCOPE"; + } + }; + private static final BindingScopingVisitor IS_SINGLETON_VISITOR + = new BindingScopingVisitor() { + public Boolean visitNoScoping() { + return false; + } + + public Boolean visitScopeAnnotation(Class scopeAnnotation) { + return scopeAnnotation == Singleton.class + || scopeAnnotation == javax.inject.Singleton.class; + } + + public Boolean visitScope(Scope scope) { + return scope == Scopes.SINGLETON; + } + + public Boolean visitEagerSingleton() { + return true; + } + }; + + private Scopes() { + } + + /** + * Returns true if {@code binding} is singleton-scoped. If the binding is a {@link + * com.google.inject.spi.LinkedKeyBinding linked key binding} and belongs to an injector (ie. it + * was retrieved via {@link Injector#getBinding Injector.getBinding()}), then this method will + * also true if the target binding is singleton-scoped. + */ + public static boolean isSingleton(Binding binding) { + do { + boolean singleton = binding.acceptScopingVisitor(IS_SINGLETON_VISITOR); + if (singleton) { + return true; + } + + if (binding instanceof LinkedBindingImpl) { + LinkedBindingImpl linkedBinding = (LinkedBindingImpl) binding; + Injector injector = linkedBinding.getInjector(); + if (injector != null) { + binding = injector.getBinding(linkedBinding.getLinkedKey()); + continue; + } + } else if (binding instanceof ExposedBinding) { + ExposedBinding exposedBinding = (ExposedBinding) binding; + Injector injector = exposedBinding.getPrivateElements().getInjector(); + if (injector != null) { + binding = injector.getBinding(exposedBinding.getKey()); + continue; + } + } + + return false; + } while (true); + } + + /** + * Returns true if {@code binding} has the given scope. If the binding is a {@link + * com.google.inject.spi.LinkedKeyBinding linked key binding} and belongs to an injector (ie. it + * was retrieved via {@link Injector#getBinding Injector.getBinding()}), then this method will + * also true if the target binding has the given scope. + * + * @param binding binding to check + * @param scope scope implementation instance + * @param scopeAnnotation scope annotation class + */ + public static boolean isScoped(Binding binding, final Scope scope, + final Class scopeAnnotation) { + do { + boolean matches = binding.acceptScopingVisitor(new BindingScopingVisitor() { + public Boolean visitNoScoping() { + return false; + } + + public Boolean visitScopeAnnotation(Class visitedAnnotation) { + return visitedAnnotation == scopeAnnotation; + } + + public Boolean visitScope(Scope visitedScope) { + return visitedScope == scope; + } + + public Boolean visitEagerSingleton() { + return false; + } + }); + + if (matches) { + return true; + } + + if (binding instanceof LinkedBindingImpl) { + LinkedBindingImpl linkedBinding = (LinkedBindingImpl) binding; + Injector injector = linkedBinding.getInjector(); + if (injector != null) { + binding = injector.getBinding(linkedBinding.getLinkedKey()); + continue; + } + } else if (binding instanceof ExposedBinding) { + ExposedBinding exposedBinding = (ExposedBinding) binding; + Injector injector = exposedBinding.getPrivateElements().getInjector(); + if (injector != null) { + binding = injector.getBinding(exposedBinding.getKey()); + continue; + } + } + + return false; + } while (true); + } + + /** + * Returns true if the object is a proxy for a circular dependency, + * constructed by Guice because it encountered a circular dependency. Scope + * implementations should be careful to not cache circular proxies, + * because the proxies are not intended for general purpose use. (They are + * designed just to fulfill the immediate injection, not all injections. + * Caching them can lead to IllegalArgumentExceptions or ClassCastExceptions.) + */ + public static boolean isCircularProxy(Object object) { + return object instanceof CircularDependencyProxy; + } +} diff --git a/src/main/java/com/google/inject/Singleton.java b/src/main/java/com/google/inject/Singleton.java new file mode 100644 index 0000000..d3bd675 --- /dev/null +++ b/src/main/java/com/google/inject/Singleton.java @@ -0,0 +1,17 @@ +package com.google.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Apply this to implementation classes when you want only one instance + * (per {@link Injector}) to be reused for all injections for that binding. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RUNTIME) +@ScopeAnnotation +public @interface Singleton { +} diff --git a/src/main/java/com/google/inject/Stage.java b/src/main/java/com/google/inject/Stage.java new file mode 100644 index 0000000..f468113 --- /dev/null +++ b/src/main/java/com/google/inject/Stage.java @@ -0,0 +1,26 @@ +package com.google.inject; + +/** + * The stage we're running in. + */ +public enum Stage { + + /** + * We're running in a tool (an IDE plugin for example). We need binding meta data but not a + * functioning Injector. Do not inject members of instances. Do not load eager singletons. Do as + * little as possible so our tools run nice and snappy. Injectors created in this stage cannot + * be used to satisfy injections. + */ + TOOL, + + /** + * We want fast startup times at the expense of runtime performance and some up front error + * checking. + */ + DEVELOPMENT, + + /** + * We want to catch errors as early as possible and take performance hits up front. + */ + PRODUCTION +} diff --git a/src/main/java/com/google/inject/TypeLiteral.java b/src/main/java/com/google/inject/TypeLiteral.java new file mode 100644 index 0000000..aaa5873 --- /dev/null +++ b/src/main/java/com/google/inject/TypeLiteral.java @@ -0,0 +1,321 @@ +package com.google.inject; + +import com.google.common.collect.ImmutableList; +import com.google.inject.internal.MoreTypes; +import com.google.inject.util.Types; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.inject.internal.MoreTypes.canonicalize; + +/** + * Represents a generic type {@code T}. Java doesn't yet provide a way to + * represent generic types, so this class does. Forces clients to create a + * subclass of this class which enables retrieval the type information even at + * runtime. + * + *

For example, to create a type literal for {@code List}, you can + * create an empty anonymous inner class: + * + *

+ * {@code TypeLiteral> list = new TypeLiteral>() {};} + * + *

Along with modeling generic types, this class can resolve type parameters. + * For example, to figure out what type {@code keySet()} returns on a {@code + * Map}, use this code:

   {@code
+ *
+ *   TypeLiteral> mapType
+ *       = new TypeLiteral>() {};
+ *   TypeLiteral keySetType
+ *       = mapType.getReturnType(Map.class.getMethod("keySet"));
+ *   System.out.println(keySetType); // prints "Set"}
+ * + */ +public class TypeLiteral { + + final Class rawType; + final Type type; + final int hashCode; + + /** + * Constructs a new type literal. Derives represented class from type + * parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + */ + @SuppressWarnings("unchecked") + protected TypeLiteral() { + this.type = getSuperclassTypeParameter(getClass()); + this.rawType = (Class) MoreTypes.getRawType(type); + this.hashCode = type.hashCode(); + } + + /** + * Unsafe. Constructs a type literal manually. + */ + @SuppressWarnings("unchecked") + TypeLiteral(Type type) { + this.type = canonicalize(checkNotNull(type, "type")); + this.rawType = (Class) MoreTypes.getRawType(this.type); + this.hashCode = this.type.hashCode(); + } + + /** + * Returns the type from super class's type parameter in {@link MoreTypes#canonicalize(Type) + * canonical form}. + */ + static Type getSuperclassTypeParameter(Class subclass) { + Type superclass = subclass.getGenericSuperclass(); + if (superclass instanceof Class) { + throw new RuntimeException("Missing type parameter."); + } + ParameterizedType parameterized = (ParameterizedType) superclass; + return canonicalize(parameterized.getActualTypeArguments()[0]); + } + + /** + * Gets type literal from super class's type parameter. + */ + static TypeLiteral fromSuperclassTypeParameter(Class subclass) { + return new TypeLiteral(getSuperclassTypeParameter(subclass)); + } + + /** + * Gets type literal for the given {@code Type} instance. + */ + public static TypeLiteral get(Type type) { + return new TypeLiteral(type); + } + + /** + * Gets type literal for the given {@code Class} instance. + */ + public static TypeLiteral get(Class type) { + return new TypeLiteral(type); + } + + /** + * Returns the raw (non-generic) type for this type. + * + */ + public final Class getRawType() { + return rawType; + } + + /** + * Gets underlying {@code Type} instance. + */ + public final Type getType() { + return type; + } + + /** + * Gets the type of this type's provider. + */ + @SuppressWarnings("unchecked") + final TypeLiteral> providerType() { + // This cast is safe and wouldn't generate a warning if Type had a type + // parameter. + return (TypeLiteral>) get(Types.providerOf(getType())); + } + + @Override + public final int hashCode() { + return this.hashCode; + } + + @Override + public final boolean equals(Object o) { + return o instanceof TypeLiteral + && MoreTypes.equals(type, ((TypeLiteral) o).type); + } + + @Override + public final String toString() { + return MoreTypes.typeToString(type); + } + + /** + * Returns an immutable list of the resolved types. + */ + private List> resolveAll(Type[] types) { + TypeLiteral[] result = new TypeLiteral[types.length]; + for (int t = 0; t < types.length; t++) { + result[t] = resolve(types[t]); + } + return ImmutableList.copyOf(result); + } + + /** + * Resolves known type parameters in {@code toResolve} and returns the result. + */ + TypeLiteral resolve(Type toResolve) { + return TypeLiteral.get(resolveType(toResolve)); + } + + Type resolveType(Type toResolve) { + // this implementation is made a little more complicated in an attempt to avoid object-creation + while (true) { + if (toResolve instanceof TypeVariable) { + TypeVariable original = (TypeVariable) toResolve; + toResolve = MoreTypes.resolveTypeVariable(type, rawType, original); + if (toResolve == original) { + return toResolve; + } + + } else if (toResolve instanceof GenericArrayType) { + GenericArrayType original = (GenericArrayType) toResolve; + Type componentType = original.getGenericComponentType(); + Type newComponentType = resolveType(componentType); + return componentType == newComponentType + ? original + : Types.arrayOf(newComponentType); + + } else if (toResolve instanceof ParameterizedType) { + ParameterizedType original = (ParameterizedType) toResolve; + Type ownerType = original.getOwnerType(); + Type newOwnerType = resolveType(ownerType); + boolean changed = newOwnerType != ownerType; + + Type[] args = original.getActualTypeArguments(); + for (int t = 0, length = args.length; t < length; t++) { + Type resolvedTypeArgument = resolveType(args[t]); + if (resolvedTypeArgument != args[t]) { + if (!changed) { + args = args.clone(); + changed = true; + } + args[t] = resolvedTypeArgument; + } + } + + return changed + ? Types.newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) + : original; + + } else if (toResolve instanceof WildcardType) { + WildcardType original = (WildcardType) toResolve; + Type[] originalLowerBound = original.getLowerBounds(); + Type[] originalUpperBound = original.getUpperBounds(); + + if (originalLowerBound.length == 1) { + Type lowerBound = resolveType(originalLowerBound[0]); + if (lowerBound != originalLowerBound[0]) { + return Types.supertypeOf(lowerBound); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolveType(originalUpperBound[0]); + if (upperBound != originalUpperBound[0]) { + return Types.subtypeOf(upperBound); + } + } + return original; + + } else { + return toResolve; + } + } + } + + /** + * Returns the generic form of {@code supertype}. For example, if this is {@code + * ArrayList}, this returns {@code Iterable} given the input {@code + * Iterable.class}. + * + * @param supertype a superclass of, or interface implemented by, this. + */ + public TypeLiteral getSupertype(Class supertype) { + checkArgument(supertype.isAssignableFrom(rawType), + "%s is not a supertype of %s", supertype, this.type); + return resolve(MoreTypes.getGenericSupertype(type, rawType, supertype)); + } + + /** + * Returns the resolved generic type of {@code field}. + * + * @param field a field defined by this or any superclass. + */ + public TypeLiteral getFieldType(Field field) { + checkArgument(field.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", field, type); + return resolve(field.getGenericType()); + } + + /** + * Returns the resolved generic parameter types of {@code methodOrConstructor}. + * + * @param methodOrConstructor a method or constructor defined by this or any supertype. + */ + public List> getParameterTypes(Member methodOrConstructor) { + Type[] genericParameterTypes; + + if (methodOrConstructor instanceof Method) { + Method method = (Method) methodOrConstructor; + checkArgument(method.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", method, type); + genericParameterTypes = method.getGenericParameterTypes(); + + } else if (methodOrConstructor instanceof Constructor) { + Constructor constructor = (Constructor) methodOrConstructor; + checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType), + "%s does not construct a supertype of %s", constructor, type); + genericParameterTypes = constructor.getGenericParameterTypes(); + + } else { + throw new IllegalArgumentException("Not a method or a constructor: " + methodOrConstructor); + } + + return resolveAll(genericParameterTypes); + } + + /** + * Returns the resolved generic exception types thrown by {@code constructor}. + * + * @param methodOrConstructor a method or constructor defined by this or any supertype. + */ + public List> getExceptionTypes(Member methodOrConstructor) { + Type[] genericExceptionTypes; + + if (methodOrConstructor instanceof Method) { + Method method = (Method) methodOrConstructor; + checkArgument(method.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", method, type); + genericExceptionTypes = method.getGenericExceptionTypes(); + + } else if (methodOrConstructor instanceof Constructor) { + Constructor constructor = (Constructor) methodOrConstructor; + checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType), + "%s does not construct a supertype of %s", constructor, type); + genericExceptionTypes = constructor.getGenericExceptionTypes(); + + } else { + throw new IllegalArgumentException("Not a method or a constructor: " + methodOrConstructor); + } + + return resolveAll(genericExceptionTypes); + } + + /** + * Returns the resolved generic return type of {@code method}. + * + * @param method a method defined by this or any supertype. + */ + public TypeLiteral getReturnType(Method method) { + checkArgument(method.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", method, type); + return resolve(method.getGenericReturnType()); + } +} diff --git a/src/main/java/com/google/inject/binder/AnnotatedBindingBuilder.java b/src/main/java/com/google/inject/binder/AnnotatedBindingBuilder.java new file mode 100644 index 0000000..bfd9452 --- /dev/null +++ b/src/main/java/com/google/inject/binder/AnnotatedBindingBuilder.java @@ -0,0 +1,20 @@ +package com.google.inject.binder; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ +public interface AnnotatedBindingBuilder extends LinkedBindingBuilder { + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + LinkedBindingBuilder annotatedWith( + Class annotationType); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + LinkedBindingBuilder annotatedWith(Annotation annotation); +} diff --git a/src/main/java/com/google/inject/binder/AnnotatedConstantBindingBuilder.java b/src/main/java/com/google/inject/binder/AnnotatedConstantBindingBuilder.java new file mode 100644 index 0000000..7132452 --- /dev/null +++ b/src/main/java/com/google/inject/binder/AnnotatedConstantBindingBuilder.java @@ -0,0 +1,20 @@ +package com.google.inject.binder; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ +public interface AnnotatedConstantBindingBuilder { + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ConstantBindingBuilder annotatedWith( + Class annotationType); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ConstantBindingBuilder annotatedWith(Annotation annotation); +} diff --git a/src/main/java/com/google/inject/binder/AnnotatedElementBuilder.java b/src/main/java/com/google/inject/binder/AnnotatedElementBuilder.java new file mode 100644 index 0000000..85fbe62 --- /dev/null +++ b/src/main/java/com/google/inject/binder/AnnotatedElementBuilder.java @@ -0,0 +1,19 @@ +package com.google.inject.binder; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ +public interface AnnotatedElementBuilder { + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + void annotatedWith(Class annotationType); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + void annotatedWith(Annotation annotation); +} diff --git a/src/main/java/com/google/inject/binder/ConstantBindingBuilder.java b/src/main/java/com/google/inject/binder/ConstantBindingBuilder.java new file mode 100644 index 0000000..8a43b2d --- /dev/null +++ b/src/main/java/com/google/inject/binder/ConstantBindingBuilder.java @@ -0,0 +1,62 @@ +package com.google.inject.binder; + +/** + * Binds to a constant value. + */ +public interface ConstantBindingBuilder { + + /** + * Binds constant to the given value. + */ + void to(String value); + + /** + * Binds constant to the given value. + */ + void to(int value); + + /** + * Binds constant to the given value. + */ + void to(long value); + + /** + * Binds constant to the given value. + */ + void to(boolean value); + + /** + * Binds constant to the given value. + */ + void to(double value); + + /** + * Binds constant to the given value. + */ + void to(float value); + + /** + * Binds constant to the given value. + */ + void to(short value); + + /** + * Binds constant to the given value. + */ + void to(char value); + + /** + * Binds constant to the given value. + */ + void to(byte value); + + /** + * Binds constant to the given value. + */ + void to(Class value); + + /** + * Binds constant to the given value. + */ + > void to(E value); +} diff --git a/src/main/java/com/google/inject/binder/LinkedBindingBuilder.java b/src/main/java/com/google/inject/binder/LinkedBindingBuilder.java new file mode 100644 index 0000000..a22e2c0 --- /dev/null +++ b/src/main/java/com/google/inject/binder/LinkedBindingBuilder.java @@ -0,0 +1,79 @@ +package com.google.inject.binder; + +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; + +import java.lang.reflect.Constructor; + +/** + * See the EDSL examples at {@link com.google.inject.Binder}. + * + */ +public interface LinkedBindingBuilder extends ScopedBindingBuilder { + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder to(Class implementation); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder to(TypeLiteral implementation); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder to(Key targetKey); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + * + * @see com.google.inject.Injector#injectMembers + */ + void toInstance(T instance); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + * + * @see com.google.inject.Injector#injectMembers + */ + ScopedBindingBuilder toProvider(Provider provider); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + * + * @see com.google.inject.Injector#injectMembers + */ + ScopedBindingBuilder toProvider(javax.inject.Provider provider); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder toProvider( + Class> providerType); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder toProvider( + TypeLiteral> providerType); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder toProvider( + Key> providerKey); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder toConstructor(Constructor constructor); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + ScopedBindingBuilder toConstructor( + Constructor constructor, TypeLiteral type); +} diff --git a/src/main/java/com/google/inject/binder/ScopedBindingBuilder.java b/src/main/java/com/google/inject/binder/ScopedBindingBuilder.java new file mode 100644 index 0000000..3b6df6c --- /dev/null +++ b/src/main/java/com/google/inject/binder/ScopedBindingBuilder.java @@ -0,0 +1,29 @@ +package com.google.inject.binder; + +import com.google.inject.Scope; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ +public interface ScopedBindingBuilder { + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + void in(Class scopeAnnotation); + + /** + * See the EDSL examples at {@link com.google.inject.Binder}. + */ + void in(Scope scope); + + /** + * Instructs the {@link com.google.inject.Injector} to eagerly initialize this + * singleton-scoped binding upon creation. Useful for application + * initialization logic. See the EDSL examples at + * {@link com.google.inject.Binder}. + */ + void asEagerSingleton(); +} diff --git a/src/main/java/com/google/inject/internal/AbstractBindingBuilder.java b/src/main/java/com/google/inject/internal/AbstractBindingBuilder.java new file mode 100644 index 0000000..789a6df --- /dev/null +++ b/src/main/java/com/google/inject/internal/AbstractBindingBuilder.java @@ -0,0 +1,118 @@ +package com.google.inject.internal; + +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.Scope; +import com.google.inject.spi.Element; +import com.google.inject.spi.InstanceBinding; + +import java.lang.annotation.Annotation; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Bind a value or constant. + * + */ +public abstract class AbstractBindingBuilder { + + public static final String IMPLEMENTATION_ALREADY_SET = "Implementation is set more than once."; + public static final String SINGLE_INSTANCE_AND_SCOPE + = "Setting the scope is not permitted when binding to a single instance."; + public static final String SCOPE_ALREADY_SET = "Scope is set more than once."; + public static final String BINDING_TO_NULL = "Binding to null instances is not allowed. " + + "Use toProvider(Providers.of(null)) if this is your intended behaviour."; + public static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more than once."; + public static final String ANNOTATION_ALREADY_SPECIFIED + = "More than one annotation is specified for this binding."; + + protected static final Key NULL_KEY = Key.get(Void.class); + protected final Binder binder; + protected List elements; + protected int position; + private BindingImpl binding; + + public AbstractBindingBuilder(Binder binder, List elements, Object source, Key key) { + this.binder = binder; + this.elements = elements; + this.position = elements.size(); + this.binding = new UntargettedBindingImpl(source, key, Scoping.UNSCOPED); + elements.add(position, this.binding); + } + + protected BindingImpl getBinding() { + return binding; + } + + protected BindingImpl setBinding(BindingImpl binding) { + this.binding = binding; + elements.set(position, binding); + return binding; + } + + /** + * Sets the binding to a copy with the specified annotation on the bound key + */ + protected BindingImpl annotatedWithInternal(Class annotationType) { + checkNotNull(annotationType, "annotationType"); + checkNotAnnotated(); + return setBinding(binding.withKey( + Key.get(this.binding.getKey().getTypeLiteral(), annotationType))); + } + + /** + * Sets the binding to a copy with the specified annotation on the bound key + */ + protected BindingImpl annotatedWithInternal(Annotation annotation) { + checkNotNull(annotation, "annotation"); + checkNotAnnotated(); + return setBinding(binding.withKey( + Key.get(this.binding.getKey().getTypeLiteral(), annotation))); + } + + public void in(final Class scopeAnnotation) { + checkNotNull(scopeAnnotation, "scopeAnnotation"); + checkNotScoped(); + setBinding(getBinding().withScoping(Scoping.forAnnotation(scopeAnnotation))); + } + + public void in(final Scope scope) { + checkNotNull(scope, "scope"); + checkNotScoped(); + setBinding(getBinding().withScoping(Scoping.forInstance(scope))); + } + + public void asEagerSingleton() { + checkNotScoped(); + setBinding(getBinding().withScoping(Scoping.EAGER_SINGLETON)); + } + + protected boolean keyTypeIsSet() { + return !Void.class.equals(binding.getKey().getTypeLiteral().getType()); + } + + protected void checkNotTargetted() { + if (!(binding instanceof UntargettedBindingImpl)) { + binder.addError(IMPLEMENTATION_ALREADY_SET); + } + } + + protected void checkNotAnnotated() { + if (binding.getKey().getAnnotationType() != null) { + binder.addError(ANNOTATION_ALREADY_SPECIFIED); + } + } + + protected void checkNotScoped() { + // Scoping isn't allowed when we have only one instance. + if (binding instanceof InstanceBinding) { + binder.addError(SINGLE_INSTANCE_AND_SCOPE); + return; + } + + if (binding.getScoping().isExplicitlyScoped()) { + binder.addError(SCOPE_ALREADY_SET); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java b/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java new file mode 100644 index 0000000..c21b01c --- /dev/null +++ b/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java @@ -0,0 +1,146 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.DefaultBindingTargetVisitor; + +import java.util.Set; + +/** + * Guarantees that processing of Binding elements happens in a sane way. + */ +abstract class AbstractBindingProcessor extends AbstractProcessor { + + // It's unfortunate that we have to maintain a blacklist of specific + // classes, but we can't easily block the whole package because of + // all our unit tests. + private static final Set> FORBIDDEN_TYPES = ImmutableSet.>of( + AbstractModule.class, + Binder.class, + Binding.class, + Injector.class, + Key.class, + MembersInjector.class, + Module.class, + Provider.class, + Scope.class, + Stage.class, + TypeLiteral.class); + + protected final ProcessedBindingData bindingData; + + AbstractBindingProcessor(Errors errors, ProcessedBindingData bindingData) { + super(errors); + this.bindingData = bindingData; + } + + protected UntargettedBindingImpl invalidBinding( + InjectorImpl injector, Key key, Object source) { + return new UntargettedBindingImpl(injector, key, source); + } + + protected void putBinding(BindingImpl binding) { + Key key = binding.getKey(); + + Class rawType = key.getTypeLiteral().getRawType(); + if (FORBIDDEN_TYPES.contains(rawType)) { + errors.cannotBindToGuiceType(rawType.getSimpleName()); + return; + } + + BindingImpl original = injector.getExistingBinding(key); + if (original != null) { + // If it failed because of an explicit duplicate binding... + if (injector.state.getExplicitBinding(key) != null) { + try { + if (!isOkayDuplicate(original, binding, injector.state)) { + errors.bindingAlreadySet(key, original.getSource()); + return; + } + } catch (Throwable t) { + errors.errorCheckingDuplicateBinding(key, original.getSource(), t); + return; + } + } else { + // Otherwise, it failed because of a duplicate JIT binding + // in the parent + errors.jitBindingAlreadySet(key); + return; + } + } + + // prevent the parent from creating a JIT binding for this key + injector.state.parent().blacklist(key, injector.state, binding.getSource()); + injector.state.putBinding(key, binding); + } + + /** + * We tolerate duplicate bindings if one exposes the other or if the two bindings + * are considered duplicates. + * + * @param original the binding in the parent injector (candidate for an exposing binding) + * @param binding the binding to check (candidate for the exposed binding) + */ + private boolean isOkayDuplicate(BindingImpl original, BindingImpl binding, State state) { + if (original instanceof ExposedBindingImpl) { + ExposedBindingImpl exposed = (ExposedBindingImpl) original; + InjectorImpl exposedFrom = (InjectorImpl) exposed.getPrivateElements().getInjector(); + return (exposedFrom == binding.getInjector()); + } else { + original = (BindingImpl) state.getExplicitBindingsThisLevel().get(binding.getKey()); + // If no original at this level, the original was on a parent, and we don't + // allow deduplication between parents & children. + return original != null && original.equals(binding); + } + } + + private void validateKey(Object source, Key key) { + Annotations.checkForMisplacedScopeAnnotations( + key.getTypeLiteral().getRawType(), source, errors); + } + + /** + * Processor for visiting bindings. Each overriden method that wants to + * actually process the binding should call prepareBinding first. + */ + abstract class Processor extends DefaultBindingTargetVisitor { + final Object source; + final Key key; + final Class rawType; + Scoping scoping; + + Processor(BindingImpl binding) { + source = binding.getSource(); + key = binding.getKey(); + rawType = key.getTypeLiteral().getRawType(); + scoping = binding.getScoping(); + } + + protected void prepareBinding() { + validateKey(source, key); + scoping = Scoping.makeInjectable(scoping, injector, errors); + } + + protected void scheduleInitialization(final BindingImpl binding) { + bindingData.addUninitializedBinding(new Runnable() { + public void run() { + try { + binding.getInjector().initializeBinding(binding, errors.withSource(source)); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + }); + } + } +} diff --git a/src/main/java/com/google/inject/internal/AbstractProcessor.java b/src/main/java/com/google/inject/internal/AbstractProcessor.java new file mode 100644 index 0000000..e36bae7 --- /dev/null +++ b/src/main/java/com/google/inject/internal/AbstractProcessor.java @@ -0,0 +1,54 @@ +package com.google.inject.internal; + +import com.google.inject.spi.DefaultElementVisitor; +import com.google.inject.spi.Element; + +import java.util.Iterator; +import java.util.List; + +/** + * Abstract base class for creating an injector from module elements. + * + *

Extending classes must return {@code true} from any overridden + * {@code visit*()} methods, in order for the element processor to remove the + * handled element. + * + */ +abstract class AbstractProcessor extends DefaultElementVisitor { + + protected Errors errors; + protected InjectorImpl injector; + + protected AbstractProcessor(Errors errors) { + this.errors = errors; + } + + public void process(Iterable isolatedInjectorBuilders) { + for (InjectorShell injectorShell : isolatedInjectorBuilders) { + process(injectorShell.getInjector(), injectorShell.getElements()); + } + } + + public void process(InjectorImpl injector, List elements) { + Errors errorsAnyElement = this.errors; + this.injector = injector; + try { + for (Iterator i = elements.iterator(); i.hasNext(); ) { + Element element = i.next(); + this.errors = errorsAnyElement.withSource(element.getSource()); + Boolean allDone = element.acceptVisitor(this); + if (allDone) { + i.remove(); + } + } + } finally { + this.errors = errorsAnyElement; + this.injector = null; + } + } + + @Override + protected Boolean visitOther(Element element) { + return false; + } +} diff --git a/src/main/java/com/google/inject/internal/Annotations.java b/src/main/java/com/google/inject/internal/Annotations.java new file mode 100644 index 0000000..0c9e27a --- /dev/null +++ b/src/main/java/com/google/inject/internal/Annotations.java @@ -0,0 +1,334 @@ +package com.google.inject.internal; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Joiner.MapJoiner; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.inject.BindingAnnotation; +import com.google.inject.Key; +import com.google.inject.ScopeAnnotation; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.util.Classes; +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import javax.inject.Qualifier; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +/** + * Annotation utilities. + * + */ +public class Annotations { + + private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("="); + private static final Function DEEP_TO_STRING_FN = new Function() { + @Override + public String apply(Object arg) { + String s = Arrays.deepToString(new Object[]{arg}); + return s.substring(1, s.length() - 1); // cut off brackets + } + }; + private static final LoadingCache, Annotation> cache = + CacheBuilder.newBuilder().weakKeys().build( + new CacheLoader, Annotation>() { + @Override + public Annotation load(Class input) { + return generateAnnotationImpl(input); + } + }); + private static final AnnotationChecker scopeChecker = new AnnotationChecker( + Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class)); + private static final AnnotationChecker bindingAnnotationChecker = new AnnotationChecker( + Arrays.asList(BindingAnnotation.class, Qualifier.class)); + + /** + * Returns {@code true} if the given annotation type has no attributes. + */ + public static boolean isMarker(Class annotationType) { + return annotationType.getDeclaredMethods().length == 0; + } + + public static boolean isAllDefaultMethods(Class annotationType) { + boolean hasMethods = false; + for (Method m : annotationType.getDeclaredMethods()) { + hasMethods = true; + if (m.getDefaultValue() == null) { + return false; + } + } + return hasMethods; + } + + /** + * Generates an Annotation for the annotation class. Requires that the annotation is all + * optionals. + */ + @SuppressWarnings("unchecked") + public static T generateAnnotation(Class annotationType) { + Preconditions.checkState( + isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType); + return (T) cache.getUnchecked(annotationType); + } + + private static T generateAnnotationImpl(final Class annotationType) { + final Map members = resolveMembers(annotationType); + return annotationType.cast(Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Exception { + String name = method.getName(); + if (name.equals("annotationType")) { + return annotationType; + } else if (name.equals("toString")) { + return annotationToString(annotationType, members); + } else if (name.equals("hashCode")) { + return annotationHashCode(annotationType, members); + } else if (name.equals("equals")) { + return annotationEquals(annotationType, members, args[0]); + } else { + return members.get(name); + } + } + })); + } + + private static ImmutableMap resolveMembers( + Class annotationType) { + ImmutableMap.Builder result = ImmutableMap.builder(); + for (Method method : annotationType.getDeclaredMethods()) { + result.put(method.getName(), method.getDefaultValue()); + } + return result.build(); + } + + /** + * Implements {@link Annotation#equals}. + */ + private static boolean annotationEquals(Class type, + Map members, Object other) throws Exception { + if (!type.isInstance(other)) { + return false; + } + for (Method method : type.getDeclaredMethods()) { + String name = method.getName(); + if (!Arrays.deepEquals( + new Object[]{method.invoke(other)}, new Object[]{members.get(name)})) { + return false; + } + } + return true; + } + + /** + * Implements {@link Annotation#hashCode}. + */ + private static int annotationHashCode(Class type, + Map members) throws Exception { + int result = 0; + for (Method method : type.getDeclaredMethods()) { + String name = method.getName(); + Object value = members.get(name); + result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new Object[]{value}) - 31); + } + return result; + } + + /** + * Implements {@link Annotation#toString}. + */ + private static String annotationToString(Class type, + Map members) throws Exception { + StringBuilder sb = new StringBuilder().append("@").append(type.getName()).append("("); + JOINER.appendTo(sb, Maps.transformValues(members, DEEP_TO_STRING_FN)); + return sb.append(")").toString(); + } + + /** + * Returns true if the given annotation is retained at runtime. + */ + public static boolean isRetainedAtRuntime(Class annotationType) { + Retention retention = annotationType.getAnnotation(Retention.class); + return retention != null && retention.value() == RetentionPolicy.RUNTIME; + } + + /** + * Returns the scope annotation on {@code type}, or null if none is specified. + */ + public static Class findScopeAnnotation( + Errors errors, Class implementation) { + return findScopeAnnotation(errors, implementation.getAnnotations()); + } + + /** + * Returns the scoping annotation, or null if there isn't one. + */ + public static Class findScopeAnnotation(Errors errors, Annotation[] annotations) { + Class found = null; + + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (isScopeAnnotation(annotationType)) { + if (found != null) { + errors.duplicateScopeAnnotations(found, annotationType); + } else { + found = annotationType; + } + } + } + + return found; + } + + static boolean containsComponentAnnotation(Annotation[] annotations) { + for (Annotation annotation : annotations) { + // TODO(user): Should we scope this down to dagger.Component? + if (annotation.annotationType().getSimpleName().equals("Component")) { + return true; + } + } + + return false; + } + + public static boolean isScopeAnnotation(Class annotationType) { + return scopeChecker.hasAnnotations(annotationType); + } + + /** + * Adds an error if there is a misplaced annotations on {@code type}. Scoping + * annotations are not allowed on abstract classes or interfaces. + */ + public static void checkForMisplacedScopeAnnotations( + Class type, Object source, Errors errors) { + if (Classes.isConcrete(type)) { + return; + } + + Class scopeAnnotation = findScopeAnnotation(errors, type); + if (scopeAnnotation != null + // We let Dagger Components through to aid migrations. + && !containsComponentAnnotation(type.getAnnotations())) { + errors.withSource(type).scopeAnnotationOnAbstractType(scopeAnnotation, type, source); + } + } + + /** + * Gets a key for the given type, member and annotations. + */ + public static Key getKey(TypeLiteral type, Member member, Annotation[] annotations, + Errors errors) throws ErrorsException { + int numErrorsBefore = errors.size(); + Annotation found = findBindingAnnotation(errors, member, annotations); + errors.throwIfNewErrors(numErrorsBefore); + return found == null ? Key.get(type) : Key.get(type, found); + } + + /** + * Returns the binding annotation on {@code member}, or null if there isn't one. + */ + public static Annotation findBindingAnnotation( + Errors errors, Member member, Annotation[] annotations) { + Annotation found = null; + + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (isBindingAnnotation(annotationType)) { + if (found != null) { + errors.duplicateBindingAnnotations(member, found.annotationType(), annotationType); + } else { + found = annotation; + } + } + } + + return found; + } + + /** + * Returns true if annotations of the specified type are binding annotations. + */ + public static boolean isBindingAnnotation(Class annotationType) { + return bindingAnnotationChecker.hasAnnotations(annotationType); + } + + /** + * If the annotation is an instance of {@code javax.inject.Named}, canonicalizes to + * com.google.guice.name.Named. Returns the given annotation otherwise. + */ + public static Annotation canonicalizeIfNamed(Annotation annotation) { + if (annotation instanceof javax.inject.Named) { + return Names.named(((javax.inject.Named) annotation).value()); + } else { + return annotation; + } + } + + /** + * If the annotation is the class {@code javax.inject.Named}, canonicalizes to + * com.google.guice.name.Named. Returns the given annotation class otherwise. + */ + public static Class canonicalizeIfNamed( + Class annotationType) { + if (annotationType == javax.inject.Named.class) { + return Named.class; + } else { + return annotationType; + } + } + + /** + * Checks for the presence of annotations. Caches results because Android doesn't. + */ + static class AnnotationChecker { + private final Collection> annotationTypes; + + /** + * Returns true if the given class has one of the desired annotations. + */ + private CacheLoader, Boolean> hasAnnotations = + new CacheLoader, Boolean>() { + public Boolean load(Class annotationType) { + for (Annotation annotation : annotationType.getAnnotations()) { + if (annotationTypes.contains(annotation.annotationType())) { + return true; + } + } + return false; + } + }; + + final LoadingCache, Boolean> cache = CacheBuilder.newBuilder().weakKeys() + .build(hasAnnotations); + + /** + * Constructs a new checker that looks for annotations of the given types. + */ + AnnotationChecker(Collection> annotationTypes) { + this.annotationTypes = annotationTypes; + } + + /** + * Returns true if the given type has one of the desired annotations. + */ + boolean hasAnnotations(Class annotated) { + return cache.getUnchecked(annotated); + } + } +} diff --git a/src/main/java/com/google/inject/internal/BindingBuilder.java b/src/main/java/com/google/inject/internal/BindingBuilder.java new file mode 100644 index 0000000..aefe976 --- /dev/null +++ b/src/main/java/com/google/inject/internal/BindingBuilder.java @@ -0,0 +1,167 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.ConfigurationException; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.ScopedBindingBuilder; +import com.google.inject.spi.Element; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.Message; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Bind a non-constant key. + */ +public class BindingBuilder extends AbstractBindingBuilder + implements AnnotatedBindingBuilder { + + public BindingBuilder(Binder binder, List elements, Object source, Key key) { + super(binder, elements, source, key); + } + + public BindingBuilder annotatedWith(Class annotationType) { + annotatedWithInternal(annotationType); + return this; + } + + public BindingBuilder annotatedWith(Annotation annotation) { + annotatedWithInternal(annotation); + return this; + } + + public BindingBuilder to(Class implementation) { + return to(Key.get(implementation)); + } + + public BindingBuilder to(TypeLiteral implementation) { + return to(Key.get(implementation)); + } + + public BindingBuilder to(Key linkedKey) { + checkNotNull(linkedKey, "linkedKey"); + checkNotTargetted(); + BindingImpl base = getBinding(); + setBinding(new LinkedBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), linkedKey)); + return this; + } + + public void toInstance(T instance) { + checkNotTargetted(); + + // lookup the injection points, adding any errors to the binder's errors list + Set injectionPoints; + if (instance != null) { + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(instance.getClass()); + } catch (ConfigurationException e) { + copyErrorsToBinder(e); + injectionPoints = e.getPartialValue(); + } + } else { + binder.addError(BINDING_TO_NULL); + injectionPoints = ImmutableSet.of(); + } + + BindingImpl base = getBinding(); + setBinding(new InstanceBindingImpl( + base.getSource(), base.getKey(), Scoping.EAGER_SINGLETON, injectionPoints, instance)); + } + + @SuppressWarnings("unchecked") + public BindingBuilder toProvider(Provider provider) { + return toProvider((javax.inject.Provider) provider); + } + + public BindingBuilder toProvider(javax.inject.Provider provider) { + checkNotNull(provider, "provider"); + checkNotTargetted(); + + // lookup the injection points, adding any errors to the binder's errors list + Set injectionPoints; + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(provider.getClass()); + } catch (ConfigurationException e) { + copyErrorsToBinder(e); + injectionPoints = e.getPartialValue(); + } + + BindingImpl base = getBinding(); + setBinding(new ProviderInstanceBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), injectionPoints, provider)); + return this; + } + + public BindingBuilder toProvider( + Class> providerType) { + return toProvider(Key.get(providerType)); + } + + public BindingBuilder toProvider( + TypeLiteral> providerType) { + return toProvider(Key.get(providerType)); + } + + public BindingBuilder toProvider( + Key> providerKey) { + checkNotNull(providerKey, "providerKey"); + checkNotTargetted(); + + BindingImpl base = getBinding(); + setBinding(new LinkedProviderBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), providerKey)); + return this; + } + + public ScopedBindingBuilder toConstructor(Constructor constructor) { + return toConstructor(constructor, TypeLiteral.get(constructor.getDeclaringClass())); + } + + public ScopedBindingBuilder toConstructor(Constructor constructor, + TypeLiteral type) { + checkNotNull(constructor, "constructor"); + checkNotNull(type, "type"); + checkNotTargetted(); + + BindingImpl base = getBinding(); + + Set injectionPoints; + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(type); + } catch (ConfigurationException e) { + copyErrorsToBinder(e); + injectionPoints = e.getPartialValue(); + } + + try { + InjectionPoint constructorPoint = InjectionPoint.forConstructor(constructor, type); + setBinding(new ConstructorBindingImpl(base.getKey(), base.getSource(), base.getScoping(), + constructorPoint, injectionPoints)); + } catch (ConfigurationException e) { + copyErrorsToBinder(e); + } + + return this; + } + + @Override + public String toString() { + return "BindingBuilder<" + getBinding().getKey().getTypeLiteral() + ">"; + } + + private void copyErrorsToBinder(ConfigurationException e) { + for (Message message : e.getErrorMessages()) { + binder.addError(message); + } + } +} diff --git a/src/main/java/com/google/inject/internal/BindingImpl.java b/src/main/java/com/google/inject/internal/BindingImpl.java new file mode 100644 index 0000000..6a9ed1b --- /dev/null +++ b/src/main/java/com/google/inject/internal/BindingImpl.java @@ -0,0 +1,100 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.spi.BindingScopingVisitor; +import com.google.inject.spi.ElementVisitor; +import com.google.inject.spi.InstanceBinding; + +public abstract class BindingImpl implements Binding { + + private final InjectorImpl injector; + private final Key key; + private final Object source; + private final Scoping scoping; + private final InternalFactory internalFactory; + private volatile Provider provider; + + public BindingImpl(InjectorImpl injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping) { + this.injector = injector; + this.key = key; + this.source = source; + this.internalFactory = internalFactory; + this.scoping = scoping; + } + + protected BindingImpl(Object source, Key key, Scoping scoping) { + this.internalFactory = null; + this.injector = null; + this.source = source; + this.key = key; + this.scoping = scoping; + } + + public Key getKey() { + return key; + } + + public Object getSource() { + return source; + } + + public Provider getProvider() { + if (provider == null) { + if (injector == null) { + throw new UnsupportedOperationException("getProvider() not supported for module bindings"); + } + + provider = injector.getProvider(key); + } + return provider; + } + + public InternalFactory getInternalFactory() { + return internalFactory; + } + + public Scoping getScoping() { + return scoping; + } + + /** + * Is this a constant binding? This returns true for constant bindings as + * well as toInstance() bindings. + */ + public boolean isConstant() { + return this instanceof InstanceBinding; + } + + public V acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public V acceptScopingVisitor(BindingScopingVisitor visitor) { + return scoping.acceptVisitor(visitor); + } + + protected BindingImpl withScoping(Scoping scoping) { + throw new AssertionError(); + } + + protected BindingImpl withKey(Key key) { + throw new AssertionError(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(Binding.class) + .add("key", key) + .add("scope", scoping) + .add("source", source) + .toString(); + } + + public InjectorImpl getInjector() { + return injector; + } +} diff --git a/src/main/java/com/google/inject/internal/BindingProcessor.java b/src/main/java/com/google/inject/internal/BindingProcessor.java new file mode 100644 index 0000000..2d4c067 --- /dev/null +++ b/src/main/java/com/google/inject/internal/BindingProcessor.java @@ -0,0 +1,180 @@ +package com.google.inject.internal; + +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.spi.ConstructorBinding; +import com.google.inject.spi.ConvertedConstantBinding; +import com.google.inject.spi.ExposedBinding; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.InstanceBinding; +import com.google.inject.spi.LinkedKeyBinding; +import com.google.inject.spi.PrivateElements; +import com.google.inject.spi.ProviderBinding; +import com.google.inject.spi.ProviderInstanceBinding; +import com.google.inject.spi.ProviderKeyBinding; +import com.google.inject.spi.UntargettedBinding; + +import java.util.Set; + +/** + * Handles {@link Binder#bind} and {@link Binder#bindConstant} elements. + */ +final class BindingProcessor extends AbstractBindingProcessor { + + private final Initializer initializer; + + BindingProcessor(Errors errors, Initializer initializer, ProcessedBindingData bindingData) { + super(errors, bindingData); + this.initializer = initializer; + } + + @Override + public Boolean visit(Binding command) { + Class rawType = command.getKey().getTypeLiteral().getRawType(); + if (Void.class.equals(rawType)) { + if (command instanceof ProviderInstanceBinding + && ((ProviderInstanceBinding) command).getUserSuppliedProvider() + instanceof ProviderMethod) { + errors.voidProviderMethod(); + } else { + errors.missingConstantValues(); + } + return true; + } + + if (rawType == Provider.class) { + errors.bindingToProvider(); + return true; + } + + return command.acceptTargetVisitor(new Processor((BindingImpl) command) { + @Override + public Boolean visit(ConstructorBinding binding) { + prepareBinding(); + try { + ConstructorBindingImpl onInjector = ConstructorBindingImpl.create(injector, key, + binding.getConstructor(), source, scoping, errors, false, false); + scheduleInitialization(onInjector); + putBinding(onInjector); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + putBinding(invalidBinding(injector, key, source)); + } + return true; + } + + @Override + public Boolean visit(InstanceBinding binding) { + prepareBinding(); + Set injectionPoints = binding.getInjectionPoints(); + T instance = binding.getInstance(); + @SuppressWarnings("unchecked") // safe to cast to binding because + // the processor was constructed w/ it + Initializable ref = initializer.requestInjection( + injector, instance, (Binding) binding, source, injectionPoints); + ConstantFactory factory = new ConstantFactory(ref); + InternalFactory scopedFactory + = Scoping.scope(key, injector, factory, source, scoping); + putBinding(new InstanceBindingImpl(injector, key, source, scopedFactory, injectionPoints, + instance)); + return true; + } + + @Override + public Boolean visit(ProviderInstanceBinding binding) { + prepareBinding(); + javax.inject.Provider provider = binding.getUserSuppliedProvider(); + Set injectionPoints = binding.getInjectionPoints(); + Initializable> initializable = + initializer.>requestInjection( + injector, provider, null, source, injectionPoints); + // always visited with Binding + @SuppressWarnings("unchecked") + InternalFactory factory = new InternalFactoryToInitializableAdapter( + initializable, source, + injector.provisionListenerStore.get((ProviderInstanceBinding) binding)); + InternalFactory scopedFactory + = Scoping.scope(key, injector, factory, source, scoping); + putBinding(new ProviderInstanceBindingImpl(injector, key, source, scopedFactory, scoping, + provider, injectionPoints)); + return true; + } + + @Override + public Boolean visit(ProviderKeyBinding binding) { + prepareBinding(); + Key> providerKey = binding.getProviderKey(); + // always visited with Binding + @SuppressWarnings("unchecked") + BoundProviderFactory boundProviderFactory = new BoundProviderFactory( + injector, providerKey, source, + injector.provisionListenerStore.get((ProviderKeyBinding) binding)); + bindingData.addCreationListener(boundProviderFactory); + InternalFactory scopedFactory = Scoping.scope( + key, injector, (InternalFactory) boundProviderFactory, source, scoping); + putBinding(new LinkedProviderBindingImpl( + injector, key, source, scopedFactory, scoping, providerKey)); + return true; + } + + @Override + public Boolean visit(LinkedKeyBinding binding) { + prepareBinding(); + Key linkedKey = binding.getLinkedKey(); + if (key.equals(linkedKey)) { + errors.recursiveBinding(); + } + + FactoryProxy factory = new FactoryProxy(injector, key, linkedKey, source); + bindingData.addCreationListener(factory); + InternalFactory scopedFactory + = Scoping.scope(key, injector, factory, source, scoping); + putBinding( + new LinkedBindingImpl(injector, key, source, scopedFactory, scoping, linkedKey)); + return true; + } + + @Override + public Boolean visit(UntargettedBinding untargetted) { + return false; + } + + @Override + public Boolean visit(ExposedBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + @Override + public Boolean visit(ConvertedConstantBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + @Override + public Boolean visit(ProviderBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + @Override + protected Boolean visitOther(Binding binding) { + throw new IllegalStateException("BindingProcessor should override all visitations"); + } + }); + } + + @Override + public Boolean visit(PrivateElements privateElements) { + for (Key key : privateElements.getExposedKeys()) { + bindExposed(privateElements, key); + } + return false; // leave the private elements for the PrivateElementsProcessor to handle + } + + private void bindExposed(PrivateElements privateElements, Key key) { + ExposedKeyFactory exposedKeyFactory = new ExposedKeyFactory(key, privateElements); + bindingData.addCreationListener(exposedKeyFactory); + putBinding(new ExposedBindingImpl( + injector, privateElements.getExposedSource(key), key, exposedKeyFactory, privateElements)); + } +} diff --git a/src/main/java/com/google/inject/internal/BoundProviderFactory.java b/src/main/java/com/google/inject/internal/BoundProviderFactory.java new file mode 100644 index 0000000..528f8d4 --- /dev/null +++ b/src/main/java/com/google/inject/internal/BoundProviderFactory.java @@ -0,0 +1,66 @@ +package com.google.inject.internal; + +import com.google.inject.Key; +import com.google.inject.internal.InjectorImpl.JitLimitation; +import com.google.inject.spi.Dependency; + +import javax.inject.Provider; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Delegates to a custom factory which is also bound in the injector. + */ +final class BoundProviderFactory extends ProviderInternalFactory implements CreationListener { + + final Key> providerKey; + private final ProvisionListenerStackCallback provisionCallback; + private final InjectorImpl injector; + private InternalFactory> providerFactory; + + BoundProviderFactory( + InjectorImpl injector, + Key> providerKey, + Object source, + ProvisionListenerStackCallback provisionCallback) { + super(source); + this.provisionCallback = checkNotNull(provisionCallback, "provisionCallback"); + this.injector = injector; + this.providerKey = providerKey; + } + + public void notify(Errors errors) { + try { + providerFactory = injector.getInternalFactory(providerKey, errors.withSource(source), JitLimitation.NEW_OR_EXISTING_JIT); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + context.pushState(providerKey, source); + try { + errors = errors.withSource(providerKey); + javax.inject.Provider provider = providerFactory.get(errors, context, dependency, true); + return circularGet(provider, errors, context, dependency, provisionCallback); + } finally { + context.popState(); + } + } + + @Override + protected T provision(Provider provider, Errors errors, Dependency dependency, + ConstructionContext constructionContext) throws ErrorsException { + try { + return super.provision(provider, errors, dependency, constructionContext); + } catch (RuntimeException userException) { + throw errors.errorInProvider(userException).toException(); + } + } + + @Override + public String toString() { + return providerKey.toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/CircularDependencyProxy.java b/src/main/java/com/google/inject/internal/CircularDependencyProxy.java new file mode 100644 index 0000000..46aeda1 --- /dev/null +++ b/src/main/java/com/google/inject/internal/CircularDependencyProxy.java @@ -0,0 +1,5 @@ +package com.google.inject.internal; + +public interface CircularDependencyProxy { + // marker interface +} diff --git a/src/main/java/com/google/inject/internal/ConstantBindingBuilderImpl.java b/src/main/java/com/google/inject/internal/ConstantBindingBuilderImpl.java new file mode 100644 index 0000000..22a13dd --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstantBindingBuilderImpl.java @@ -0,0 +1,115 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.binder.AnnotatedConstantBindingBuilder; +import com.google.inject.binder.ConstantBindingBuilder; +import com.google.inject.spi.Element; +import com.google.inject.spi.InjectionPoint; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * Bind a constant. + * + */ +public final class ConstantBindingBuilderImpl + extends AbstractBindingBuilder + implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder { + + @SuppressWarnings("unchecked") // constant bindings start out with T unknown + public ConstantBindingBuilderImpl(Binder binder, List elements, Object source) { + super(binder, elements, source, (Key) NULL_KEY); + } + + public ConstantBindingBuilder annotatedWith(Class annotationType) { + annotatedWithInternal(annotationType); + return this; + } + + public ConstantBindingBuilder annotatedWith(Annotation annotation) { + annotatedWithInternal(annotation); + return this; + } + + public void to(final String value) { + toConstant(String.class, value); + } + + public void to(final int value) { + toConstant(Integer.class, value); + } + + public void to(final long value) { + toConstant(Long.class, value); + } + + public void to(final boolean value) { + toConstant(Boolean.class, value); + } + + public void to(final double value) { + toConstant(Double.class, value); + } + + public void to(final float value) { + toConstant(Float.class, value); + } + + public void to(final short value) { + toConstant(Short.class, value); + } + + public void to(final char value) { + toConstant(Character.class, value); + } + + public void to(final byte value) { + toConstant(Byte.class, value); + } + + public void to(final Class value) { + toConstant(Class.class, value); + } + + public > void to(final E value) { + toConstant(value.getDeclaringClass(), value); + } + + private void toConstant(Class type, Object instance) { + // this type will define T, so these assignments are safe + @SuppressWarnings("unchecked") + Class typeAsClassT = (Class) type; + @SuppressWarnings("unchecked") + T instanceAsT = (T) instance; + + if (keyTypeIsSet()) { + binder.addError(CONSTANT_VALUE_ALREADY_SET); + return; + } + + BindingImpl base = getBinding(); + Key key; + if (base.getKey().getAnnotation() != null) { + key = Key.get(typeAsClassT, base.getKey().getAnnotation()); + } else if (base.getKey().getAnnotationType() != null) { + key = Key.get(typeAsClassT, base.getKey().getAnnotationType()); + } else { + key = Key.get(typeAsClassT); + } + + if (instanceAsT == null) { + binder.addError(BINDING_TO_NULL); + } + + setBinding(new InstanceBindingImpl( + base.getSource(), key, base.getScoping(), ImmutableSet.of(), instanceAsT)); + } + + @Override + public String toString() { + return "ConstantBindingBuilder"; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/ConstantFactory.java b/src/main/java/com/google/inject/internal/ConstantFactory.java new file mode 100644 index 0000000..2c18513 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstantFactory.java @@ -0,0 +1,24 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.inject.spi.Dependency; + +final class ConstantFactory implements InternalFactory { + + private final Initializable initializable; + + public ConstantFactory(Initializable initializable) { + this.initializable = initializable; + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + return initializable.get(errors); + } + + public String toString() { + return MoreObjects.toStringHelper(ConstantFactory.class) + .add("value", initializable) + .toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/ConstructionContext.java b/src/main/java/com/google/inject/internal/ConstructionContext.java new file mode 100644 index 0000000..a2dbbc8 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstructionContext.java @@ -0,0 +1,75 @@ +package com.google.inject.internal; + +import com.google.inject.internal.InjectorImpl.InjectorOptions; + +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +/** + * Context of a dependency construction. Used to manage circular references. + */ +final class ConstructionContext { + + T currentReference; + boolean constructing; + + List> invocationHandlers; + + public T getCurrentReference() { + return currentReference; + } + + public void setCurrentReference(T currentReference) { + this.currentReference = currentReference; + } + + public void removeCurrentReference() { + this.currentReference = null; + } + + public boolean isConstructing() { + return constructing; + } + + public void startConstruction() { + this.constructing = true; + } + + public void finishConstruction() { + this.constructing = false; + invocationHandlers = null; + } + + public Object createProxy(Errors errors, InjectorOptions injectorOptions, + Class expectedType) throws ErrorsException { + if (injectorOptions.disableCircularProxies) { + throw errors.circularProxiesDisabled(expectedType).toException(); + } + if (!expectedType.isInterface()) { + throw errors.cannotSatisfyCircularDependency(expectedType).toException(); + } + + if (invocationHandlers == null) { + invocationHandlers = new ArrayList>(); + } + + DelegatingInvocationHandler invocationHandler = new DelegatingInvocationHandler(); + invocationHandlers.add(invocationHandler); + + ClassLoader classLoader = expectedType.getClass().getClassLoader() != null ? + expectedType.getClass().getClassLoader() : ClassLoader.getSystemClassLoader(); + return expectedType.cast(Proxy.newProxyInstance(classLoader, + new Class[]{expectedType, CircularDependencyProxy.class}, invocationHandler)); + } + + public void setProxyDelegates(T delegate) { + if (invocationHandlers != null) { + for (DelegatingInvocationHandler handler : invocationHandlers) { + handler.setDelegate(delegate); + } + // initialization of each handler can happen no more than once + invocationHandlers = null; + } + } +} diff --git a/src/main/java/com/google/inject/internal/ConstructionProxy.java b/src/main/java/com/google/inject/internal/ConstructionProxy.java new file mode 100644 index 0000000..d4c0f09 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstructionProxy.java @@ -0,0 +1,30 @@ +package com.google.inject.internal; + +import com.google.inject.spi.InjectionPoint; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Proxies calls to a {@link java.lang.reflect.Constructor} for a class + * {@code T}. + */ +interface ConstructionProxy { + + /** + * Constructs an instance of {@code T} for the given arguments. + */ + T newInstance(Object... arguments) throws InvocationTargetException; + + /** + * Returns the injection point for this constructor. + */ + InjectionPoint getInjectionPoint(); + + /** + * Returns the injected constructor. If the injected constructor is synthetic (such as generated + * code for method interception), the natural constructor is returned. + */ + Constructor getConstructor(); + +} diff --git a/src/main/java/com/google/inject/internal/ConstructionProxyFactory.java b/src/main/java/com/google/inject/internal/ConstructionProxyFactory.java new file mode 100644 index 0000000..5151326 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstructionProxyFactory.java @@ -0,0 +1,12 @@ +package com.google.inject.internal; + +/** + * Creates {@link ConstructionProxy} instances. + */ +interface ConstructionProxyFactory { + + /** + * Gets a construction proxy for the given constructor. + */ + ConstructionProxy create() throws ErrorsException; +} diff --git a/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java b/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java new file mode 100644 index 0000000..d52f486 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java @@ -0,0 +1,258 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.ConfigurationException; +import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.util.Classes; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.ConstructorBinding; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.InjectionPoint; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.inject.internal.Annotations.findScopeAnnotation; + +final class ConstructorBindingImpl extends BindingImpl + implements ConstructorBinding, DelayedInitialize { + + private final Factory factory; + private final InjectionPoint constructorInjectionPoint; + + private ConstructorBindingImpl(InjectorImpl injector, Key key, Object source, + InternalFactory scopedFactory, Scoping scoping, Factory factory, + InjectionPoint constructorInjectionPoint) { + super(injector, key, source, scopedFactory, scoping); + this.factory = factory; + this.constructorInjectionPoint = constructorInjectionPoint; + } + + public ConstructorBindingImpl(Key key, Object source, Scoping scoping, + InjectionPoint constructorInjectionPoint, Set injectionPoints) { + super(source, key, scoping); + this.factory = new Factory(false, key); + ConstructionProxy constructionProxy + = new DefaultConstructionProxyFactory(constructorInjectionPoint).create(); + this.constructorInjectionPoint = constructorInjectionPoint; + factory.constructorInjector = new ConstructorInjector( + injectionPoints, constructionProxy, null, null); + } + + /** + * @param constructorInjector the constructor to use, or {@code null} to use the default. + * @param failIfNotLinked true if this ConstructorBindingImpl's InternalFactory should + * only succeed if retrieved from a linked binding + */ + static ConstructorBindingImpl create(InjectorImpl injector, Key key, + InjectionPoint constructorInjector, Object source, Scoping scoping, Errors errors, + boolean failIfNotLinked, boolean failIfNotExplicit) + throws ErrorsException { + int numErrors = errors.size(); + + @SuppressWarnings("unchecked") // constructorBinding guarantees type is consistent + Class rawType = constructorInjector == null + ? key.getTypeLiteral().getRawType() + : (Class) constructorInjector.getDeclaringType().getRawType(); + + // We can't inject abstract classes. + if (Modifier.isAbstract(rawType.getModifiers())) { + errors.missingImplementation(key); + } + + // Error: Inner class. + if (Classes.isInnerClass(rawType)) { + errors.cannotInjectInnerClass(rawType); + } + + errors.throwIfNewErrors(numErrors); + + // Find a constructor annotated @Inject + if (constructorInjector == null) { + try { + constructorInjector = InjectionPoint.forConstructorOf(key.getTypeLiteral()); + if (failIfNotExplicit && !hasAtInject((Constructor) constructorInjector.getMember())) { + errors.atInjectRequired(rawType); + } + } catch (ConfigurationException e) { + throw errors.merge(e.getErrorMessages()).toException(); + } + } + + // if no scope is specified, look for a scoping annotation on the concrete class + if (!scoping.isExplicitlyScoped()) { + Class annotatedType = constructorInjector.getMember().getDeclaringClass(); + Class scopeAnnotation = findScopeAnnotation(errors, annotatedType); + if (scopeAnnotation != null) { + scoping = Scoping.makeInjectable(Scoping.forAnnotation(scopeAnnotation), + injector, errors.withSource(rawType)); + } + } + + errors.throwIfNewErrors(numErrors); + + Factory factoryFactory = new Factory(failIfNotLinked, key); + InternalFactory scopedFactory + = Scoping.scope(key, injector, factoryFactory, source, scoping); + + return new ConstructorBindingImpl( + injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector); + } + + /** + * Returns true if the inject annotation is on the constructor. + */ + private static boolean hasAtInject(Constructor cxtor) { + return cxtor.isAnnotationPresent(Inject.class) + || cxtor.isAnnotationPresent(javax.inject.Inject.class); + } + + @SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type + public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { + factory.constructorInjector = + (ConstructorInjector) injector.constructors.get(constructorInjectionPoint, errors); + factory.provisionCallback = + injector.provisionListenerStore.get(this); + } + + /** + * True if this binding has been initialized and is ready for use. + */ + boolean isInitialized() { + return factory.constructorInjector != null; + } + + /** + * Returns an injection point that can be used to clean up the constructor store. + */ + InjectionPoint getInternalConstructor() { + if (factory.constructorInjector != null) { + return factory.constructorInjector.getConstructionProxy().getInjectionPoint(); + } else { + return constructorInjectionPoint; + } + } + + /** + * Returns a set of dependencies that can be iterated over to clean up stray JIT bindings. + */ + Set> getInternalDependencies() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + if (factory.constructorInjector == null) { + builder.add(constructorInjectionPoint); + // If the below throws, it's OK -- we just ignore those dependencies, because no one + // could have used them anyway. + try { + builder.addAll(InjectionPoint.forInstanceMethodsAndFields(constructorInjectionPoint.getDeclaringType())); + } catch (ConfigurationException ignored) { + } + } else { + builder.add(getConstructor()) + .addAll(getInjectableMembers()); + } + + return Dependency.forInjectionPoints(builder.build()); + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + checkState(factory.constructorInjector != null, "not initialized"); + return visitor.visit(this); + } + + public InjectionPoint getConstructor() { + checkState(factory.constructorInjector != null, "Binding is not ready"); + return factory.constructorInjector.getConstructionProxy().getInjectionPoint(); + } + + public Set getInjectableMembers() { + checkState(factory.constructorInjector != null, "Binding is not ready"); + return factory.constructorInjector.getInjectableMembers(); + } + + public Set> getDependencies() { + return Dependency.forInjectionPoints(new ImmutableSet.Builder() + .add(getConstructor()) + .addAll(getInjectableMembers()) + .build()); + } + + @Override + protected BindingImpl withScoping(Scoping scoping) { + return new ConstructorBindingImpl( + null, getKey(), getSource(), factory, scoping, factory, constructorInjectionPoint); + } + + @Override + protected BindingImpl withKey(Key key) { + return new ConstructorBindingImpl( + null, key, getSource(), factory, getScoping(), factory, constructorInjectionPoint); + } + + @SuppressWarnings("unchecked") // the raw constructor member and declaring type always agree + public void applyTo(Binder binder) { + InjectionPoint constructor = getConstructor(); + getScoping().applyTo(binder.withSource(getSource()).bind(getKey()).toConstructor( + (Constructor) getConstructor().getMember(), (TypeLiteral) constructor.getDeclaringType())); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ConstructorBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ConstructorBindingImpl) { + ConstructorBindingImpl o = (ConstructorBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(constructorInjectionPoint, o.constructorInjectionPoint); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping(), constructorInjectionPoint); + } + + private static class Factory implements InternalFactory { + private final boolean failIfNotLinked; + private final Key key; + private ConstructorInjector constructorInjector; + private ProvisionListenerStackCallback provisionCallback; + + Factory(boolean failIfNotLinked, Key key) { + this.failIfNotLinked = failIfNotLinked; + this.key = key; + } + + @SuppressWarnings("unchecked") + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + checkState(constructorInjector != null, "Constructor not ready"); + + if (failIfNotLinked && !linked) { + throw errors.jitDisabled(key).toException(); + } + + // This may not actually be safe because it could return a super type of T (if that's all the + // client needs), but it should be OK in practice thanks to the wonders of erasure. + return (T) constructorInjector.construct(errors, context, + dependency.getKey().getTypeLiteral().getRawType(), provisionCallback); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ConstructorInjector.java b/src/main/java/com/google/inject/internal/ConstructorInjector.java new file mode 100644 index 0000000..127f296 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstructorInjector.java @@ -0,0 +1,112 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.internal.ProvisionListenerStackCallback.ProvisionCallback; +import com.google.inject.spi.InjectionPoint; + +import java.lang.reflect.InvocationTargetException; +import java.util.Set; + +/** + * Creates instances using an injectable constructor. After construction, all injectable fields and + * methods are injected. + */ +final class ConstructorInjector { + + private final ImmutableSet injectableMembers; + private final SingleParameterInjector[] parameterInjectors; + private final ConstructionProxy constructionProxy; + private final MembersInjectorImpl membersInjector; + + ConstructorInjector(Set injectableMembers, + ConstructionProxy constructionProxy, + SingleParameterInjector[] parameterInjectors, + MembersInjectorImpl membersInjector) { + this.injectableMembers = ImmutableSet.copyOf(injectableMembers); + this.constructionProxy = constructionProxy; + this.parameterInjectors = parameterInjectors; + this.membersInjector = membersInjector; + } + + public ImmutableSet getInjectableMembers() { + return injectableMembers; + } + + ConstructionProxy getConstructionProxy() { + return constructionProxy; + } + + /** + * Construct an instance. Returns {@code Object} instead of {@code T} because + * it may return a proxy. + */ + Object construct(final Errors errors, final InternalContext context, + Class expectedType, + ProvisionListenerStackCallback provisionCallback) + throws ErrorsException { + final ConstructionContext constructionContext = context.getConstructionContext(this); + + // We have a circular reference between constructors. Return a proxy. + if (constructionContext.isConstructing()) { + // TODO (crazybob): if we can't proxy this object, can we proxy the other object? + return constructionContext.createProxy( + errors, context.getInjectorOptions(), expectedType); + } + + // If we're re-entering this factory while injecting fields or methods, + // return the same instance. This prevents infinite loops. + T t = constructionContext.getCurrentReference(); + if (t != null) { + return t; + } + + constructionContext.startConstruction(); + try { + // Optimization: Don't go through the callback stack if we have no listeners. + if (!provisionCallback.hasListeners()) { + return provision(errors, context, constructionContext); + } else { + return provisionCallback.provision(errors, context, new ProvisionCallback() { + public T call() throws ErrorsException { + return provision(errors, context, constructionContext); + } + }); + } + } finally { + constructionContext.finishConstruction(); + } + } + + /** + * Provisions a new T. + */ + private T provision(Errors errors, InternalContext context, + ConstructionContext constructionContext) throws ErrorsException { + try { + T t; + try { + Object[] parameters = SingleParameterInjector.getAll(errors, context, parameterInjectors); + t = constructionProxy.newInstance(parameters); + constructionContext.setProxyDelegates(t); + } finally { + constructionContext.finishConstruction(); + } + + // Store reference. If an injector re-enters this factory, they'll get the same reference. + constructionContext.setCurrentReference(t); + + membersInjector.injectMembers(t, errors, context, false); + membersInjector.notifyListeners(t, errors); + + return t; + } catch (InvocationTargetException userException) { + Throwable cause = userException.getCause() != null + ? userException.getCause() + : userException; + throw errors.withSource(constructionProxy.getInjectionPoint()) + .errorInjectingConstructor(cause).toException(); + } finally { + constructionContext.removeCurrentReference(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ConstructorInjectorStore.java b/src/main/java/com/google/inject/internal/ConstructorInjectorStore.java new file mode 100644 index 0000000..7060654 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ConstructorInjectorStore.java @@ -0,0 +1,63 @@ +package com.google.inject.internal; + +import com.google.inject.spi.InjectionPoint; + +/** + * Constructor injectors by type. + */ +final class ConstructorInjectorStore { + private final InjectorImpl injector; + + private final FailableCache> cache + = new FailableCache>() { + @Override + protected ConstructorInjector create(InjectionPoint constructorInjector, Errors errors) + throws ErrorsException { + return createConstructor(constructorInjector, errors); + } + }; + + ConstructorInjectorStore(InjectorImpl injector) { + this.injector = injector; + } + + /** + * Returns a new complete constructor injector with injection listeners registered. + */ + public ConstructorInjector get(InjectionPoint constructorInjector, Errors errors) + throws ErrorsException { + return cache.get(constructorInjector, errors); + } + + /** + * Purges an injection point from the cache. Use this only if the cache is not actually valid and + * needs to be purged. (See issue 319 and + * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and + * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is + * necessary.) + * + * Returns true if the injector for that point was stored in the cache, false otherwise. + */ + boolean remove(InjectionPoint ip) { + return cache.remove(ip); + } + + private ConstructorInjector createConstructor(InjectionPoint injectionPoint, Errors errors) + throws ErrorsException { + int numErrorsBefore = errors.size(); + + SingleParameterInjector[] constructorParameterInjectors + = injector.getParametersInjectors(injectionPoint.getDependencies(), errors); + + @SuppressWarnings("unchecked") // the injector type agrees with the injection point type + MembersInjectorImpl membersInjector = (MembersInjectorImpl) injector.membersInjectorStore + .get(injectionPoint.getDeclaringType(), errors); + + ConstructionProxyFactory factory = new DefaultConstructionProxyFactory(injectionPoint); + + errors.throwIfNewErrors(numErrorsBefore); + + return new ConstructorInjector(membersInjector.getInjectionPoints(), factory.create(), + constructorParameterInjectors, membersInjector); + } +} diff --git a/src/main/java/com/google/inject/internal/ContextualCallable.java b/src/main/java/com/google/inject/internal/ContextualCallable.java new file mode 100644 index 0000000..d67b8b7 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ContextualCallable.java @@ -0,0 +1,5 @@ +package com.google.inject.internal; + +interface ContextualCallable { + T call(InternalContext context) throws ErrorsException; +} diff --git a/src/main/java/com/google/inject/internal/CreationListener.java b/src/main/java/com/google/inject/internal/CreationListener.java new file mode 100644 index 0000000..104a2d8 --- /dev/null +++ b/src/main/java/com/google/inject/internal/CreationListener.java @@ -0,0 +1,12 @@ +package com.google.inject.internal; + +/** + * Something that is notified upon creation. + */ +interface CreationListener { + + /** + * Notifies that creation should happen. + */ + void notify(Errors errors); +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/CycleDetectingLock.java b/src/main/java/com/google/inject/internal/CycleDetectingLock.java new file mode 100644 index 0000000..ef3a605 --- /dev/null +++ b/src/main/java/com/google/inject/internal/CycleDetectingLock.java @@ -0,0 +1,308 @@ +package com.google.inject.internal; + +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Simplified version of {@link Lock} that is special due to how it handles deadlocks detection. + * + *

Is an inherent part of {@link SingletonScope}, moved into a upper level class due + * to its size and complexity. + * + * @param Lock identification provided by the client, is returned unmodified to the client + * when lock cycle is detected to identify it. Only toString() needs to be implemented. + * Lock references this object internally, + * for the purposes of Garbage Collection you should not use heavy IDs. + * Lock is referenced by a lock factory as long as it's owned by a thread. + */ +interface CycleDetectingLock { + + /** + * Takes a lock in a blocking fashion in case no potential deadlocks are detected. + * If the lock was successfully owned, returns an empty map indicating no detected potential + * deadlocks. + * + * Otherwise, a map indicating threads involved in a potential deadlock are returned. + * Map is ordered by dependency cycle and lists locks for each thread that are part of + * the loop in order. Returned map is created atomically. + * + * In case no cycle is detected performance is O(threads creating singletons), + * in case cycle is detected performance is O(singleton locks). + */ + ListMultimap lockOrDetectPotentialLocksCycle(); + + /** + * Unlocks previously locked lock. + */ + void unlock(); + + /** + * Wraps locks so they would never cause a deadlock. On each + * {@link CycleDetectingLock#lockOrDetectPotentialLocksCycle} we check for dependency cycles + * within locks created by the same factory. Either we detect a cycle and return it + * or take it atomically. + * + *

Important to note that we do not prevent deadlocks in the client code. As an example: + * Thread A takes lock L and creates singleton class CA depending on the singleton class CB. + * Meanwhile thread B is creating class CB and is waiting on the lock L. Issue happens + * due to client code creating interdependent classes and using locks, where + * no guarantees on the creation order from Guice are provided. + * + *

Instances of these locks are not intended to be exposed outside of {@link SingletonScope}. + */ + class CycleDetectingLockFactory { + + /** + * Lists locks that thread owns. + * Used only to populate locks in a potential cycle when it is detected. + * + * Key: thread id + * Value: stack of locks that were owned. + * + * Element is added inside {@link #lockOrDetectPotentialLocksCycle()} after {@link Lock#lock} + * is called. Element is removed inside {@link #unlock()} synchronously with + * {@link Lock#unlock()} call. + * + * Same lock can only be present several times for the same thread as locks are + * reentrant. Lock can not be owned by several different threads as the same time. + * + * Guarded by {@code this}. + */ + private final Multimap locksOwnedByThread = + LinkedHashMultimap.create(); + /** + * Specifies lock that thread is currently waiting on to own it. + * Used only for purposes of locks cycle detection. + * + * Key: thread id + * Value: lock that is being waited on + * + * Element is added inside {@link #lockOrDetectPotentialLocksCycle()} before {@link Lock#lock} + * is called. Element is removed inside {@link #lockOrDetectPotentialLocksCycle()} after + * {@link Lock#lock} and synchronously with adding it to {@link #locksOwnedByThread}. + * + * Same lock can be added for several threads in case all of them are trying to + * take it. + * + * Guarded by {@code this}. + */ + private Map lockThreadIsWaitingOn = Maps.newHashMap(); + + /** + * Creates new lock within this factory context. We can guarantee that locks created by + * the same factory would not deadlock. + * + * @param newLockId lock id that would be used to report lock cycles if detected + */ + CycleDetectingLock create(ID newLockId) { + return new ReentrantCycleDetectingLock(newLockId, new ReentrantLock()); + } + + /** + * The implementation for {@link CycleDetectingLock}. + */ + class ReentrantCycleDetectingLock implements CycleDetectingLock { + + /** + * Underlying lock used for actual waiting when no potential deadlocks are detected. + */ + private final Lock lockImplementation; + /** + * User id for this lock. + */ + private final ID userLockId; + /** + * Thread id for the thread that owned this lock. Nullable. + * Guarded by {@code CycleDetectingLockFactory.this}. + */ + private Long lockOwnerThreadId = null; + /** + * Number of times that thread owned this lock. + * Guarded by {@code CycleDetectingLockFactory.this}. + */ + private int lockReentranceCount = 0; + + ReentrantCycleDetectingLock(ID userLockId, Lock lockImplementation) { + this.userLockId = Preconditions.checkNotNull(userLockId, "userLockId"); + this.lockImplementation = Preconditions.checkNotNull( + lockImplementation, "lockImplementation"); + } + + @Override + public ListMultimap lockOrDetectPotentialLocksCycle() { + final long currentThreadId = Thread.currentThread().getId(); + synchronized (CycleDetectingLockFactory.this) { + checkState(); + ListMultimap locksInCycle = detectPotentialLocksCycle(); + if (!locksInCycle.isEmpty()) { + // potential deadlock is found, we don't try to take this lock + return locksInCycle; + } + + lockThreadIsWaitingOn.put(currentThreadId, this); + } + + // this may be blocking, but we don't expect it to cause a deadlock + lockImplementation.lock(); + + synchronized (CycleDetectingLockFactory.this) { + // current thread is no longer waiting on this lock + lockThreadIsWaitingOn.remove(currentThreadId); + checkState(); + + // mark it as owned by us + lockOwnerThreadId = currentThreadId; + lockReentranceCount++; + // add this lock to the list of locks owned by a current thread + locksOwnedByThread.put(currentThreadId, this); + } + // no deadlock is found, locking successful + return ImmutableListMultimap.of(); + } + + @Override + public void unlock() { + final long currentThreadId = Thread.currentThread().getId(); + synchronized (CycleDetectingLockFactory.this) { + checkState(); + Preconditions.checkState(lockOwnerThreadId != null, + "Thread is trying to unlock a lock that is not locked"); + Preconditions.checkState(lockOwnerThreadId == currentThreadId, + "Thread is trying to unlock a lock owned by another thread"); + + // releasing underlying lock + lockImplementation.unlock(); + + // be sure to release the lock synchronously with updating internal state + lockReentranceCount--; + if (lockReentranceCount == 0) { + // we no longer own this lock + lockOwnerThreadId = null; + Preconditions.checkState(locksOwnedByThread.remove(currentThreadId, this), + "Internal error: Can not find this lock in locks owned by a current thread"); + if (locksOwnedByThread.get(currentThreadId).isEmpty()) { + // clearing memory + locksOwnedByThread.removeAll(currentThreadId); + } + } + } + } + + /** + * Check consistency of an internal state. + */ + void checkState() throws IllegalStateException { + final long currentThreadId = Thread.currentThread().getId(); + Preconditions.checkState(!lockThreadIsWaitingOn.containsKey(currentThreadId), + "Internal error: Thread should not be in a waiting thread on a lock now"); + if (lockOwnerThreadId != null) { + // check state of a locked lock + Preconditions.checkState(lockReentranceCount >= 0, + "Internal error: Lock ownership and reentrance count internal states do not match"); + Preconditions.checkState(locksOwnedByThread.get(lockOwnerThreadId).contains(this), + "Internal error: Set of locks owned by a current thread and lock " + + "ownership status do not match"); + } else { + // check state of a non locked lock + Preconditions.checkState(lockReentranceCount == 0, + "Internal error: Reentrance count of a non locked lock is expect to be zero"); + Preconditions.checkState(!locksOwnedByThread.values().contains(this), + "Internal error: Non locked lock should not be owned by any thread"); + } + } + + /** + * Algorithm to detect a potential lock cycle. + * + * For lock's thread owner check which lock is it trying to take. + * Repeat recursively. When current thread is found a potential cycle is detected. + * + * @see CycleDetectingLock#lockOrDetectPotentialLocksCycle() + */ + private ListMultimap detectPotentialLocksCycle() { + final long currentThreadId = Thread.currentThread().getId(); + if (lockOwnerThreadId == null || lockOwnerThreadId == currentThreadId) { + // if nobody owns this lock, lock cycle is impossible + // if a current thread owns this lock, we let Guice to handle it + return ImmutableListMultimap.of(); + } + + ListMultimap potentialLocksCycle = Multimaps.newListMultimap( + new LinkedHashMap>(), + new Supplier>() { + @Override + public List get() { + return Lists.newArrayList(); + } + }); + // lock that is a part of a potential locks cycle, starts with current lock + ReentrantCycleDetectingLock lockOwnerWaitingOn = this; + // try to find a dependency path between lock's owner thread and a current thread + while (lockOwnerWaitingOn != null && lockOwnerWaitingOn.lockOwnerThreadId != null) { + Long threadOwnerThreadWaits = lockOwnerWaitingOn.lockOwnerThreadId; + // in case locks cycle exists lock we're waiting for is part of it + potentialLocksCycle.putAll(threadOwnerThreadWaits, + getAllLockIdsAfter(threadOwnerThreadWaits, lockOwnerWaitingOn)); + + if (threadOwnerThreadWaits == currentThreadId) { + // owner thread depends on current thread, cycle detected + return potentialLocksCycle; + } + // going for the next thread we wait on indirectly + lockOwnerWaitingOn = lockThreadIsWaitingOn.get(threadOwnerThreadWaits); + } + // no dependency path from an owner thread to a current thread + return ImmutableListMultimap.of(); + } + + /** + * Return locks owned by a thread after a lock specified, inclusive. + */ + private List getAllLockIdsAfter(long threadId, ReentrantCycleDetectingLock lock) { + List ids = Lists.newArrayList(); + boolean found = false; + Collection ownedLocks = locksOwnedByThread.get(threadId); + Preconditions.checkNotNull(ownedLocks, + "Internal error: No locks were found taken by a thread"); + for (ReentrantCycleDetectingLock ownedLock : ownedLocks) { + if (ownedLock == lock) { + found = true; + } + if (found) { + ids.add(ownedLock.userLockId); + } + } + Preconditions.checkState(found, "Internal error: We can not find locks that " + + "created a cycle that we detected"); + return ids; + } + + @Override + public String toString() { + // copy is made to prevent a data race + // no synchronization is used, potentially stale data, should be good enough + Long localLockOwnerThreadId = this.lockOwnerThreadId; + if (localLockOwnerThreadId != null) { + return String.format("CycleDetectingLock[%s][locked by %s]", + userLockId, localLockOwnerThreadId); + } else { + return String.format("CycleDetectingLock[%s][unlocked]", userLockId); + } + } + } + } +} diff --git a/src/main/java/com/google/inject/internal/DefaultConstructionProxyFactory.java b/src/main/java/com/google/inject/internal/DefaultConstructionProxyFactory.java new file mode 100644 index 0000000..12f19cd --- /dev/null +++ b/src/main/java/com/google/inject/internal/DefaultConstructionProxyFactory.java @@ -0,0 +1,57 @@ +package com.google.inject.internal; + +import com.google.inject.spi.InjectionPoint; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +/** + * Produces construction proxies that invoke the class constructor. + */ +final class DefaultConstructionProxyFactory implements ConstructionProxyFactory { + + private final InjectionPoint injectionPoint; + + /** + * @param injectionPoint an injection point whose member is a constructor of {@code T}. + */ + DefaultConstructionProxyFactory(InjectionPoint injectionPoint) { + this.injectionPoint = injectionPoint; + } + + public ConstructionProxy create() { + @SuppressWarnings("unchecked") // the injection point is for a constructor of T + final Constructor constructor = (Constructor) injectionPoint.getMember(); + + // Use FastConstructor if the constructor is public. + if (Modifier.isPublic(constructor.getModifiers())) { + Class classToConstruct = constructor.getDeclaringClass(); + if (!Modifier.isPublic(classToConstruct.getModifiers())) { + constructor.setAccessible(true); + } + } else { + constructor.setAccessible(true); + } + + return new ConstructionProxy() { + public T newInstance(Object... arguments) throws InvocationTargetException { + try { + return constructor.newInstance(arguments); + } catch (InstantiationException e) { + throw new AssertionError(e); // shouldn't happen, we know this is a concrete type + } catch (IllegalAccessException e) { + throw new AssertionError(e); // a security manager is blocking us, we're hosed + } + } + + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + public Constructor getConstructor() { + return constructor; + } + }; + } +} diff --git a/src/main/java/com/google/inject/internal/DeferredLookups.java b/src/main/java/com/google/inject/internal/DeferredLookups.java new file mode 100644 index 0000000..ee3c021 --- /dev/null +++ b/src/main/java/com/google/inject/internal/DeferredLookups.java @@ -0,0 +1,45 @@ +package com.google.inject.internal; + +import com.google.common.collect.Lists; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.Element; +import com.google.inject.spi.MembersInjectorLookup; +import com.google.inject.spi.ProviderLookup; + +import java.util.List; + +/** + * Returns providers and members injectors that haven't yet been initialized. As a part of injector + * creation it's necessary to {@link #initialize initialize} these lookups. + */ +final class DeferredLookups implements Lookups { + private final InjectorImpl injector; + private final List lookups = Lists.newArrayList(); + + DeferredLookups(InjectorImpl injector) { + this.injector = injector; + } + + /** + * Initialize the specified lookups, either immediately or when the injector is created. + */ + void initialize(Errors errors) { + injector.lookups = injector; + new LookupProcessor(errors).process(injector, lookups); + } + + public Provider getProvider(Key key) { + ProviderLookup lookup = new ProviderLookup(key, key); + lookups.add(lookup); + return lookup.getProvider(); + } + + public MembersInjector getMembersInjector(TypeLiteral type) { + MembersInjectorLookup lookup = new MembersInjectorLookup(type, type); + lookups.add(lookup); + return lookup.getMembersInjector(); + } +} diff --git a/src/main/java/com/google/inject/internal/DelayedInitialize.java b/src/main/java/com/google/inject/internal/DelayedInitialize.java new file mode 100644 index 0000000..3fb3f7b --- /dev/null +++ b/src/main/java/com/google/inject/internal/DelayedInitialize.java @@ -0,0 +1,15 @@ +package com.google.inject.internal; + +/** + * Something that needs some delayed initialization, typically + * a binding or internal factory that needs to be created & put + * into the bindings map & then initialized later. + */ +interface DelayedInitialize { + + /** + * Initializes this binding, throwing any errors if necessary. + */ + void initialize(InjectorImpl injector, Errors errors) throws ErrorsException; + +} diff --git a/src/main/java/com/google/inject/internal/DelegatingInvocationHandler.java b/src/main/java/com/google/inject/internal/DelegatingInvocationHandler.java new file mode 100644 index 0000000..9edd669 --- /dev/null +++ b/src/main/java/com/google/inject/internal/DelegatingInvocationHandler.java @@ -0,0 +1,44 @@ +package com.google.inject.internal; + +import com.google.common.base.Preconditions; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +class DelegatingInvocationHandler implements InvocationHandler { + + private volatile boolean initialized; + + private T delegate; + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + try { + // checking volatile field for synchronization + Preconditions.checkState(initialized, + "This is a proxy used to support" + + " circular references. The object we're" + + " proxying is not constructed yet. Please wait until after" + + " injection has completed to use this object."); + Preconditions.checkNotNull(delegate, + "This is a proxy used to support" + + " circular references. The object we're " + + " proxying is initialized to null." + + " No methods can be called."); + + return method.invoke(delegate, args); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + void setDelegate(T delegate) { + this.delegate = delegate; + initialized = true; + } +} diff --git a/src/main/java/com/google/inject/internal/EncounterImpl.java b/src/main/java/com/google/inject/internal/EncounterImpl.java new file mode 100644 index 0000000..9a2824a --- /dev/null +++ b/src/main/java/com/google/inject/internal/EncounterImpl.java @@ -0,0 +1,98 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.Message; +import com.google.inject.spi.TypeEncounter; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkState; + +final class EncounterImpl implements TypeEncounter { + + private final Errors errors; + private final Lookups lookups; + private List> membersInjectors; // lazy + private List> injectionListeners; // lazy + private boolean valid = true; + + EncounterImpl(Errors errors, Lookups lookups) { + this.errors = errors; + this.lookups = lookups; + } + + void invalidate() { + valid = false; + } + + ImmutableSet> getMembersInjectors() { + return membersInjectors == null + ? ImmutableSet.>of() + : ImmutableSet.copyOf(membersInjectors); + } + + ImmutableSet> getInjectionListeners() { + return injectionListeners == null + ? ImmutableSet.>of() + : ImmutableSet.copyOf(injectionListeners); + } + + public void register(MembersInjector membersInjector) { + checkState(valid, "Encounters may not be used after hear() returns."); + + if (membersInjectors == null) { + membersInjectors = Lists.newArrayList(); + } + + membersInjectors.add(membersInjector); + } + + public void register(InjectionListener injectionListener) { + checkState(valid, "Encounters may not be used after hear() returns."); + + if (injectionListeners == null) { + injectionListeners = Lists.newArrayList(); + } + + injectionListeners.add(injectionListener); + } + + public void addError(String message, Object... arguments) { + checkState(valid, "Encounters may not be used after hear() returns."); + errors.addMessage(message, arguments); + } + + public void addError(Throwable t) { + checkState(valid, "Encounters may not be used after hear() returns."); + errors.errorInUserCode(t, "An exception was caught and reported. Message: %s", t.getMessage()); + } + + public void addError(Message message) { + checkState(valid, "Encounters may not be used after hear() returns."); + errors.addMessage(message); + } + + public Provider getProvider(Key key) { + checkState(valid, "Encounters may not be used after hear() returns."); + return lookups.getProvider(key); + } + + public Provider getProvider(Class type) { + return getProvider(Key.get(type)); + } + + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + checkState(valid, "Encounters may not be used after hear() returns."); + return lookups.getMembersInjector(typeLiteral); + } + + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/Errors.java b/src/main/java/com/google/inject/internal/Errors.java new file mode 100644 index 0000000..0a61fc6 --- /dev/null +++ b/src/main/java/com/google/inject/internal/Errors.java @@ -0,0 +1,817 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; +import com.google.inject.ConfigurationException; +import com.google.inject.CreationException; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.ProvisionException; +import com.google.inject.Scope; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.util.Classes; +import com.google.inject.internal.util.SourceProvider; +import com.google.inject.internal.util.StackTraceElements; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.ElementSource; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.Message; +import com.google.inject.spi.ScopeBinding; +import com.google.inject.spi.TypeConverterBinding; +import com.google.inject.spi.TypeListenerBinding; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.Formatter; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A collection of error messages. If this type is passed as a method parameter, the method is + * considered to have executed successfully only if new errors were not added to this collection. + * + *

Errors can be chained to provide additional context. To add context, call {@link #withSource} + * to create a new Errors instance that contains additional context. All messages added to the + * returned instance will contain full context. + * + *

To avoid messages with redundant context, {@link #withSource} should be added sparingly. A + * good rule of thumb is to assume a method's caller has already specified enough context to + * identify that method. When calling a method that's defined in a different context, call that + * method with an errors object that includes its context. + * + */ +public final class Errors { + + private static final Set> warnedDependencies = + Collections.newSetFromMap(new ConcurrentHashMap, Boolean>()); + private static final String CONSTRUCTOR_RULES = + "Classes must have either one (and only one) constructor " + + "annotated with @Inject or a zero-argument constructor that is not private."; + private static final Collection> converters = ImmutableList.of( + new Converter(Class.class) { + @Override + public String toString(Class c) { + return c.getName(); + } + }, + new Converter(Member.class) { + @Override + public String toString(Member member) { + return Classes.toString(member); + } + }, + new Converter(Key.class) { + @Override + public String toString(Key key) { + if (key.getAnnotationType() != null) { + return key.getTypeLiteral() + " annotated with " + + (key.getAnnotation() != null ? key.getAnnotation() : key.getAnnotationType()); + } else { + return key.getTypeLiteral().toString(); + } + } + }); + /** + * The root errors object. Used to access the list of error messages. + */ + private final Errors root; + /** + * The parent errors object. Used to obtain the chain of source objects. + */ + private final Errors parent; + /** + * The leaf source for errors added here. + */ + private final Object source; + /** + * null unless (root == this) and error messages exist. Never an empty list. + */ + private List errors; // lazy, use getErrorsForAdd() + + public Errors() { + this.root = this; + this.parent = null; + this.source = SourceProvider.UNKNOWN_SOURCE; + } + + public Errors(Object source) { + this.root = this; + this.parent = null; + this.source = source; + } + + private Errors(Errors parent, Object source) { + this.root = parent.root; + this.parent = parent; + this.source = source; + } + + public static Collection getMessagesFromThrowable(Throwable throwable) { + if (throwable instanceof ProvisionException) { + return ((ProvisionException) throwable).getErrorMessages(); + } else if (throwable instanceof ConfigurationException) { + return ((ConfigurationException) throwable).getErrorMessages(); + } else if (throwable instanceof CreationException) { + return ((CreationException) throwable).getErrorMessages(); + } else { + return ImmutableSet.of(); + } + } + + public static String format(String messageFormat, Object... arguments) { + for (int i = 0; i < arguments.length; i++) { + arguments[i] = Errors.convert(arguments[i]); + } + return String.format(messageFormat, arguments); + } + + /** + * Returns the formatted message for an exception with the specified messages. + */ + public static String format(String heading, Collection errorMessages) { + Formatter fmt = new Formatter().format(heading).format(":%n%n"); + int index = 1; + boolean displayCauses = getOnlyCause(errorMessages) == null; + + for (Message errorMessage : errorMessages) { + fmt.format("%s) %s%n", index++, errorMessage.getMessage()); + + List dependencies = errorMessage.getSources(); + for (int i = dependencies.size() - 1; i >= 0; i--) { + Object source = dependencies.get(i); + formatSource(fmt, source); + } + + Throwable cause = errorMessage.getCause(); + if (displayCauses && cause != null) { + StringWriter writer = new StringWriter(); + cause.printStackTrace(new PrintWriter(writer)); + fmt.format("Caused by: %s", writer.getBuffer()); + } + + fmt.format("%n"); + } + + if (errorMessages.size() == 1) { + fmt.format("1 error"); + } else { + fmt.format("%s errors", errorMessages.size()); + } + + return fmt.toString(); + } + + /** + * Returns the cause throwable if there is exactly one cause in {@code messages}. If there are + * zero or multiple messages with causes, null is returned. + */ + public static Throwable getOnlyCause(Collection messages) { + Throwable onlyCause = null; + for (Message message : messages) { + Throwable messageCause = message.getCause(); + if (messageCause == null) { + continue; + } + + if (onlyCause != null) { + return null; + } + + onlyCause = messageCause; + } + + return onlyCause; + } + + public static Object convert(Object o) { + ElementSource source = null; + if (o instanceof ElementSource) { + source = (ElementSource) o; + o = source.getDeclaringSource(); + } + return convert(o, source); + } + + public static Object convert(Object o, ElementSource source) { + for (Converter converter : converters) { + if (converter.appliesTo(o)) { + return appendModules(converter.convert(o), source); + } + } + return appendModules(o, source); + } + + private static Object appendModules(Object source, ElementSource elementSource) { + String modules = moduleSourceString(elementSource); + if (modules.length() == 0) { + return source; + } else { + return source + modules; + } + } + + private static String moduleSourceString(ElementSource elementSource) { + // if we only have one module (or don't know what they are), then don't bother + // reporting it, because the source already is going to report exactly that module. + if (elementSource == null) { + return ""; + } + List modules = Lists.newArrayList(elementSource.getModuleClassNames()); + // Insert any original element sources w/ module info into the path. + while (elementSource.getOriginalElementSource() != null) { + elementSource = elementSource.getOriginalElementSource(); + modules.addAll(0, elementSource.getModuleClassNames()); + } + if (modules.size() <= 1) { + return ""; + } + + // Ideally we'd do: + // return Joiner.on(" -> ") + // .appendTo(new StringBuilder(" (via modules: "), Lists.reverse(modules)) + // .append(")").toString(); + // ... but for some reason we can't find Lists.reverse, so do it the boring way. + StringBuilder builder = new StringBuilder(" (via modules: "); + for (int i = modules.size() - 1; i >= 0; i--) { + builder.append(modules.get(i)); + if (i != 0) { + builder.append(" -> "); + } + } + builder.append(")"); + return builder.toString(); + } + + public static void formatSource(Formatter formatter, Object source) { + ElementSource elementSource = null; + if (source instanceof ElementSource) { + elementSource = (ElementSource) source; + source = elementSource.getDeclaringSource(); + } + formatSource(formatter, source, elementSource); + } + + public static void formatSource(Formatter formatter, Object source, ElementSource elementSource) { + String modules = moduleSourceString(elementSource); + if (source instanceof Dependency) { + Dependency dependency = (Dependency) source; + InjectionPoint injectionPoint = dependency.getInjectionPoint(); + if (injectionPoint != null) { + formatInjectionPoint(formatter, dependency, injectionPoint, elementSource); + } else { + formatSource(formatter, dependency.getKey(), elementSource); + } + + } else if (source instanceof InjectionPoint) { + formatInjectionPoint(formatter, null, (InjectionPoint) source, elementSource); + + } else if (source instanceof Class) { + formatter.format(" at %s%s%n", StackTraceElements.forType((Class) source), modules); + + } else if (source instanceof Member) { + formatter.format(" at %s%s%n", StackTraceElements.forMember((Member) source), modules); + + } else if (source instanceof TypeLiteral) { + formatter.format(" while locating %s%s%n", source, modules); + + } else if (source instanceof Key) { + Key key = (Key) source; + formatter.format(" while locating %s%n", convert(key, elementSource)); + + } else if (source instanceof Thread) { + formatter.format(" in thread %s%n", source); + + } else { + formatter.format(" at %s%s%n", source, modules); + } + } + + public static void formatInjectionPoint(Formatter formatter, Dependency dependency, + InjectionPoint injectionPoint, ElementSource elementSource) { + Member member = injectionPoint.getMember(); + Class memberType = Classes.memberType(member); + + if (memberType == Field.class) { + dependency = injectionPoint.getDependencies().get(0); + formatter.format(" while locating %s%n", convert(dependency.getKey(), elementSource)); + formatter.format(" for field at %s%n", StackTraceElements.forMember(member)); + + } else if (dependency != null) { + formatter.format(" while locating %s%n", convert(dependency.getKey(), elementSource)); + formatter.format(" for parameter %s at %s%n", + dependency.getParameterIndex(), StackTraceElements.forMember(member)); + + } else { + formatSource(formatter, injectionPoint.getMember()); + } + } + + /** + * Returns an instance that uses {@code source} as a reference point for newly added errors. + */ + public Errors withSource(Object source) { + return source == this.source || source == SourceProvider.UNKNOWN_SOURCE + ? this + : new Errors(this, source); + } + + /** + * We use a fairly generic error message here. The motivation is to share the + * same message for both bind time errors: + *
Guice.createInjector(new AbstractModule() {
+     *   public void configure() {
+     *     bind(Runnable.class);
+     *   }
+     * }
+ * ...and at provide-time errors: + *
Guice.createInjector().getInstance(Runnable.class);
+ * Otherwise we need to know who's calling when resolving a just-in-time + * binding, which makes things unnecessarily complex. + */ + public Errors missingImplementation(Key key) { + return addMessage("No implementation for %s was bound.", key); + } + + public Errors jitDisabled(Key key) { + return addMessage("Explicit bindings are required and %s is not explicitly bound.", key); + } + + public Errors jitDisabledInParent(Key key) { + return addMessage( + "Explicit bindings are required and %s would be bound in a parent injector.%n" + + "Please add an explicit binding for it, either in the child or the parent.", + key); + } + + public Errors atInjectRequired(Class clazz) { + return addMessage( + "Explicit @Inject annotations are required on constructors," + + " but %s has no constructors annotated with @Inject.", + clazz); + } + + public Errors converterReturnedNull(String stringValue, Object source, + TypeLiteral type, TypeConverterBinding typeConverterBinding) { + return addMessage("Received null converting '%s' (bound at %s) to %s%n" + + " using %s.", + stringValue, convert(source), type, typeConverterBinding); + } + + public Errors conversionTypeError(String stringValue, Object source, TypeLiteral type, + TypeConverterBinding typeConverterBinding, Object converted) { + return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n" + + " using %s.%n" + + " Converter returned %s.", + stringValue, convert(source), type, typeConverterBinding, converted); + } + + public Errors conversionError(String stringValue, Object source, + TypeLiteral type, TypeConverterBinding typeConverterBinding, RuntimeException cause) { + return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n" + + " using %s.%n" + + " Reason: %s", + stringValue, convert(source), type, typeConverterBinding, cause); + } + + public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral type, + TypeConverterBinding a, TypeConverterBinding b) { + return addMessage("Multiple converters can convert '%s' (bound at %s) to %s:%n" + + " %s and%n" + + " %s.%n" + + " Please adjust your type converter configuration to avoid overlapping matches.", + stringValue, convert(source), type, a, b); + } + + public Errors bindingToProvider() { + return addMessage("Binding to Provider is not allowed."); + } + + public Errors subtypeNotProvided(Class> providerType, + Class type) { + return addMessage("%s doesn't provide instances of %s.", providerType, type); + } + + public Errors notASubtype(Class implementationType, Class type) { + return addMessage("%s doesn't extend %s.", implementationType, type); + } + + public Errors recursiveImplementationType() { + return addMessage("@ImplementedBy points to the same class it annotates."); + } + + public Errors recursiveProviderType() { + return addMessage("@ProvidedBy points to the same class it annotates."); + } + + public Errors missingRuntimeRetention(Class annotation) { + return addMessage(format("Please annotate %s with @Retention(RUNTIME).", annotation)); + } + + public Errors missingScopeAnnotation(Class annotation) { + return addMessage(format("Please annotate %s with @ScopeAnnotation.", annotation)); + } + + public Errors optionalConstructor(Constructor constructor) { + return addMessage("%s is annotated @Inject(optional=true), " + + "but constructors cannot be optional.", constructor); + } + + public Errors cannotBindToGuiceType(String simpleName) { + return addMessage("Binding to core guice framework type is not allowed: %s.", simpleName); + } + + public Errors scopeNotFound(Class scopeAnnotation) { + return addMessage("No scope is bound to %s.", scopeAnnotation); + } + + public Errors scopeAnnotationOnAbstractType( + Class scopeAnnotation, Class type, Object source) { + return addMessage("%s is annotated with %s, but scope annotations are not supported " + + "for abstract types.%n Bound at %s.", type, scopeAnnotation, convert(source)); + } + + public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) { + return addMessage("%s is annotated with %s, but binding annotations should be applied " + + "to its parameters instead.", member, bindingAnnotation); + } + + public Errors missingConstructor(Class implementation) { + return addMessage("Could not find a suitable constructor in %s. " + CONSTRUCTOR_RULES, + implementation); + } + + public Errors tooManyConstructors(Class implementation) { + return addMessage("%s has more than one constructor annotated with @Inject. " + + CONSTRUCTOR_RULES, implementation); + } + + public Errors constructorNotDefinedByType(Constructor constructor, TypeLiteral type) { + return addMessage("%s does not define %s", type, constructor); + } + + public Errors duplicateScopes(ScopeBinding existing, + Class annotationType, Scope scope) { + return addMessage("Scope %s is already bound to %s at %s.%n Cannot bind %s.", + existing.getScope(), annotationType, existing.getSource(), scope); + } + + public Errors voidProviderMethod() { + return addMessage("Provider methods must return a value. Do not return void."); + } + + public Errors missingConstantValues() { + return addMessage("Missing constant value. Please call to(...)."); + } + + public Errors cannotInjectInnerClass(Class type) { + return addMessage("Injecting into inner classes is not supported. " + + "Please use a 'static' class (top-level or nested) instead of %s.", type); + } + + public Errors duplicateBindingAnnotations(Member member, + Class a, Class b) { + return addMessage("%s has more than one annotation annotated with @BindingAnnotation: " + + "%s and %s", member, a, b); + } + + public Errors staticInjectionOnInterface(Class clazz) { + return addMessage("%s is an interface, but interfaces have no static injection points.", clazz); + } + + public Errors cannotInjectFinalField(Field field) { + return addMessage("Injected field %s cannot be final.", field); + } + + public Errors cannotInjectAbstractMethod(Method method) { + return addMessage("Injected method %s cannot be abstract.", method); + } + + public Errors cannotInjectNonVoidMethod(Method method) { + return addMessage("Injected method %s must return void.", method); + } + + public Errors cannotInjectMethodWithTypeParameters(Method method) { + return addMessage("Injected method %s cannot declare type parameters of its own.", method); + } + + public Errors duplicateScopeAnnotations( + Class a, Class b) { + return addMessage("More than one scope annotation was found: %s and %s.", a, b); + } + + public Errors recursiveBinding() { + return addMessage("Binding points to itself."); + } + + public Errors bindingAlreadySet(Key key, Object source) { + return addMessage("A binding to %s was already configured at %s.", key, convert(source)); + } + + public Errors jitBindingAlreadySet(Key key) { + return addMessage("A just-in-time binding to %s was already configured on a parent injector.", key); + } + + public Errors childBindingAlreadySet(Key key, Set sources) { + Formatter allSources = new Formatter(); + for (Object source : sources) { + if (source == null) { + allSources.format("%n (bound by a just-in-time binding)"); + } else { + allSources.format("%n bound at %s", source); + } + } + Errors errors = addMessage( + "Unable to create binding for %s." + + " It was already configured on one or more child injectors or private modules" + + "%s%n" + + " If it was in a PrivateModule, did you forget to expose the binding?", + key, allSources.out()); + return errors; + } + + public Errors errorCheckingDuplicateBinding(Key key, Object source, Throwable t) { + return addMessage( + "A binding to %s was already configured at %s and an error was thrown " + + "while checking duplicate bindings. Error: %s", + key, convert(source), t); + } + + public Errors errorInjectingMethod(Throwable cause) { + return errorInUserCode(cause, "Error injecting method, %s", cause); + } + + public Errors errorNotifyingTypeListener(TypeListenerBinding listener, + TypeLiteral type, Throwable cause) { + return errorInUserCode(cause, + "Error notifying TypeListener %s (bound at %s) of %s.%n" + + " Reason: %s", + listener.getListener(), convert(listener.getSource()), type, cause); + } + + public Errors errorInjectingConstructor(Throwable cause) { + return errorInUserCode(cause, "Error injecting constructor, %s", cause); + } + + public Errors errorInProvider(RuntimeException runtimeException) { + Throwable unwrapped = unwrap(runtimeException); + return errorInUserCode(unwrapped, "Error in custom provider, %s", unwrapped); + } + + public Errors errorInUserInjector( + MembersInjector listener, TypeLiteral type, RuntimeException cause) { + return errorInUserCode(cause, "Error injecting %s using %s.%n" + + " Reason: %s", type, listener, cause); + } + + public Errors errorNotifyingInjectionListener( + InjectionListener listener, TypeLiteral type, RuntimeException cause) { + return errorInUserCode(cause, "Error notifying InjectionListener %s of %s.%n" + + " Reason: %s", listener, type, cause); + } + + public Errors exposedButNotBound(Key key) { + return addMessage("Could not expose() %s, it must be explicitly bound.", key); + } + + public Errors keyNotFullySpecified(TypeLiteral typeLiteral) { + return addMessage("%s cannot be used as a key; It is not fully specified.", typeLiteral); + } + + public Errors errorEnhancingClass(Class clazz, Throwable cause) { + return errorInUserCode(cause, "Unable to method intercept: %s", clazz); + } + + public Errors errorInUserCode(Throwable cause, String messageFormat, Object... arguments) { + Collection messages = getMessagesFromThrowable(cause); + + if (!messages.isEmpty()) { + return merge(messages); + } else { + return addMessage(cause, messageFormat, arguments); + } + } + + private Throwable unwrap(RuntimeException runtimeException) { + if (runtimeException instanceof Exceptions.UnhandledCheckedUserException) { + return runtimeException.getCause(); + } else { + return runtimeException; + } + } + + public Errors cannotInjectRawProvider() { + return addMessage("Cannot inject a Provider that has no type parameter"); + } + + public Errors cannotInjectRawMembersInjector() { + return addMessage("Cannot inject a MembersInjector that has no type parameter"); + } + + public Errors cannotInjectTypeLiteralOf(Type unsupportedType) { + return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType); + } + + public Errors cannotInjectRawTypeLiteral() { + return addMessage("Cannot inject a TypeLiteral that has no type parameter"); + } + + public Errors cannotSatisfyCircularDependency(Class expectedType) { + return addMessage( + "Tried proxying %s to support a circular dependency, but it is not an interface.", + expectedType); + } + + public Errors circularProxiesDisabled(Class expectedType) { + return addMessage( + "Tried proxying %s to support a circular dependency, but circular proxies are disabled.", + expectedType); + } + + public void throwCreationExceptionIfErrorsExist() { + if (!hasErrors()) { + return; + } + + throw new CreationException(getMessages()); + } + + public void throwConfigurationExceptionIfErrorsExist() { + if (!hasErrors()) { + return; + } + + throw new ConfigurationException(getMessages()); + } + + public void throwProvisionExceptionIfErrorsExist() { + if (!hasErrors()) { + return; + } + + throw new ProvisionException(getMessages()); + } + + private Message merge(Message message) { + List sources = Lists.newArrayList(); + sources.addAll(getSources()); + sources.addAll(message.getSources()); + return new Message(sources, message.getMessage(), message.getCause()); + } + + public Errors merge(Collection messages) { + for (Message message : messages) { + addMessage(merge(message)); + } + return this; + } + + public Errors merge(Errors moreErrors) { + if (moreErrors.root == root || moreErrors.root.errors == null) { + return this; + } + + merge(moreErrors.root.errors); + return this; + } + + public List getSources() { + List sources = Lists.newArrayList(); + for (Errors e = this; e != null; e = e.parent) { + if (e.source != SourceProvider.UNKNOWN_SOURCE) { + sources.add(0, e.source); + } + } + return sources; + } + + public void throwIfNewErrors(int expectedSize) throws ErrorsException { + if (size() == expectedSize) { + return; + } + + throw toException(); + } + + public ErrorsException toException() { + return new ErrorsException(this); + } + + public boolean hasErrors() { + return root.errors != null; + } + + public Errors addMessage(String messageFormat, Object... arguments) { + return addMessage(null, messageFormat, arguments); + } + + private Errors addMessage(Throwable cause, String messageFormat, Object... arguments) { + String message = format(messageFormat, arguments); + addMessage(new Message(getSources(), message, cause)); + return this; + } + + public Errors addMessage(Message message) { + if (root.errors == null) { + root.errors = Lists.newArrayList(); + } + root.errors.add(message); + return this; + } + + public List getMessages() { + if (root.errors == null) { + return ImmutableList.of(); + } + + return new Ordering() { + @Override + public int compare(Message a, Message b) { + return a.getSource().compareTo(b.getSource()); + } + }.sortedCopy(root.errors); + } + + /** + * Returns {@code value} if it is non-null allowed to be null. Otherwise a message is added and + * an {@code ErrorsException} is thrown. + */ + public T checkForNull(T value, Object source, Dependency dependency) + throws ErrorsException { + if (value != null || dependency.isNullable()) { + return value; + } + + // Hack to allow null parameters to @Provides methods, for backwards compatibility. + if (dependency.getInjectionPoint().getMember() instanceof Method) { + Method annotated = (Method) dependency.getInjectionPoint().getMember(); + if (annotated.isAnnotationPresent(Provides.class)) { + switch (InternalFlags.getNullableProvidesOption()) { + case ERROR: + break; // break out & let the below exception happen + case IGNORE: + return value; // user doesn't care about injecting nulls to non-@Nullables. + case WARN: + // Warn only once, otherwise we spam logs too much. + if (!warnedDependencies.add(dependency)) { + return value; + } + /*logger.log(Level.WARNING, + "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable." + + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" + + " error.", + new Object[]{ + dependency.getParameterIndex(), + convert(dependency.getInjectionPoint().getMember()), + convert(dependency.getKey())});*/ + return null; // log & exit. + } + } + } + + int parameterIndex = dependency.getParameterIndex(); + String parameterName = (parameterIndex != -1) + ? "parameter " + parameterIndex + " of " + : ""; + addMessage("null returned by binding at %s%n but %s%s is not @Nullable", + source, parameterName, dependency.getInjectionPoint().getMember()); + + throw toException(); + } + + public int size() { + return root.errors == null ? 0 : root.errors.size(); + } + + private static abstract class Converter { + + final Class type; + + Converter(Class type) { + this.type = type; + } + + boolean appliesTo(Object o) { + return o != null && type.isAssignableFrom(o.getClass()); + } + + String convert(Object o) { + return toString(type.cast(o)); + } + + abstract String toString(T t); + } +} diff --git a/src/main/java/com/google/inject/internal/ErrorsException.java b/src/main/java/com/google/inject/internal/ErrorsException.java new file mode 100644 index 0000000..b2718cb --- /dev/null +++ b/src/main/java/com/google/inject/internal/ErrorsException.java @@ -0,0 +1,20 @@ +package com.google.inject.internal; + +/** + * Indicates that a result could not be returned while preparing or resolving a binding. The caller + * should {@link Errors#merge(Errors) merge} the errors from this exception with their existing + * errors. + */ +@SuppressWarnings("serial") +public class ErrorsException extends Exception { + + private final Errors errors; + + public ErrorsException(Errors errors) { + this.errors = errors; + } + + public Errors getErrors() { + return errors; + } +} diff --git a/src/main/java/com/google/inject/internal/Exceptions.java b/src/main/java/com/google/inject/internal/Exceptions.java new file mode 100644 index 0000000..e3502ae --- /dev/null +++ b/src/main/java/com/google/inject/internal/Exceptions.java @@ -0,0 +1,46 @@ +package com.google.inject.internal; + +/** + * Rethrows user-code exceptions in wrapped exceptions so that Errors can target the correct + * exception. + */ +class Exceptions { + + /** + * Rethrows the exception (or it's cause, if it has one) directly if possible. + * If it was a checked exception, this wraps the exception in a stack trace + * with no frames, so that the exception is shown immediately with no frames + * above it. + */ + public static RuntimeException rethrowCause(Throwable throwable) { + Throwable cause = throwable; + if (cause.getCause() != null) { + cause = cause.getCause(); + } + return rethrow(cause); + } + + /** + * Rethrows the exception. + */ + public static RuntimeException rethrow(Throwable throwable) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } else if (throwable instanceof Error) { + throw (Error) throwable; + } else { + throw new UnhandledCheckedUserException(throwable); + } + } + + /** + * A marker exception class that we look for in order to unwrap the exception + * into the user exception, to provide a cleaner stack trace. + */ + @SuppressWarnings("serial") + static class UnhandledCheckedUserException extends RuntimeException { + public UnhandledCheckedUserException(Throwable cause) { + super(cause); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ExposedBindingImpl.java b/src/main/java/com/google/inject/internal/ExposedBindingImpl.java new file mode 100644 index 0000000..93d96dc --- /dev/null +++ b/src/main/java/com/google/inject/internal/ExposedBindingImpl.java @@ -0,0 +1,52 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.ExposedBinding; +import com.google.inject.spi.PrivateElements; + +import java.util.Set; + +public final class ExposedBindingImpl extends BindingImpl implements ExposedBinding { + + private final PrivateElements privateElements; + + public ExposedBindingImpl(InjectorImpl injector, Object source, Key key, + InternalFactory factory, PrivateElements privateElements) { + super(injector, key, source, factory, Scoping.UNSCOPED); + this.privateElements = privateElements; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(Key.get(Injector.class))); + } + + public PrivateElements getPrivateElements() { + return privateElements; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ExposedBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("privateElements", privateElements) + .toString(); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } + + // Purposely does not override equals/hashcode, because exposed bindings are only equal to + // themselves right now -- that is, there cannot be "duplicate" exposed bindings. +} diff --git a/src/main/java/com/google/inject/internal/ExposedKeyFactory.java b/src/main/java/com/google/inject/internal/ExposedKeyFactory.java new file mode 100644 index 0000000..d44ed70 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ExposedKeyFactory.java @@ -0,0 +1,40 @@ +package com.google.inject.internal; + +import com.google.inject.Key; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.PrivateElements; + +/** + * This factory exists in a parent injector. When invoked, it retrieves its value from a child + * injector. + */ +final class ExposedKeyFactory implements InternalFactory, CreationListener { + private final Key key; + private final PrivateElements privateElements; + private BindingImpl delegate; + + ExposedKeyFactory(Key key, PrivateElements privateElements) { + this.key = key; + this.privateElements = privateElements; + } + + public void notify(Errors errors) { + InjectorImpl privateInjector = (InjectorImpl) privateElements.getInjector(); + BindingImpl explicitBinding = privateInjector.state.getExplicitBinding(key); + + // validate that the child injector has its own factory. If the getInternalFactory() returns + // this, then that child injector doesn't have a factory (and getExplicitBinding has returned + // its parent's binding instead + if (explicitBinding.getInternalFactory() == this) { + errors.withSource(explicitBinding.getSource()).exposedButNotBound(key); + return; + } + + this.delegate = explicitBinding; + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + return delegate.getInternalFactory().get(errors, context, dependency, linked); + } +} diff --git a/src/main/java/com/google/inject/internal/ExposureBuilder.java b/src/main/java/com/google/inject/internal/ExposureBuilder.java new file mode 100644 index 0000000..612c141 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ExposureBuilder.java @@ -0,0 +1,54 @@ +package com.google.inject.internal; + +import com.google.common.base.Preconditions; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.binder.AnnotatedElementBuilder; + +import java.lang.annotation.Annotation; + +/** + * For private binder's expose() method. + */ +public class ExposureBuilder implements AnnotatedElementBuilder { + private final Binder binder; + private final Object source; + private Key key; + + public ExposureBuilder(Binder binder, Object source, Key key) { + this.binder = binder; + this.source = source; + this.key = key; + } + + protected void checkNotAnnotated() { + if (key.getAnnotationType() != null) { + binder.addError(AbstractBindingBuilder.ANNOTATION_ALREADY_SPECIFIED); + } + } + + public void annotatedWith(Class annotationType) { + Preconditions.checkNotNull(annotationType, "annotationType"); + checkNotAnnotated(); + key = Key.get(key.getTypeLiteral(), annotationType); + } + + public void annotatedWith(Annotation annotation) { + Preconditions.checkNotNull(annotation, "annotation"); + checkNotAnnotated(); + key = Key.get(key.getTypeLiteral(), annotation); + } + + public Key getKey() { + return key; + } + + public Object getSource() { + return source; + } + + @Override + public String toString() { + return "AnnotatedElementBuilder"; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/FactoryProxy.java b/src/main/java/com/google/inject/internal/FactoryProxy.java new file mode 100644 index 0000000..3347bfc --- /dev/null +++ b/src/main/java/com/google/inject/internal/FactoryProxy.java @@ -0,0 +1,53 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.inject.Key; +import com.google.inject.internal.InjectorImpl.JitLimitation; +import com.google.inject.spi.Dependency; + +/** + * A placeholder which enables us to swap in the real factory once the injector is created. + * Used for a linked binding, so that getting the linked binding returns the link's factory. + */ +final class FactoryProxy implements InternalFactory, CreationListener { + + private final InjectorImpl injector; + private final Key key; + private final Key targetKey; + private final Object source; + + private InternalFactory targetFactory; + + FactoryProxy(InjectorImpl injector, Key key, Key targetKey, Object source) { + this.injector = injector; + this.key = key; + this.targetKey = targetKey; + this.source = source; + } + + public void notify(final Errors errors) { + try { + targetFactory = injector.getInternalFactory(targetKey, errors.withSource(source), JitLimitation.NEW_OR_EXISTING_JIT); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + context.pushState(targetKey, source); + try { + return targetFactory.get(errors.withSource(targetKey), context, dependency, true); + } finally { + context.popState(); + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(FactoryProxy.class) + .add("key", key) + .add("provider", targetFactory) + .toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/FailableCache.java b/src/main/java/com/google/inject/internal/FailableCache.java new file mode 100644 index 0000000..89e4525 --- /dev/null +++ b/src/main/java/com/google/inject/internal/FailableCache.java @@ -0,0 +1,44 @@ +package com.google.inject.internal; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +/** + * Lazily creates (and caches) values for keys. If creating the value fails (with errors), an + * exception is thrown on retrieval. + */ +public abstract class FailableCache { + + private final LoadingCache delegate = CacheBuilder.newBuilder().build( + new CacheLoader() { + public Object load(K key) { + Errors errors = new Errors(); + V result = null; + try { + result = FailableCache.this.create(key, errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + return errors.hasErrors() ? errors : result; + } + }); + + protected abstract V create(K key, Errors errors) throws ErrorsException; + + public V get(K key, Errors errors) throws ErrorsException { + Object resultOrError = delegate.getUnchecked(key); + if (resultOrError instanceof Errors) { + errors.merge((Errors) resultOrError); + throw errors.toException(); + } else { + @SuppressWarnings("unchecked") // create returned a non-error result, so this is safe + V result = (V) resultOrError; + return result; + } + } + + boolean remove(K key) { + return delegate.asMap().remove(key) != null; + } +} diff --git a/src/main/java/com/google/inject/internal/InheritingState.java b/src/main/java/com/google/inject/internal/InheritingState.java new file mode 100644 index 0000000..1d79797 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InheritingState.java @@ -0,0 +1,160 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Scope; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; +import com.google.inject.spi.ProvisionListenerBinding; +import com.google.inject.spi.ScopeBinding; +import com.google.inject.spi.TypeConverterBinding; +import com.google.inject.spi.TypeListenerBinding; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +final class InheritingState implements State { + + private final State parent; + + // Must be a linked hashmap in order to preserve order of bindings in Modules. + private final Map, Binding> explicitBindingsMutable = Maps.newLinkedHashMap(); + private final Map, Binding> explicitBindings + = Collections.unmodifiableMap(explicitBindingsMutable); + private final Map, ScopeBinding> scopes = Maps.newHashMap(); + private final List converters = Lists.newArrayList(); + private final List typeListenerBindings = Lists.newArrayList(); + private final List provisionListenerBindings = Lists.newArrayList(); + private final List scannerBindings = Lists.newArrayList(); + private final WeakKeySet blacklistedKeys; + private final Object lock; + + InheritingState(State parent) { + this.parent = checkNotNull(parent, "parent"); + this.lock = (parent == State.NONE) ? this : parent.lock(); + this.blacklistedKeys = new WeakKeySet(lock); + } + + public State parent() { + return parent; + } + + @SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types + public BindingImpl getExplicitBinding(Key key) { + Binding binding = explicitBindings.get(key); + return binding != null ? (BindingImpl) binding : parent.getExplicitBinding(key); + } + + public Map, Binding> getExplicitBindingsThisLevel() { + return explicitBindings; + } + + public void putBinding(Key key, BindingImpl binding) { + explicitBindingsMutable.put(key, binding); + } + + public ScopeBinding getScopeBinding(Class annotationType) { + ScopeBinding scopeBinding = scopes.get(annotationType); + return scopeBinding != null ? scopeBinding : parent.getScopeBinding(annotationType); + } + + public void putScopeBinding(Class annotationType, ScopeBinding scope) { + scopes.put(annotationType, scope); + } + + public Iterable getConvertersThisLevel() { + return converters; + } + + public void addConverter(TypeConverterBinding typeConverterBinding) { + converters.add(typeConverterBinding); + } + + public TypeConverterBinding getConverter( + String stringValue, TypeLiteral type, Errors errors, Object source) { + TypeConverterBinding matchingConverter = null; + for (State s = this; s != State.NONE; s = s.parent()) { + for (TypeConverterBinding converter : s.getConvertersThisLevel()) { + if (converter.getTypeMatcher().matches(type)) { + if (matchingConverter != null) { + errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter); + } + matchingConverter = converter; + } + } + } + return matchingConverter; + } + + public void addTypeListener(TypeListenerBinding listenerBinding) { + typeListenerBindings.add(listenerBinding); + } + + public List getTypeListenerBindings() { + List parentBindings = parent.getTypeListenerBindings(); + List result = + Lists.newArrayListWithCapacity(parentBindings.size() + typeListenerBindings.size()); + result.addAll(parentBindings); + result.addAll(typeListenerBindings); + return result; + } + + public void addProvisionListener(ProvisionListenerBinding listenerBinding) { + provisionListenerBindings.add(listenerBinding); + } + + public List getProvisionListenerBindings() { + List parentBindings = parent.getProvisionListenerBindings(); + List result = + Lists.newArrayListWithCapacity(parentBindings.size() + provisionListenerBindings.size()); + result.addAll(parentBindings); + result.addAll(provisionListenerBindings); + return result; + } + + public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { + scannerBindings.add(scanner); + } + + public List getScannerBindings() { + List parentBindings = parent.getScannerBindings(); + List result = + Lists.newArrayListWithCapacity(parentBindings.size() + scannerBindings.size()); + result.addAll(parentBindings); + result.addAll(scannerBindings); + return result; + } + + public void blacklist(Key key, State state, Object source) { + parent.blacklist(key, state, source); + blacklistedKeys.add(key, state, source); + } + + public boolean isBlacklisted(Key key) { + return blacklistedKeys.contains(key); + } + + public Set getSourcesForBlacklistedKey(Key key) { + return blacklistedKeys.getSources(key); + } + + public Object lock() { + return lock; + } + + public Map, Scope> getScopes() { + ImmutableMap.Builder, Scope> builder = ImmutableMap.builder(); + for (Map.Entry, ScopeBinding> entry : scopes.entrySet()) { + builder.put(entry.getKey(), entry.getValue().getScope()); + } + return builder.build(); + } +} diff --git a/src/main/java/com/google/inject/internal/Initializable.java b/src/main/java/com/google/inject/internal/Initializable.java new file mode 100644 index 0000000..f8df3c5 --- /dev/null +++ b/src/main/java/com/google/inject/internal/Initializable.java @@ -0,0 +1,12 @@ +package com.google.inject.internal; + +/** + * Holds a reference that requires initialization to be performed before it can be used. + */ +interface Initializable { + + /** + * Ensures the reference is initialized, then returns it. + */ + T get(Errors errors) throws ErrorsException; +} diff --git a/src/main/java/com/google/inject/internal/Initializables.java b/src/main/java/com/google/inject/internal/Initializables.java new file mode 100644 index 0000000..9ae6c8d --- /dev/null +++ b/src/main/java/com/google/inject/internal/Initializables.java @@ -0,0 +1,20 @@ +package com.google.inject.internal; + +final class Initializables { + + /** + * Returns an initializable for an instance that requires no initialization. + */ + static Initializable of(final T instance) { + return new Initializable() { + public T get(Errors errors) throws ErrorsException { + return instance; + } + + @Override + public String toString() { + return String.valueOf(instance); + } + }; + } +} diff --git a/src/main/java/com/google/inject/internal/Initializer.java b/src/main/java/com/google/inject/internal/Initializer.java new file mode 100644 index 0000000..dbd80bd --- /dev/null +++ b/src/main/java/com/google/inject/internal/Initializer.java @@ -0,0 +1,181 @@ +package com.google.inject.internal; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.InjectionPoint; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Manages and injects instances at injector-creation time. This is made more complicated by + * instances that request other instances while they're being injected. We overcome this by using + * {@link Initializable}, which attempts to perform injection before use. + * + */ +final class Initializer { + + /** + * the only thread that we'll use to inject members. + */ + private final Thread creatingThread = Thread.currentThread(); + + /** + * zero means everything is injected. + */ + private final CountDownLatch ready = new CountDownLatch(1); + + /** + * Maps from instances that need injection to the MembersInjector that will inject them. + */ + private final Map> pendingMembersInjectors = + Maps.newIdentityHashMap(); + + /** + * Maps instances that need injection to a source that registered them + */ + private final Map> pendingInjection = Maps.newIdentityHashMap(); + + /** + * Registers an instance for member injection when that step is performed. + * + * @param instance an instance that optionally has members to be injected (each annotated with + * @param binding the binding that caused this initializable to be created, if it exists. + * @param source the source location that this injection was requested + * @Inject). + */ + Initializable requestInjection(InjectorImpl injector, T instance, Binding binding, + Object source, Set injectionPoints) { + checkNotNull(source); + + ProvisionListenerStackCallback provisionCallback = + binding == null ? null : injector.provisionListenerStore.get(binding); + + // short circuit if the object has no injections or listeners. + if (instance == null || (injectionPoints.isEmpty() + && !injector.membersInjectorStore.hasTypeListeners() + && (provisionCallback == null || !provisionCallback.hasListeners()))) { + return Initializables.of(instance); + } + + InjectableReference initializable = new InjectableReference( + injector, instance, binding == null ? null : binding.getKey(), provisionCallback, source); + pendingInjection.put(instance, initializable); + return initializable; + } + + /** + * Prepares member injectors for all injected instances. This prompts Guice to do static analysis + * on the injected instances. + */ + void validateOustandingInjections(Errors errors) { + for (InjectableReference reference : pendingInjection.values()) { + try { + pendingMembersInjectors.put(reference.instance, reference.validate(errors)); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + } + + /** + * Performs creation-time injections on all objects that require it. Whenever fulfilling an + * injection depends on another object that requires injection, we inject it first. If the two + * instances are codependent (directly or transitively), ordering of injection is arbitrary. + */ + void injectAll(final Errors errors) { + // loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy + // is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work + for (InjectableReference reference : Lists.newArrayList(pendingInjection.values())) { + try { + reference.get(errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + if (!pendingInjection.isEmpty()) { + throw new AssertionError("Failed to satisfy " + pendingInjection); + } + + ready.countDown(); + } + + private class InjectableReference implements Initializable { + private final InjectorImpl injector; + private final T instance; + private final Object source; + private final Key key; + private final ProvisionListenerStackCallback provisionCallback; + + public InjectableReference(InjectorImpl injector, T instance, Key key, + ProvisionListenerStackCallback provisionCallback, Object source) { + this.injector = injector; + this.key = key; // possibly null! + this.provisionCallback = provisionCallback; // possibly null! + this.instance = checkNotNull(instance, "instance"); + this.source = checkNotNull(source, "source"); + } + + public MembersInjectorImpl validate(Errors errors) throws ErrorsException { + @SuppressWarnings("unchecked") // the type of 'T' is a TypeLiteral + TypeLiteral type = TypeLiteral.get((Class) instance.getClass()); + return injector.membersInjectorStore.get(type, errors.withSource(source)); + } + + /** + * Reentrant. If {@code instance} was registered for injection at injector-creation time, this + * method will ensure that all its members have been injected before returning. + */ + public T get(Errors errors) throws ErrorsException { + if (ready.getCount() == 0) { + return instance; + } + + // just wait for everything to be injected by another thread + if (Thread.currentThread() != creatingThread) { + try { + ready.await(); + return instance; + } catch (InterruptedException e) { + // Give up, since we don't know if our injection is ready + throw new RuntimeException(e); + } + } + + // toInject needs injection, do it right away. we only do this once, even if it fails + if (pendingInjection.remove(instance) != null) { + // safe because we only insert a members injector for the appropriate instance + @SuppressWarnings("unchecked") + MembersInjectorImpl membersInjector = + (MembersInjectorImpl) pendingMembersInjectors.remove(instance); + Preconditions.checkState(membersInjector != null, + "No membersInjector available for instance: %s, from key: %s", instance, key); + + // if in Stage.TOOL, we only want to inject & notify toolable injection points. + // (otherwise we'll inject all of them) + membersInjector.injectAndNotify(instance, + errors.withSource(source), + key, + provisionCallback, + source, + injector.options.stage == Stage.TOOL); + } + + return instance; + } + + @Override + public String toString() { + return instance.toString(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/InjectionRequestProcessor.java b/src/main/java/com/google/inject/internal/InjectionRequestProcessor.java new file mode 100644 index 0000000..3cd8e3e --- /dev/null +++ b/src/main/java/com/google/inject/internal/InjectionRequestProcessor.java @@ -0,0 +1,119 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.inject.ConfigurationException; +import com.google.inject.Stage; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.InjectionRequest; +import com.google.inject.spi.StaticInjectionRequest; + +import java.util.List; +import java.util.Set; + +/** + * Handles {@code Binder.requestInjection} and {@code Binder.requestStaticInjection} commands. + * + */ +final class InjectionRequestProcessor extends AbstractProcessor { + + private final List staticInjections = Lists.newArrayList(); + private final Initializer initializer; + + InjectionRequestProcessor(Errors errors, Initializer initializer) { + super(errors); + this.initializer = initializer; + } + + @Override + public Boolean visit(StaticInjectionRequest request) { + staticInjections.add(new StaticInjection(injector, request)); + return true; + } + + @Override + public Boolean visit(InjectionRequest request) { + Set injectionPoints; + try { + injectionPoints = request.getInjectionPoints(); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + injectionPoints = e.getPartialValue(); + } + + initializer.requestInjection( + injector, request.getInstance(), null, request.getSource(), injectionPoints); + return true; + } + + void validate() { + for (StaticInjection staticInjection : staticInjections) { + staticInjection.validate(); + } + } + + void injectMembers() { + /* + * TODO: If you request both a parent class and one of its + * subclasses, the parent class's static members will be + * injected twice. + */ + for (StaticInjection staticInjection : staticInjections) { + staticInjection.injectMembers(); + } + } + + /** + * A requested static injection. + */ + private class StaticInjection { + final InjectorImpl injector; + final Object source; + final StaticInjectionRequest request; + ImmutableList memberInjectors; + + public StaticInjection(InjectorImpl injector, StaticInjectionRequest request) { + this.injector = injector; + this.source = request.getSource(); + this.request = request; + } + + void validate() { + Errors errorsForMember = errors.withSource(source); + Set injectionPoints; + try { + injectionPoints = request.getInjectionPoints(); + } catch (ConfigurationException e) { + errorsForMember.merge(e.getErrorMessages()); + injectionPoints = e.getPartialValue(); + } + if (injectionPoints != null) { + memberInjectors = injector.membersInjectorStore.getInjectors( + injectionPoints, errorsForMember); + } else { + memberInjectors = ImmutableList.of(); + } + + errors.merge(errorsForMember); + } + + void injectMembers() { + try { + injector.callInContext(new ContextualCallable() { + public Void call(InternalContext context) { + for (SingleMemberInjector memberInjector : memberInjectors) { + // Run injections if we're not in tool stage (ie, PRODUCTION or DEV), + // or if we are in tool stage and the injection point is toolable. + if (injector.options.stage != Stage.TOOL || memberInjector.getInjectionPoint().isToolable()) { + memberInjector.inject(errors, context, null); + } + } + return null; + } + }); + } catch (ErrorsException e) { + throw new AssertionError(); + } + } + } +} diff --git a/src/main/java/com/google/inject/internal/InjectorImpl.java b/src/main/java/com/google/inject/internal/InjectorImpl.java new file mode 100644 index 0000000..9ea9da2 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InjectorImpl.java @@ -0,0 +1,1142 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.ConfigurationException; +import com.google.inject.ImplementedBy; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Module; +import com.google.inject.ProvidedBy; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; +import com.google.inject.Scope; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.util.SourceProvider; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.ConvertedConstantBinding; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ProviderBinding; +import com.google.inject.spi.TypeConverterBinding; +import com.google.inject.util.Providers; + +import java.lang.annotation.Annotation; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +/** + * Default {@link Injector} implementation. + * + */ +final class InjectorImpl implements Injector, Lookups { + public static final TypeLiteral STRING_TYPE = TypeLiteral.get(String.class); + /** + * Synchronization: map value is modified only for the current thread, + * it's ok to read map values of other threads. It can change between your + * calls. + * + * @see #getGlobalInternalContext + */ + private static final ConcurrentMap globalInternalContext = + Maps.newConcurrentMap(); + final State state; + final InjectorImpl parent; + final BindingsMultimap bindingsMultimap = new BindingsMultimap(); + final InjectorOptions options; + /** + * Just-in-time binding cache. Guarded by state.lock() + */ + final Map, BindingImpl> jitBindings = Maps.newHashMap(); + /** + * Cache of Keys that we were unable to create JIT bindings for, so we don't + * keep trying. Also guarded by state.lock(). + */ + final Set> failedJitBindings = Sets.newHashSet(); + /** + * Cached constructor injectors for each type + */ + final ConstructorInjectorStore constructors = new ConstructorInjectorStore(this); + /** + * @see #getGlobalInternalContext + */ + private final ThreadLocal localContext; + Lookups lookups = new DeferredLookups(this); + /** + * Cached field and method injectors for each type. + */ + MembersInjectorStore membersInjectorStore; + /** + * Cached provision listener callbacks for each key. + */ + ProvisionListenerCallbackStore provisionListenerStore; + + InjectorImpl(InjectorImpl parent, State state, InjectorOptions injectorOptions) { + this.parent = parent; + this.state = state; + this.options = injectorOptions; + + if (parent != null) { + localContext = parent.localContext; + } else { + localContext = new ThreadLocal(); + } + } + + /** + * Returns true if the key type is Provider (but not a subclass of Provider). + */ + private static boolean isProvider(Key key) { + return key.getTypeLiteral().getRawType().equals(Provider.class); + } + + private static boolean isTypeLiteral(Key key) { + return key.getTypeLiteral().getRawType().equals(TypeLiteral.class); + } + + private static Key getProvidedKey(Key> key, Errors errors) throws ErrorsException { + Type providerType = key.getTypeLiteral().getType(); + + // If the Provider has no type parameter (raw Provider)... + if (!(providerType instanceof ParameterizedType)) { + throw errors.cannotInjectRawProvider().toException(); + } + + Type entryType = ((ParameterizedType) providerType).getActualTypeArguments()[0]; + + @SuppressWarnings("unchecked") // safe because T came from Key> + Key providedKey = (Key) key.ofType(entryType); + return providedKey; + } + + /** + * Returns true if the key type is MembersInjector (but not a subclass of MembersInjector). + */ + private static boolean isMembersInjector(Key key) { + return key.getTypeLiteral().getRawType().equals(MembersInjector.class) + && key.getAnnotationType() == null; + } + + /** + * Provides access to the internal context for the current injector of all threads. + * One does not need to use this from Guice source code as context could be passed on the stack. + * It is required for custom scopes which are called from Guice and sometimes do require + * access to current internal context, but it is not passed in. Contrary to {@link #localContext} + * it is not used to store injector-specific state, but to provide easy access to the current + * state. + * + * @return unmodifiable map + */ + static Map getGlobalInternalContext() { + return Collections.unmodifiableMap(globalInternalContext); + } + + /** + * Indexes bindings by type. + */ + void index() { + for (Binding binding : state.getExplicitBindingsThisLevel().values()) { + index(binding); + } + } + + void index(Binding binding) { + bindingsMultimap.put(binding.getKey().getTypeLiteral(), binding); + } + + public List> findBindingsByType(TypeLiteral type) { + return bindingsMultimap.getAll(type); + } + + /** + * Returns the binding for {@code key} + */ + public BindingImpl getBinding(Key key) { + Errors errors = new Errors(key); + try { + BindingImpl result = getBindingOrThrow(key, errors, JitLimitation.EXISTING_JIT); + errors.throwConfigurationExceptionIfErrorsExist(); + return result; + } catch (ErrorsException e) { + throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + } + } + + public BindingImpl getExistingBinding(Key key) { + // Check explicit bindings, i.e. bindings created by modules. + BindingImpl explicitBinding = state.getExplicitBinding(key); + if (explicitBinding != null) { + return explicitBinding; + } + synchronized (state.lock()) { + // See if any jit bindings have been created for this key. + for (InjectorImpl injector = this; injector != null; injector = injector.parent) { + @SuppressWarnings("unchecked") + BindingImpl jitBinding = (BindingImpl) injector.jitBindings.get(key); + if (jitBinding != null) { + return jitBinding; + } + } + } + + // If Key is a Provider, we have to see if the type it is providing exists, + // and, if so, we have to create the binding for the provider. + if (isProvider(key)) { + try { + // This is safe because isProvider above ensures that T is a Provider + @SuppressWarnings({"unchecked", "cast"}) + Key providedKey = (Key) getProvidedKey((Key) key, new Errors()); + if (getExistingBinding(providedKey) != null) { + return getBinding(key); + } + } catch (ErrorsException e) { + throw new ConfigurationException(e.getErrors().getMessages()); + } + } + + // No existing binding exists. + return null; + } + + /** + * Gets a binding implementation. First, it check to see if the parent has a binding. If the + * parent has a binding and the binding is scoped, it will use that binding. Otherwise, this + * checks for an explicit binding. If no explicit binding is found, it looks for a just-in-time + * binding. + */ + BindingImpl getBindingOrThrow(Key key, Errors errors, JitLimitation jitType) + throws ErrorsException { + // Check explicit bindings, i.e. bindings created by modules. + BindingImpl binding = state.getExplicitBinding(key); + if (binding != null) { + return binding; + } + + // Look for an on-demand binding. + return getJustInTimeBinding(key, errors, jitType); + } + + public Binding getBinding(Class type) { + return getBinding(Key.get(type)); + } + + public Injector getParent() { + return parent; + } + + public Injector createChildInjector(Iterable modules) { + return new InternalInjectorCreator() + .parentInjector(this) + .addModules(modules) + .build(); + } + + public Injector createChildInjector(Module... modules) { + return createChildInjector(ImmutableList.copyOf(modules)); + } + + /** + * Returns a just-in-time binding for {@code key}, creating it if necessary. + * + * @throws ErrorsException if the binding could not be created. + */ + private BindingImpl getJustInTimeBinding(Key key, Errors errors, JitLimitation jitType) + throws ErrorsException { + + boolean jitOverride = isProvider(key) || isTypeLiteral(key) || isMembersInjector(key); + synchronized (state.lock()) { + // first try to find a JIT binding that we've already created + for (InjectorImpl injector = this; injector != null; injector = injector.parent) { + @SuppressWarnings("unchecked") // we only store bindings that match their key + BindingImpl binding = (BindingImpl) injector.jitBindings.get(key); + + if (binding != null) { + // If we found a JIT binding and we don't allow them, + // fail. (But allow bindings created through TypeConverters.) + if (options.jitDisabled + && jitType == JitLimitation.NO_JIT + && !jitOverride + && !(binding instanceof ConvertedConstantBindingImpl)) { + throw errors.jitDisabled(key).toException(); + } else { + return binding; + } + } + } + + // If we previously failed creating this JIT binding and our Errors has + // already recorded an error, then just directly throw that error. + // We need to do this because it's possible we already cleaned up the + // entry in jitBindings (during cleanup), and we may be trying + // to create it again (in the case of a recursive JIT binding). + // We need both of these guards for different reasons + // failedJitBindings.contains: We want to continue processing if we've never + // failed before, so that our initial error message contains + // as much useful information as possible about what errors exist. + // errors.hasErrors: If we haven't already failed, then it's OK to + // continue processing, to make sure the ultimate error message + // is the correct one. + // See: ImplicitBindingsTest#testRecursiveJitBindingsCleanupCorrectly + // for where this guard compes into play. + if (failedJitBindings.contains(key) && errors.hasErrors()) { + throw errors.toException(); + } + return createJustInTimeBindingRecursive(key, errors, options.jitDisabled, jitType); + } // end synchronized(state.lock()) + } + + private BindingImpl> createMembersInjectorBinding( + Key> key, Errors errors) throws ErrorsException { + Type membersInjectorType = key.getTypeLiteral().getType(); + if (!(membersInjectorType instanceof ParameterizedType)) { + throw errors.cannotInjectRawMembersInjector().toException(); + } + + @SuppressWarnings("unchecked") // safe because T came from Key> + TypeLiteral instanceType = (TypeLiteral) TypeLiteral.get( + ((ParameterizedType) membersInjectorType).getActualTypeArguments()[0]); + MembersInjector membersInjector = membersInjectorStore.get(instanceType, errors); + + InternalFactory> factory = new ConstantFactory>( + Initializables.of(membersInjector)); + + + return new InstanceBindingImpl>(this, key, SourceProvider.UNKNOWN_SOURCE, + factory, ImmutableSet.of(), membersInjector); + } + + /** + * Creates a synthetic binding to {@code Provider}, i.e. a binding to the provider from + * {@code Binding}. + */ + private BindingImpl> createProviderBinding(Key> key, Errors errors) + throws ErrorsException { + Key providedKey = getProvidedKey(key, errors); + BindingImpl delegate = getBindingOrThrow(providedKey, errors, JitLimitation.NO_JIT); + return new ProviderBindingImpl(this, key, delegate); + } + + /** + * Converts a constant string binding to the required type. + * + * @return the binding if it could be resolved, or null if the binding doesn't exist + * @throws com.google.inject.internal.ErrorsException if there was an error resolving the binding + */ + private BindingImpl convertConstantStringBinding(Key key, Errors errors) + throws ErrorsException { + // Find a constant string binding. + Key stringKey = key.ofType(STRING_TYPE); + BindingImpl stringBinding = state.getExplicitBinding(stringKey); + if (stringBinding == null || !stringBinding.isConstant()) { + return null; + } + + String stringValue = stringBinding.getProvider().get(); + Object source = stringBinding.getSource(); + + // Find a matching type converter. + TypeLiteral type = key.getTypeLiteral(); + TypeConverterBinding typeConverterBinding = state.getConverter(stringValue, type, errors, source); + + if (typeConverterBinding == null) { + // No converter can handle the given type. + return null; + } + + // Try to convert the string. A failed conversion results in an error. + try { + @SuppressWarnings("unchecked") // This cast is safe because we double check below. + T converted = (T) typeConverterBinding.getTypeConverter().convert(stringValue, type); + + if (converted == null) { + throw errors.converterReturnedNull(stringValue, source, type, typeConverterBinding) + .toException(); + } + + if (!type.getRawType().isInstance(converted)) { + throw errors.conversionTypeError(stringValue, source, type, typeConverterBinding, converted) + .toException(); + } + + return new ConvertedConstantBindingImpl(this, key, converted, stringBinding, + typeConverterBinding); + } catch (ErrorsException e) { + throw e; + } catch (RuntimeException e) { + throw errors.conversionError(stringValue, source, type, typeConverterBinding, e) + .toException(); + } + } + + void initializeBinding(BindingImpl binding, Errors errors) throws ErrorsException { + if (binding instanceof DelayedInitialize) { + ((DelayedInitialize) binding).initialize(this, errors); + } + } + + void initializeJitBinding(BindingImpl binding, Errors errors) throws ErrorsException { + // Put the partially constructed binding in the map a little early. This enables us to handle + // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl. + // Note: We don't need to synchronize on state.lock() during injector creation. + if (binding instanceof DelayedInitialize) { + Key key = binding.getKey(); + jitBindings.put(key, binding); + boolean successful = false; + DelayedInitialize delayed = (DelayedInitialize) binding; + try { + delayed.initialize(this, errors); + successful = true; + } finally { + if (!successful) { + // We do not pass cb.getInternalConstructor as the second parameter + // so that cached exceptions while constructing it get stored. + // See TypeListenerTest#testTypeListenerThrows + removeFailedJitBinding(binding, null); + cleanup(binding, new HashSet()); + } + } + } + } + + /** + * Iterates through the binding's dependencies to clean up any stray bindings that were leftover + * from a failed JIT binding. This is required because the bindings are eagerly & + * optimistically added to allow circular dependency support, so dependencies may pass where they + * should have failed. + */ + private boolean cleanup(BindingImpl binding, Set encountered) { + boolean bindingFailed = false; + Set> deps = getInternalDependencies(binding); + for (Dependency dep : deps) { + Key depKey = dep.getKey(); + InjectionPoint ip = dep.getInjectionPoint(); + if (encountered.add(depKey)) { // only check if we haven't looked at this key yet + BindingImpl depBinding = jitBindings.get(depKey); + if (depBinding != null) { // if the binding still exists, validate + boolean failed = cleanup(depBinding, encountered); // if children fail, we fail + if (depBinding instanceof ConstructorBindingImpl) { + ConstructorBindingImpl ctorBinding = (ConstructorBindingImpl) depBinding; + ip = ctorBinding.getInternalConstructor(); + if (!ctorBinding.isInitialized()) { + failed = true; + } + } + if (failed) { + removeFailedJitBinding(depBinding, ip); + bindingFailed = true; + } + } else if (state.getExplicitBinding(depKey) == null) { + // ignore keys if they were explicitly bound, but if neither JIT + // nor explicit, it's also invalid & should let parent know. + bindingFailed = true; + } + } + } + return bindingFailed; + } + + /** + * Cleans up any state that may have been cached when constructing the JIT binding. + */ + private void removeFailedJitBinding(Binding binding, InjectionPoint ip) { + failedJitBindings.add(binding.getKey()); + jitBindings.remove(binding.getKey()); + membersInjectorStore.remove(binding.getKey().getTypeLiteral()); + provisionListenerStore.remove(binding); + if (ip != null) { + constructors.remove(ip); + } + } + + /** + * Safely gets the dependencies of possibly not initialized bindings. + */ + @SuppressWarnings("unchecked") + private Set> getInternalDependencies(BindingImpl binding) { + if (binding instanceof ConstructorBindingImpl) { + return ((ConstructorBindingImpl) binding).getInternalDependencies(); + } else if (binding instanceof HasDependencies) { + return ((HasDependencies) binding).getDependencies(); + } else { + return ImmutableSet.of(); + } + } + + /** + * Creates a binding for an injectable type with the given scope. Looks for a scope on the type if + * none is specified. + */ + BindingImpl createUninitializedBinding(Key key, Scoping scoping, Object source, + Errors errors, boolean jitBinding) throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + + ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class); + + // Don't try to inject arrays or enums annotated with @ImplementedBy. + if (rawType.isArray() || (rawType.isEnum() && implementedBy != null)) { + throw errors.missingImplementation(key).toException(); + } + + // Handle TypeLiteral by binding the inner type + if (rawType == TypeLiteral.class) { + @SuppressWarnings("unchecked") // we have to fudge the inner type as Object + BindingImpl binding = (BindingImpl) createTypeLiteralBinding( + (Key>) key, errors); + return binding; + } + + // Handle @ImplementedBy + if (implementedBy != null) { + Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); + return createImplementedByBinding(key, scoping, implementedBy, errors); + } + + // Handle @ProvidedBy. + ProvidedBy providedBy = rawType.getAnnotation(ProvidedBy.class); + if (providedBy != null) { + Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); + return createProvidedByBinding(key, scoping, providedBy, errors); + } + + + return ConstructorBindingImpl.create(this, + key, + null, /* use default constructor */ + source, + scoping, + errors, + jitBinding && options.jitDisabled, + options.atInjectRequired); + } + + /** + * Converts a binding for a {@code Key>} to the value {@code TypeLiteral}. It's + * a bit awkward because we have to pull out the inner type in the type literal. + */ + private BindingImpl> createTypeLiteralBinding( + Key> key, Errors errors) throws ErrorsException { + Type typeLiteralType = key.getTypeLiteral().getType(); + if (!(typeLiteralType instanceof ParameterizedType)) { + throw errors.cannotInjectRawTypeLiteral().toException(); + } + + ParameterizedType parameterizedType = (ParameterizedType) typeLiteralType; + Type innerType = parameterizedType.getActualTypeArguments()[0]; + + // this is unforunate. We don't support building TypeLiterals for type variable like 'T'. If + // this proves problematic, we can probably fix TypeLiteral to support type variables + if (!(innerType instanceof Class) + && !(innerType instanceof GenericArrayType) + && !(innerType instanceof ParameterizedType)) { + throw errors.cannotInjectTypeLiteralOf(innerType).toException(); + } + + @SuppressWarnings("unchecked") // by definition, innerType == T, so this is safe + TypeLiteral value = (TypeLiteral) TypeLiteral.get(innerType); + InternalFactory> factory = new ConstantFactory>( + Initializables.of(value)); + return new InstanceBindingImpl>(this, key, SourceProvider.UNKNOWN_SOURCE, + factory, ImmutableSet.of(), value); + } + + /** + * Creates a binding for a type annotated with @ProvidedBy. + */ + BindingImpl createProvidedByBinding(Key key, Scoping scoping, + ProvidedBy providedBy, Errors errors) throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + Class> providerType = providedBy.value(); + + // Make sure it's not the same type. TODO: Can we check for deeper loops? + if (providerType == rawType) { + throw errors.recursiveProviderType().toException(); + } + + // Assume the provider provides an appropriate type. We double check at runtime. + @SuppressWarnings("unchecked") + Key> providerKey = (Key>) Key.get(providerType); + ProvidedByInternalFactory internalFactory = + new ProvidedByInternalFactory(rawType, providerType, providerKey); + Object source = rawType; + BindingImpl binding = LinkedProviderBindingImpl.createWithInitializer( + this, + key, + source, + Scoping.scope(key, this, internalFactory, source, scoping), + scoping, + providerKey, + internalFactory); + internalFactory.setProvisionListenerCallback(provisionListenerStore.get(binding)); + return binding; + } + + /** + * Creates a binding for a type annotated with @ImplementedBy. + */ + private BindingImpl createImplementedByBinding(Key key, Scoping scoping, + ImplementedBy implementedBy, Errors errors) + throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + Class implementationType = implementedBy.value(); + + // Make sure it's not the same type. TODO: Can we check for deeper cycles? + if (implementationType == rawType) { + throw errors.recursiveImplementationType().toException(); + } + + // Make sure implementationType extends type. + if (!rawType.isAssignableFrom(implementationType)) { + throw errors.notASubtype(implementationType, rawType).toException(); + } + + @SuppressWarnings("unchecked") // After the preceding check, this cast is safe. + Class subclass = (Class) implementationType; + + // Look up the target binding. + final Key targetKey = Key.get(subclass); + final BindingImpl targetBinding = getBindingOrThrow(targetKey, errors, JitLimitation.NEW_OR_EXISTING_JIT); + + InternalFactory internalFactory = new InternalFactory() { + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + context.pushState(targetKey, targetBinding.getSource()); + try { + return targetBinding.getInternalFactory().get( + errors.withSource(targetKey), context, dependency, true); + } finally { + context.popState(); + } + } + }; + + Object source = rawType; + return new LinkedBindingImpl( + this, + key, + source, + Scoping.scope(key, this, internalFactory, source, scoping), + scoping, + targetKey); + } + + /** + * Attempts to create a just-in-time binding for {@code key} in the root injector, falling back to + * other ancestor injectors until this injector is tried. + */ + private BindingImpl createJustInTimeBindingRecursive(Key key, Errors errors, + boolean jitDisabled, JitLimitation jitType) throws ErrorsException { + // ask the parent to create the JIT binding + if (parent != null) { + if (jitType == JitLimitation.NEW_OR_EXISTING_JIT + && jitDisabled && !parent.options.jitDisabled) { + // If the binding would be forbidden here but allowed in a parent, report an error instead + throw errors.jitDisabledInParent(key).toException(); + } + + try { + return parent.createJustInTimeBindingRecursive(key, new Errors(), jitDisabled, + parent.options.jitDisabled ? JitLimitation.NO_JIT : jitType); + } catch (ErrorsException ignored) { + } + } + + // Retrieve the sources before checking for blacklisting to guard against sources becoming null + // due to a full GC happening after calling state.isBlacklisted and + // state.getSourcesForBlacklistedKey. + // TODO(user): Consolidate these two APIs. + Set sources = state.getSourcesForBlacklistedKey(key); + if (state.isBlacklisted(key)) { + throw errors.childBindingAlreadySet(key, sources).toException(); + } + + key = MoreTypes.canonicalizeKey(key); // before storing the key long-term, canonicalize it. + BindingImpl binding = createJustInTimeBinding(key, errors, jitDisabled, jitType); + state.parent().blacklist(key, state, binding.getSource()); + jitBindings.put(key, binding); + return binding; + } + + /** + * Returns a new just-in-time binding created by resolving {@code key}. The strategies used to + * create just-in-time bindings are: + *
    + *
  1. Internalizing Providers. If the requested binding is for {@code Provider}, we delegate + * to the binding for {@code T}. + *
  2. Converting constants. + *
  3. ImplementedBy and ProvidedBy annotations. Only for unannotated keys. + *
  4. The constructor of the raw type. Only for unannotated keys. + *
+ * + * @throws com.google.inject.internal.ErrorsException if the binding cannot be created. + */ + private BindingImpl createJustInTimeBinding(Key key, Errors errors, + boolean jitDisabled, JitLimitation jitType) throws ErrorsException { + int numErrorsBefore = errors.size(); + + // Retrieve the sources before checking for blacklisting to guard against sources becoming null + // due to a full GC happening after calling state.isBlacklisted and + // state.getSourcesForBlacklistedKey. + // TODO(user): Consolidate these two APIs. + Set sources = state.getSourcesForBlacklistedKey(key); + if (state.isBlacklisted(key)) { + throw errors.childBindingAlreadySet(key, sources).toException(); + } + + // Handle cases where T is a Provider. + if (isProvider(key)) { + // These casts are safe. We know T extends Provider and that given Key>, + // createProviderBinding() will return BindingImpl>. + @SuppressWarnings({"unchecked", "cast"}) + BindingImpl binding = (BindingImpl) createProviderBinding((Key) key, errors); + return binding; + } + + // Handle cases where T is a MembersInjector + if (isMembersInjector(key)) { + // These casts are safe. T extends MembersInjector and that given Key>, + // createMembersInjectorBinding() will return BindingImpl>. + @SuppressWarnings({"unchecked", "cast"}) + BindingImpl binding = (BindingImpl) createMembersInjectorBinding((Key) key, errors); + return binding; + } + + // Try to convert a constant string binding to the requested type. + BindingImpl convertedBinding = convertConstantStringBinding(key, errors); + if (convertedBinding != null) { + return convertedBinding; + } + + if (!isTypeLiteral(key) + && jitDisabled + && jitType != JitLimitation.NEW_OR_EXISTING_JIT) { + throw errors.jitDisabled(key).toException(); + } + + // If the key has an annotation... + if (key.getAnnotationType() != null) { + // Look for a binding without annotation attributes or return null. + if (key.hasAttributes() && !options.exactBindingAnnotationsRequired) { + try { + Errors ignored = new Errors(); + return getBindingOrThrow(key.withoutAttributes(), ignored, JitLimitation.NO_JIT); + } catch (ErrorsException ignored) { + // throw with a more appropriate message below + } + } + throw errors.missingImplementation(key).toException(); + } + + Object source = key.getTypeLiteral().getRawType(); + BindingImpl binding = createUninitializedBinding(key, Scoping.UNSCOPED, source, errors, true); + errors.throwIfNewErrors(numErrorsBefore); + initializeJitBinding(binding, errors); + return binding; + } + + InternalFactory getInternalFactory(Key key, Errors errors, JitLimitation jitType) + throws ErrorsException { + return getBindingOrThrow(key, errors, jitType).getInternalFactory(); + } + + public Map, Binding> getBindings() { + return state.getExplicitBindingsThisLevel(); + } + + public Map, Binding> getAllBindings() { + synchronized (state.lock()) { + return new ImmutableMap.Builder, Binding>() + .putAll(state.getExplicitBindingsThisLevel()) + .putAll(jitBindings) + .build(); + } + } + + public Map, Scope> getScopeBindings() { + return ImmutableMap.copyOf(state.getScopes()); + } + + public Set getTypeConverterBindings() { + return ImmutableSet.copyOf(state.getConvertersThisLevel()); + } + + /** + * Returns parameter injectors, or {@code null} if there are no parameters. + */ + SingleParameterInjector[] getParametersInjectors( + List> parameters, Errors errors) throws ErrorsException { + if (parameters.isEmpty()) { + return null; + } + + int numErrorsBefore = errors.size(); + SingleParameterInjector[] result = new SingleParameterInjector[parameters.size()]; + int i = 0; + for (Dependency parameter : parameters) { + try { + result[i++] = createParameterInjector(parameter, errors.withSource(parameter)); + } catch (ErrorsException rethrownBelow) { + // rethrown below + } + } + + errors.throwIfNewErrors(numErrorsBefore); + return result; + } + + SingleParameterInjector createParameterInjector(final Dependency dependency, + final Errors errors) throws ErrorsException { + BindingImpl binding = getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT); + return new SingleParameterInjector(dependency, binding); + } + + @SuppressWarnings("unchecked") // the members injector type is consistent with instance's type + public void injectMembers(Object instance) { + MembersInjector membersInjector = getMembersInjector(instance.getClass()); + membersInjector.injectMembers(instance); + } + + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + Errors errors = new Errors(typeLiteral); + try { + return membersInjectorStore.get(typeLiteral, errors); + } catch (ErrorsException e) { + throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + } + } + + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } + + public Provider getProvider(Class type) { + return getProvider(Key.get(type)); + } + + Provider getProviderOrThrow(final Dependency dependency, Errors errors) throws ErrorsException { + final Key key = dependency.getKey(); + final BindingImpl binding = getBindingOrThrow(key, errors, JitLimitation.NO_JIT); + + return new Provider() { + public T get() { + final Errors errors = new Errors(dependency); + try { + T t = callInContext(new ContextualCallable() { + public T call(InternalContext context) throws ErrorsException { + Dependency previous = context.pushDependency(dependency, binding.getSource()); + try { + return binding.getInternalFactory().get(errors, context, dependency, false); + } finally { + context.popStateAndSetDependency(previous); + } + } + }); + errors.throwIfNewErrors(0); + return t; + } catch (ErrorsException e) { + throw new ProvisionException(errors.merge(e.getErrors()).getMessages()); + } + } + + @Override + public String toString() { + return binding.getInternalFactory().toString(); + } + }; + } + + public Provider getProvider(final Key key) { + Errors errors = new Errors(key); + try { + Provider result = getProviderOrThrow(Dependency.get(key), errors); + errors.throwIfNewErrors(0); + return result; + } catch (ErrorsException e) { + throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + } + } + + public T getInstance(Key key) { + return getProvider(key).get(); + } + + public T getInstance(Class type) { + return getProvider(type).get(); + } + + /** + * Looks up thread local context. Creates (and removes) a new context if necessary. + */ + T callInContext(ContextualCallable callable) throws ErrorsException { + Object[] reference = localContext.get(); + if (reference == null) { + reference = new Object[1]; + localContext.set(reference); + } + Thread currentThread = Thread.currentThread(); + if (reference[0] == null) { + reference[0] = new InternalContext(options); + globalInternalContext.put(currentThread, (InternalContext) reference[0]); + try { + return callable.call((InternalContext) reference[0]); + } finally { + // Only clear contexts if this call created them. + reference[0] = null; + globalInternalContext.remove(currentThread); + } + } else { + Object previousGlobalInternalContext = globalInternalContext.get(currentThread); + globalInternalContext.put(currentThread, (InternalContext) reference[0]); + try { + // Someone else will clean up this local context. + return callable.call((InternalContext) reference[0]); + } finally { + if (previousGlobalInternalContext != null) { + globalInternalContext.put(currentThread, (InternalContext) previousGlobalInternalContext); + } else { + globalInternalContext.remove(currentThread); + } + } + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(Injector.class) + .add("bindings", state.getExplicitBindingsThisLevel().values()) + .toString(); + } + + /** + * some limitations on what just in time bindings are allowed. + */ + enum JitLimitation { + /** + * does not allow just in time bindings + */ + NO_JIT, + /** + * allows existing just in time bindings, but does not allow new ones + */ + EXISTING_JIT, + /** + * allows existing just in time bindings & allows new ones to be created + */ + NEW_OR_EXISTING_JIT, + } + + /** + * Invokes a method. + */ + interface MethodInvoker { + Object invoke(Object target, Object... parameters) + throws IllegalAccessException, InvocationTargetException; + } + + /** + * Options that control how the injector behaves. + */ + static class InjectorOptions { + final Stage stage; + final boolean jitDisabled; + final boolean disableCircularProxies; + final boolean atInjectRequired; + final boolean exactBindingAnnotationsRequired; + + InjectorOptions(Stage stage, boolean jitDisabled, boolean disableCircularProxies, + boolean atInjectRequired, boolean exactBindingAnnotationsRequired) { + this.stage = stage; + this.jitDisabled = jitDisabled; + this.disableCircularProxies = disableCircularProxies; + this.atInjectRequired = atInjectRequired; + this.exactBindingAnnotationsRequired = exactBindingAnnotationsRequired; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("stage", stage) + .add("jitDisabled", jitDisabled) + .add("disableCircularProxies", disableCircularProxies) + .add("atInjectRequired", atInjectRequired) + .add("exactBindingAnnotationsRequired", exactBindingAnnotationsRequired) + .toString(); + } + } + + private static class ProviderBindingImpl extends BindingImpl> + implements ProviderBinding>, HasDependencies { + final BindingImpl providedBinding; + + ProviderBindingImpl(InjectorImpl injector, Key> key, Binding providedBinding) { + super(injector, key, providedBinding.getSource(), createInternalFactory(providedBinding), + Scoping.UNSCOPED); + this.providedBinding = (BindingImpl) providedBinding; + } + + static InternalFactory> createInternalFactory(Binding providedBinding) { + final Provider provider = providedBinding.getProvider(); + return new InternalFactory>() { + public Provider get(Errors errors, InternalContext context, Dependency dependency, boolean linked) { + return provider; + } + }; + } + + public Key getProvidedKey() { + return providedBinding.getKey(); + } + + public V acceptTargetVisitor(BindingTargetVisitor, V> visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ProviderBinding.class) + .add("key", getKey()) + .add("providedKey", getProvidedKey()) + .toString(); + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(getProvidedKey())); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ProviderBindingImpl) { + ProviderBindingImpl o = (ProviderBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(providedBinding, o.providedBinding); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping(), providedBinding); + } + } + + private static class ConvertedConstantBindingImpl + extends BindingImpl implements ConvertedConstantBinding { + final T value; + final Provider provider; + final Binding originalBinding; + final TypeConverterBinding typeConverterBinding; + + ConvertedConstantBindingImpl( + InjectorImpl injector, Key key, T value, Binding originalBinding, + TypeConverterBinding typeConverterBinding) { + super(injector, key, originalBinding.getSource(), + new ConstantFactory(Initializables.of(value)), Scoping.UNSCOPED); + this.value = value; + provider = Providers.of(value); + this.originalBinding = originalBinding; + this.typeConverterBinding = typeConverterBinding; + } + + @Override + public Provider getProvider() { + return provider; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public T getValue() { + return value; + } + + public TypeConverterBinding getTypeConverterBinding() { + return typeConverterBinding; + } + + public Key getSourceKey() { + return originalBinding.getKey(); + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(getSourceKey())); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ConvertedConstantBinding.class) + .add("key", getKey()) + .add("sourceKey", getSourceKey()) + .add("value", value) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ConvertedConstantBindingImpl) { + ConvertedConstantBindingImpl o = (ConvertedConstantBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(value, o.value); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping(), value); + } + } + + private static class BindingsMultimap { + final Map, List>> multimap = Maps.newHashMap(); + + void put(TypeLiteral type, Binding binding) { + List> bindingsForType = multimap.get(type); + if (bindingsForType == null) { + bindingsForType = Lists.newArrayList(); + multimap.put(type, bindingsForType); + } + bindingsForType.add(binding); + } + + + @SuppressWarnings("unchecked") + // safe because we only put matching entries into the map + List> getAll(TypeLiteral type) { + List> bindings = multimap.get(type); + return bindings != null + ? Collections.>unmodifiableList((List) multimap.get(type)) + : ImmutableList.>of(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/InjectorOptionsProcessor.java b/src/main/java/com/google/inject/internal/InjectorOptionsProcessor.java new file mode 100644 index 0000000..d84e8c3 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InjectorOptionsProcessor.java @@ -0,0 +1,71 @@ +package com.google.inject.internal; + +import com.google.inject.Stage; +import com.google.inject.internal.InjectorImpl.InjectorOptions; +import com.google.inject.spi.DisableCircularProxiesOption; +import com.google.inject.spi.RequireAtInjectOnConstructorsOption; +import com.google.inject.spi.RequireExactBindingAnnotationsOption; +import com.google.inject.spi.RequireExplicitBindingsOption; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * A processor to gather injector options. + */ +class InjectorOptionsProcessor extends AbstractProcessor { + + private boolean disableCircularProxies = false; + private boolean jitDisabled = false; + private boolean atInjectRequired = false; + private boolean exactBindingAnnotationsRequired = false; + + InjectorOptionsProcessor(Errors errors) { + super(errors); + } + + @Override + public Boolean visit(DisableCircularProxiesOption option) { + disableCircularProxies = true; + return true; + } + + @Override + public Boolean visit(RequireExplicitBindingsOption option) { + jitDisabled = true; + return true; + } + + @Override + public Boolean visit(RequireAtInjectOnConstructorsOption option) { + atInjectRequired = true; + return true; + } + + @Override + public Boolean visit(RequireExactBindingAnnotationsOption option) { + exactBindingAnnotationsRequired = true; + return true; + } + + InjectorOptions getOptions(Stage stage, InjectorOptions parentOptions) { + checkNotNull(stage, "stage must be set"); + if (parentOptions == null) { + return new InjectorOptions( + stage, + jitDisabled, + disableCircularProxies, + atInjectRequired, + exactBindingAnnotationsRequired); + } else { + checkState(stage == parentOptions.stage, "child & parent stage don't match"); + return new InjectorOptions( + stage, + jitDisabled || parentOptions.jitDisabled, + disableCircularProxies || parentOptions.disableCircularProxies, + atInjectRequired || parentOptions.atInjectRequired, + exactBindingAnnotationsRequired || parentOptions.exactBindingAnnotationsRequired); + } + } + +} diff --git a/src/main/java/com/google/inject/internal/InjectorShell.java b/src/main/java/com/google/inject/internal/InjectorShell.java new file mode 100644 index 0000000..542b3fd --- /dev/null +++ b/src/main/java/com/google/inject/internal/InjectorShell.java @@ -0,0 +1,296 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.Stage; +import com.google.inject.internal.InjectorImpl.InjectorOptions; +import com.google.inject.internal.util.SourceProvider; +import com.google.inject.internal.util.Stopwatch; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.Element; +import com.google.inject.spi.Elements; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; +import com.google.inject.spi.PrivateElements; +import com.google.inject.spi.ProvisionListenerBinding; +import com.google.inject.spi.TypeListenerBinding; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.inject.Scopes.SINGLETON; + +/** + * A partially-initialized injector. See {@link InternalInjectorCreator}, which + * uses this to build a tree of injectors in batch. + */ +final class InjectorShell { + + private final List elements; + private final InjectorImpl injector; + + private InjectorShell(Builder builder, List elements, InjectorImpl injector) { + this.elements = elements; + this.injector = injector; + } + + /** + * The Injector is a special case because we allow both parent and child injectors to both have + * a binding for that key. + */ + private static void bindInjector(InjectorImpl injector) { + Key key = Key.get(Injector.class); + InjectorFactory injectorFactory = new InjectorFactory(injector); + injector.state.putBinding(key, + new ProviderInstanceBindingImpl(injector, key, SourceProvider.UNKNOWN_SOURCE, + injectorFactory, Scoping.UNSCOPED, injectorFactory, + ImmutableSet.of())); + } + + /** + * The Logger is a special case because it knows the injection point of the injected member. It's + * the only binding that does this. + */ + /*private static void bindLogger(InjectorImpl injector) { + Key key = Key.get(Logger.class); + LoggerFactory loggerFactory = new LoggerFactory(); + injector.state.putBinding(key, + new ProviderInstanceBindingImpl(injector, key, + SourceProvider.UNKNOWN_SOURCE, loggerFactory, Scoping.UNSCOPED, + loggerFactory, ImmutableSet.of())); + }*/ + + private static void bindStage(InjectorImpl injector, Stage stage) { + Key key = Key.get(Stage.class); + InstanceBindingImpl stageBinding = new InstanceBindingImpl( + injector, + key, + SourceProvider.UNKNOWN_SOURCE, + new ConstantFactory(Initializables.of(stage)), + ImmutableSet.of(), + stage); + injector.state.putBinding(key, stageBinding); + } + + InjectorImpl getInjector() { + return injector; + } + + List getElements() { + return elements; + } + + static class Builder { + private final List elements = Lists.newArrayList(); + private final List modules = Lists.newArrayList(); + + /** + * lazily constructed + */ + private State state; + + private InjectorImpl parent; + private InjectorOptions options; + private Stage stage; + + /** + * null unless this exists in a {@link Binder#newPrivateBinder private environment} + */ + private PrivateElementsImpl privateElements; + + Builder stage(Stage stage) { + this.stage = stage; + return this; + } + + Builder parent(InjectorImpl parent) { + this.parent = parent; + this.state = new InheritingState(parent.state); + this.options = parent.options; + this.stage = options.stage; + return this; + } + + Builder privateElements(PrivateElements privateElements) { + this.privateElements = (PrivateElementsImpl) privateElements; + this.elements.addAll(privateElements.getElements()); + return this; + } + + void addModules(Iterable modules) { + if (modules != null) { + for (Module module : modules) { + this.modules.add(module); + } + } + } + + Stage getStage() { + return options.stage; + } + + /** + * Synchronize on this before calling {@link #build}. + */ + Object lock() { + return getState().lock(); + } + + /** + * Creates and returns the injector shells for the current modules. Multiple shells will be + * returned if any modules contain {@link Binder#newPrivateBinder private environments}. The + * primary injector will be first in the returned list. + */ + List build( + Initializer initializer, + ProcessedBindingData bindingData, + Stopwatch stopwatch, + Errors errors) { + checkState(stage != null, "Stage not initialized"); + checkState(privateElements == null || parent != null, "PrivateElements with no parent"); + checkState(state != null, "no state. Did you remember to lock() ?"); + + // bind Singleton if this is a top-level injector + if (parent == null) { + modules.add(0, new RootModule()); + } else { + modules.add(0, new InheritedScannersModule(parent.state)); + } + elements.addAll(Elements.getElements(stage, modules)); + + // Look for injector-changing options + InjectorOptionsProcessor optionsProcessor = new InjectorOptionsProcessor(errors); + optionsProcessor.process(null, elements); + options = optionsProcessor.getOptions(stage, options); + + InjectorImpl injector = new InjectorImpl(parent, state, options); + if (privateElements != null) { + privateElements.initInjector(injector); + } + + // add default type converters if this is a top-level injector + if (parent == null) { + TypeConverterBindingProcessor.prepareBuiltInConverters(injector); + } + + stopwatch.resetAndLog("Module execution"); + + new MessageProcessor(errors).process(injector, elements); + + new ListenerBindingProcessor(errors).process(injector, elements); + List typeListenerBindings = injector.state.getTypeListenerBindings(); + injector.membersInjectorStore = new MembersInjectorStore(injector, typeListenerBindings); + List provisionListenerBindings = + injector.state.getProvisionListenerBindings(); + injector.provisionListenerStore = + new ProvisionListenerCallbackStore(provisionListenerBindings); + stopwatch.resetAndLog("TypeListeners & ProvisionListener creation"); + + new ScopeBindingProcessor(errors).process(injector, elements); + stopwatch.resetAndLog("Scopes creation"); + + new TypeConverterBindingProcessor(errors).process(injector, elements); + stopwatch.resetAndLog("Converters creation"); + + bindStage(injector, stage); + bindInjector(injector); + //bindLogger(injector); + + // Process all normal bindings, then UntargettedBindings. + // This is necessary because UntargettedBindings can create JIT bindings + // and need all their other dependencies set up ahead of time. + new BindingProcessor(errors, initializer, bindingData).process(injector, elements); + new UntargettedBindingProcessor(errors, bindingData).process(injector, elements); + stopwatch.resetAndLog("Binding creation"); + + new ModuleAnnotatedMethodScannerProcessor(errors).process(injector, elements); + stopwatch.resetAndLog("Module annotated method scanners creation"); + + List injectorShells = Lists.newArrayList(); + injectorShells.add(new InjectorShell(this, elements, injector)); + + // recursively build child shells + PrivateElementProcessor processor = new PrivateElementProcessor(errors); + processor.process(injector, elements); + for (Builder builder : processor.getInjectorShellBuilders()) { + injectorShells.addAll(builder.build(initializer, bindingData, stopwatch, errors)); + } + stopwatch.resetAndLog("Private environment creation"); + + return injectorShells; + } + + private State getState() { + if (state == null) { + state = new InheritingState(State.NONE); + } + return state; + } + } + + private static class InjectorFactory implements InternalFactory, Provider { + private final Injector injector; + + private InjectorFactory(Injector injector) { + this.injector = injector; + } + + public Injector get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + return injector; + } + + public Injector get() { + return injector; + } + + public String toString() { + return "Provider"; + } + } + + /*private static class LoggerFactory implements InternalFactory, Provider { + public Logger get(Errors errors, InternalContext context, Dependency dependency, boolean linked) { + InjectionPoint injectionPoint = dependency.getInjectionPoint(); + return injectionPoint == null + ? Logger.getAnonymousLogger() + : Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); + } + + public Logger get() { + return Logger.getAnonymousLogger(); + } + + public String toString() { + return "Provider"; + } + }*/ + + private static class RootModule implements Module { + public void configure(Binder binder) { + binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE); + binder.bindScope(Singleton.class, SINGLETON); + binder.bindScope(javax.inject.Singleton.class, SINGLETON); + } + } + + private static class InheritedScannersModule implements Module { + private final State state; + + InheritedScannersModule(State state) { + this.state = state; + } + + public void configure(Binder binder) { + for (ModuleAnnotatedMethodScannerBinding binding : state.getScannerBindings()) { + binding.applyTo(binder); + } + } + } +} diff --git a/src/main/java/com/google/inject/internal/InstanceBindingImpl.java b/src/main/java/com/google/inject/internal/InstanceBindingImpl.java new file mode 100644 index 0000000..bb72868 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InstanceBindingImpl.java @@ -0,0 +1,102 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.InstanceBinding; +import com.google.inject.util.Providers; + +import java.util.Set; + +final class InstanceBindingImpl extends BindingImpl implements InstanceBinding { + + final T instance; + final Provider provider; + final ImmutableSet injectionPoints; + + public InstanceBindingImpl(InjectorImpl injector, Key key, Object source, + InternalFactory internalFactory, Set injectionPoints, + T instance) { + super(injector, key, source, internalFactory, Scoping.EAGER_SINGLETON); + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + this.instance = instance; + this.provider = Providers.of(instance); + } + + public InstanceBindingImpl(Object source, Key key, Scoping scoping, + Set injectionPoints, T instance) { + super(source, key, scoping); + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + this.instance = instance; + this.provider = Providers.of(instance); + } + + @Override + public Provider getProvider() { + return this.provider; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public T getInstance() { + return instance; + } + + public Set getInjectionPoints() { + return injectionPoints; + } + + public Set> getDependencies() { + return instance instanceof HasDependencies + ? ImmutableSet.copyOf(((HasDependencies) instance).getDependencies()) + : Dependency.forInjectionPoints(injectionPoints); + } + + public BindingImpl withScoping(Scoping scoping) { + return new InstanceBindingImpl(getSource(), getKey(), scoping, injectionPoints, instance); + } + + public BindingImpl withKey(Key key) { + return new InstanceBindingImpl(getSource(), key, getScoping(), injectionPoints, instance); + } + + public void applyTo(Binder binder) { + // instance bindings aren't scoped + binder.withSource(getSource()).bind(getKey()).toInstance(instance); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(InstanceBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("instance", instance) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof InstanceBindingImpl) { + InstanceBindingImpl o = (InstanceBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(instance, o.instance); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping()); + } +} diff --git a/src/main/java/com/google/inject/internal/InternalContext.java b/src/main/java/com/google/inject/internal/InternalContext.java new file mode 100644 index 0000000..298c4c8 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalContext.java @@ -0,0 +1,136 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.inject.Key; +import com.google.inject.internal.InjectorImpl.InjectorOptions; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.DependencyAndSource; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * Internal context. Used to coordinate injections and support circular + * dependencies. + */ +final class InternalContext { + + private final InjectorOptions options; + /** + * Keeps track of the hierarchy of types needed during injection. + */ + private final DependencyStack state = new DependencyStack(); + private Map> constructionContexts = Maps.newHashMap(); + /** + * Keeps track of the type that is currently being requested for injection. + */ + private Dependency dependency; + + InternalContext(InjectorOptions options) { + this.options = options; + } + + public InjectorOptions getInjectorOptions() { + return options; + } + + @SuppressWarnings("unchecked") + public ConstructionContext getConstructionContext(Object key) { + ConstructionContext constructionContext + = (ConstructionContext) constructionContexts.get(key); + if (constructionContext == null) { + constructionContext = new ConstructionContext(); + constructionContexts.put(key, constructionContext); + } + return constructionContext; + } + + public Dependency getDependency() { + return dependency; + } + + /** + * Sets the new current dependency & adds it to the state. + */ + public Dependency pushDependency(Dependency dependency, Object source) { + Dependency previous = this.dependency; + this.dependency = dependency; + state.add(dependency, source); + return previous; + } + + /** + * Pops the current state & sets the new dependency. + */ + public void popStateAndSetDependency(Dependency newDependency) { + state.pop(); + this.dependency = newDependency; + } + + /** + * Adds to the state without setting the dependency. + */ + public void pushState(Key key, Object source) { + state.add(key, source); + } + + /** + * Pops from the state without setting a dependency. + */ + public void popState() { + state.pop(); + } + + /** + * Returns the current dependency chain (all the state). + */ + public List getDependencyChain() { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < state.size(); i += 2) { + Object evenEntry = state.get(i); + Dependency dependency; + if (evenEntry instanceof Key) { + dependency = Dependency.get((Key) evenEntry); + } else { + dependency = (Dependency) evenEntry; + } + builder.add(new DependencyAndSource(dependency, state.get(i + 1))); + } + return builder.build(); + } + + /** + * Keeps track of the hierarchy of types needed during injection. + * + *

This is a pairwise combination of dependencies and sources, with dependencies or keys on + * even indices, and sources on odd indices. This structure is to avoid the memory overhead of + * DependencyAndSource objects, which can add to several tens of megabytes in large applications. + */ + private static final class DependencyStack { + private Object[] elements = new Object[16]; + private int size = 0; + + public void add(Object dependencyOrKey, Object source) { + if (elements.length < size + 2) { + elements = Arrays.copyOf(elements, (elements.length * 3) / 2 + 2); + } + elements[size++] = dependencyOrKey; + elements[size++] = source; + } + + public void pop() { + elements[--size] = null; + elements[--size] = null; + } + + public Object get(int i) { + return elements[i]; + } + + public int size() { + return size; + } + } +} diff --git a/src/main/java/com/google/inject/internal/InternalFactory.java b/src/main/java/com/google/inject/internal/InternalFactory.java new file mode 100644 index 0000000..99a564b --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalFactory.java @@ -0,0 +1,20 @@ +package com.google.inject.internal; + +import com.google.inject.spi.Dependency; + +/** + * Creates objects which will be injected. + */ +interface InternalFactory { + + /** + * Creates an object to be injected. + * + * @param context of this injection + * @param linked true if getting as a result of a linked binding + * @return instance to be injected + * @throws com.google.inject.internal.ErrorsException if a value cannot be provided + */ + T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException; +} diff --git a/src/main/java/com/google/inject/internal/InternalFactoryToInitializableAdapter.java b/src/main/java/com/google/inject/internal/InternalFactoryToInitializableAdapter.java new file mode 100644 index 0000000..bb8520c --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalFactoryToInitializableAdapter.java @@ -0,0 +1,45 @@ +package com.google.inject.internal; + +import com.google.inject.spi.Dependency; +import com.google.inject.spi.ProviderInstanceBinding; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Adapts {@link ProviderInstanceBinding} providers, ensuring circular proxies + * fail (or proxy) properly. + */ +final class InternalFactoryToInitializableAdapter extends ProviderInternalFactory { + + private final ProvisionListenerStackCallback provisionCallback; + private final Initializable> initializable; + + public InternalFactoryToInitializableAdapter( + Initializable> initializable, + Object source, ProvisionListenerStackCallback provisionCallback) { + super(source); + this.provisionCallback = checkNotNull(provisionCallback, "provisionCallback"); + this.initializable = checkNotNull(initializable, "provider"); + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + return circularGet(initializable.get(errors), errors, context, dependency, + provisionCallback); + } + + @Override + protected T provision(javax.inject.Provider provider, Errors errors, + Dependency dependency, ConstructionContext constructionContext) throws ErrorsException { + try { + return super.provision(provider, errors, dependency, constructionContext); + } catch (RuntimeException userException) { + throw errors.withSource(source).errorInProvider(userException).toException(); + } + } + + @Override + public String toString() { + return initializable.toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/InternalFactoryToProviderAdapter.java b/src/main/java/com/google/inject/internal/InternalFactoryToProviderAdapter.java new file mode 100644 index 0000000..fa5025e --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalFactoryToProviderAdapter.java @@ -0,0 +1,32 @@ +package com.google.inject.internal; + +import com.google.inject.Provider; +import com.google.inject.spi.Dependency; + +import static com.google.common.base.Preconditions.checkNotNull; + +final class InternalFactoryToProviderAdapter implements InternalFactory { + + private final Provider provider; + private final Object source; + + public InternalFactoryToProviderAdapter(Provider provider, Object source) { + this.provider = checkNotNull(provider, "provider"); + this.source = checkNotNull(source, "source"); + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + // TODO(sameb): Does this need to push state into the context? + try { + return errors.checkForNull(provider.get(), source, dependency); + } catch (RuntimeException userException) { + throw errors.withSource(source).errorInProvider(userException).toException(); + } + } + + @Override + public String toString() { + return provider.toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/InternalFlags.java b/src/main/java/com/google/inject/internal/InternalFlags.java new file mode 100644 index 0000000..cc6db1f --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalFlags.java @@ -0,0 +1,132 @@ +package com.google.inject.internal; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Contains flags for Guice. + */ +public class InternalFlags { + + private static final IncludeStackTraceOption INCLUDE_STACK_TRACES + = parseIncludeStackTraceOption(); + + private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING + = parseCustomClassLoadingOption(); + + private static final NullableProvidesOption NULLABLE_PROVIDES + = parseNullableProvidesOption(NullableProvidesOption.ERROR); + + public static IncludeStackTraceOption getIncludeStackTraceOption() { + return INCLUDE_STACK_TRACES; + } + + public static CustomClassLoadingOption getCustomClassLoadingOption() { + return CUSTOM_CLASS_LOADING; + } + + public static NullableProvidesOption getNullableProvidesOption() { + return NULLABLE_PROVIDES; + } + + private static IncludeStackTraceOption parseIncludeStackTraceOption() { + return getSystemOption("guice_include_stack_traces", + IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE); + } + + private static CustomClassLoadingOption parseCustomClassLoadingOption() { + return getSystemOption("guice_custom_class_loading", + CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF); + } + + private static NullableProvidesOption parseNullableProvidesOption( + NullableProvidesOption defaultValue) { + return getSystemOption("guice_check_nullable_provides_params", defaultValue); + } + + /** + * Gets the system option indicated by the specified key; runs as a privileged action. + * + * @param name of the system option + * @param defaultValue if the option is not set + * @return value of the option, defaultValue if not set + */ + private static > T getSystemOption(final String name, T defaultValue) { + return getSystemOption(name, defaultValue, defaultValue); + } + + /** + * Gets the system option indicated by the specified key; runs as a privileged action. + * + * @param name of the system option + * @param defaultValue if the option is not set + * @param secureValue if the security manager disallows access to the option + * @return value of the option, defaultValue if not set, secureValue if no access + */ + private static > T getSystemOption(final String name, T defaultValue, + T secureValue) { + Class enumType = defaultValue.getDeclaringClass(); + String value = null; + try { + value = AccessController.doPrivileged(new PrivilegedAction() { + public String run() { + return System.getProperty(name); + } + }); + return (value != null && value.length() > 0) ? Enum.valueOf(enumType, value) : defaultValue; + } catch (SecurityException e) { + return secureValue; + } catch (IllegalArgumentException e) { + //logger.warning(value + " is not a valid flag value for " + name + ". " + // + " Values must be one of " + Arrays.asList(enumType.getEnumConstants())); + return defaultValue; + } + } + + /** + * The options for Guice stack trace collection. + */ + public enum IncludeStackTraceOption { + /** + * No stack trace collection + */ + OFF, + /** + * Minimum stack trace collection (Default) + */ + ONLY_FOR_DECLARING_SOURCE, + /** + * Full stack trace for everything + */ + COMPLETE + } + + /** + * The options for Guice custom class loading. + */ + public enum CustomClassLoadingOption { + /** + * No custom class loading + */ + OFF, + /** + * Automatically bridge between class loaders (Default) + */ + BRIDGE + } + + public enum NullableProvidesOption { + /** + * Ignore null parameters to @Provides methods. + */ + IGNORE, + /** + * Warn if null parameters are passed to non-@Nullable parameters of provides methods. + */ + WARN, + /** + * Error if null parameters are passed to non-@Nullable parameters of provides parameters + */ + ERROR + } +} diff --git a/src/main/java/com/google/inject/internal/InternalInjectorCreator.java b/src/main/java/com/google/inject/internal/InternalInjectorCreator.java new file mode 100644 index 0000000..5d9bec3 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalInjectorCreator.java @@ -0,0 +1,308 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.util.Stopwatch; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.TypeConverterBinding; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Builds a tree of injectors. This is a primary injector, plus child injectors needed for each + * {@code Binder.newPrivateBinder() private environment}. The primary injector is not necessarily a + * top-level injector. + * + *

Injector construction happens in two phases. + *

    + *
  1. Static building. In this phase, we interpret commands, create bindings, and inspect + * dependencies. During this phase, we hold a lock to ensure consistency with parent injectors. + * No user code is executed in this phase.
  2. + *
  3. Dynamic injection. In this phase, we call user code. We inject members that requested + * injection. This may require user's objects be created and their providers be called. And we + * create eager singletons. In this phase, user code may have started other threads. This phase + * is not executed for injectors created using {@link Stage#TOOL the tool stage}
  4. + *
+ * + */ +public final class InternalInjectorCreator { + + private final Stopwatch stopwatch = new Stopwatch(); + private final Errors errors = new Errors(); + + private final Initializer initializer = new Initializer(); + private final ProcessedBindingData bindingData; + private final InjectionRequestProcessor injectionRequestProcessor; + + private final InjectorShell.Builder shellBuilder = new InjectorShell.Builder(); + private List shells; + + public InternalInjectorCreator() { + injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer); + bindingData = new ProcessedBindingData(); + } + + public InternalInjectorCreator stage(Stage stage) { + shellBuilder.stage(stage); + return this; + } + + /** + * Sets the parent of the injector to-be-constructed. As a side effect, this sets this injector's + * stage to the stage of {@code parent}. + */ + public InternalInjectorCreator parentInjector(InjectorImpl parent) { + shellBuilder.parent(parent); + return this; + } + + public InternalInjectorCreator addModules(Iterable modules) { + shellBuilder.addModules(modules); + return this; + } + + public Injector build() { + + // Synchronize while we're building up the bindings and other injector state. This ensures that + // the JIT bindings in the parent injector don't change while we're being built + synchronized (shellBuilder.lock()) { + shells = shellBuilder.build(initializer, bindingData, stopwatch, errors); + stopwatch.resetAndLog("Injector construction"); + + initializeStatically(); + } + + injectDynamically(); + + if (shellBuilder.getStage() == Stage.TOOL) { + // wrap the primaryInjector in a ToolStageInjector + // to prevent non-tool-friendy methods from being called. + return new ToolStageInjector(primaryInjector()); + } else { + return primaryInjector(); + } + } + + /** + * Initialize and validate everything. + */ + private void initializeStatically() { + bindingData.initializeBindings(); + stopwatch.resetAndLog("Binding initialization"); + + for (InjectorShell shell : shells) { + shell.getInjector().index(); + } + stopwatch.resetAndLog("Binding indexing"); + + injectionRequestProcessor.process(shells); + stopwatch.resetAndLog("Collecting injection requests"); + + bindingData.runCreationListeners(errors); + stopwatch.resetAndLog("Binding validation"); + + injectionRequestProcessor.validate(); + stopwatch.resetAndLog("Static validation"); + + initializer.validateOustandingInjections(errors); + stopwatch.resetAndLog("Instance member validation"); + + new LookupProcessor(errors).process(shells); + for (InjectorShell shell : shells) { + ((DeferredLookups) shell.getInjector().lookups).initialize(errors); + } + stopwatch.resetAndLog("Provider verification"); + + for (InjectorShell shell : shells) { + if (!shell.getElements().isEmpty()) { + throw new AssertionError("Failed to execute " + shell.getElements()); + } + } + + errors.throwCreationExceptionIfErrorsExist(); + } + + /** + * Returns the injector being constructed. This is not necessarily the root injector. + */ + private Injector primaryInjector() { + return shells.get(0).getInjector(); + } + + /** + * Inject everything that can be injected. This method is intentionally not synchronized. If we + * locked while injecting members (ie. running user code), things would deadlock should the user + * code build a just-in-time binding from another thread. + */ + private void injectDynamically() { + injectionRequestProcessor.injectMembers(); + stopwatch.resetAndLog("Static member injection"); + + initializer.injectAll(errors); + stopwatch.resetAndLog("Instance injection"); + errors.throwCreationExceptionIfErrorsExist(); + + if (shellBuilder.getStage() != Stage.TOOL) { + for (InjectorShell shell : shells) { + loadEagerSingletons(shell.getInjector(), shellBuilder.getStage(), errors); + } + stopwatch.resetAndLog("Preloading singletons"); + } + errors.throwCreationExceptionIfErrorsExist(); + } + + /** + * Loads eager singletons, or all singletons if we're in Stage.PRODUCTION. Bindings discovered + * while we're binding these singletons are not be eager. + */ + void loadEagerSingletons(InjectorImpl injector, Stage stage, final Errors errors) { + @SuppressWarnings("unchecked") // casting Collection to Collection is safe + Iterable> candidateBindings = ImmutableList.copyOf(Iterables.concat( + (Collection) injector.state.getExplicitBindingsThisLevel().values(), + injector.jitBindings.values())); + for (final BindingImpl binding : candidateBindings) { + if (isEagerSingleton(injector, binding, stage)) { + try { + injector.callInContext(new ContextualCallable() { + Dependency dependency = Dependency.get(binding.getKey()); + + public Void call(InternalContext context) { + Dependency previous = context.pushDependency(dependency, binding.getSource()); + Errors errorsForBinding = errors.withSource(dependency); + try { + binding.getInternalFactory().get(errorsForBinding, context, dependency, false); + } catch (ErrorsException e) { + errorsForBinding.merge(e.getErrors()); + } finally { + context.popStateAndSetDependency(previous); + } + + return null; + } + }); + } catch (ErrorsException e) { + throw new AssertionError(); + } + } + } + } + + private boolean isEagerSingleton(InjectorImpl injector, BindingImpl binding, Stage stage) { + if (binding.getScoping().isEagerSingleton(stage)) { + return true; + } + + // handle a corner case where a child injector links to a binding in a parent injector, and + // that binding is singleton. We won't catch this otherwise because we only iterate the child's + // bindings. + if (binding instanceof LinkedBindingImpl) { + Key linkedBinding = ((LinkedBindingImpl) binding).getLinkedKey(); + return isEagerSingleton(injector, injector.getBinding(linkedBinding), stage); + } + + return false; + } + + /** + * {@link Injector} exposed to users in {@link Stage#TOOL}. + */ + static class ToolStageInjector implements Injector { + private final Injector delegateInjector; + + ToolStageInjector(Injector delegateInjector) { + this.delegateInjector = delegateInjector; + } + + public void injectMembers(Object o) { + throw new UnsupportedOperationException( + "Injector.injectMembers(Object) is not supported in Stage.TOOL"); + } + + public Map, Binding> getBindings() { + return this.delegateInjector.getBindings(); + } + + public Map, Binding> getAllBindings() { + return this.delegateInjector.getAllBindings(); + } + + public Binding getBinding(Key key) { + return this.delegateInjector.getBinding(key); + } + + public Binding getBinding(Class type) { + return this.delegateInjector.getBinding(type); + } + + public Binding getExistingBinding(Key key) { + return this.delegateInjector.getExistingBinding(key); + } + + public List> findBindingsByType(TypeLiteral type) { + return this.delegateInjector.findBindingsByType(type); + } + + public Injector getParent() { + return delegateInjector.getParent(); + } + + public Injector createChildInjector(Iterable modules) { + return delegateInjector.createChildInjector(modules); + } + + public Injector createChildInjector(Module... modules) { + return delegateInjector.createChildInjector(modules); + } + + public Map, Scope> getScopeBindings() { + return delegateInjector.getScopeBindings(); + } + + public Set getTypeConverterBindings() { + return delegateInjector.getTypeConverterBindings(); + } + + public Provider getProvider(Key key) { + throw new UnsupportedOperationException( + "Injector.getProvider(Key) is not supported in Stage.TOOL"); + } + + public Provider getProvider(Class type) { + throw new UnsupportedOperationException( + "Injector.getProvider(Class) is not supported in Stage.TOOL"); + } + + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + throw new UnsupportedOperationException( + "Injector.getMembersInjector(TypeLiteral) is not supported in Stage.TOOL"); + } + + public MembersInjector getMembersInjector(Class type) { + throw new UnsupportedOperationException( + "Injector.getMembersInjector(Class) is not supported in Stage.TOOL"); + } + + public T getInstance(Key key) { + throw new UnsupportedOperationException( + "Injector.getInstance(Key) is not supported in Stage.TOOL"); + } + + public T getInstance(Class type) { + throw new UnsupportedOperationException( + "Injector.getInstance(Class) is not supported in Stage.TOOL"); + } + } +} diff --git a/src/main/java/com/google/inject/internal/LinkedBindingImpl.java b/src/main/java/com/google/inject/internal/LinkedBindingImpl.java new file mode 100644 index 0000000..4b90fee --- /dev/null +++ b/src/main/java/com/google/inject/internal/LinkedBindingImpl.java @@ -0,0 +1,81 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.LinkedKeyBinding; + +import java.util.Set; + +public final class LinkedBindingImpl extends BindingImpl implements LinkedKeyBinding, HasDependencies { + + final Key targetKey; + + public LinkedBindingImpl(InjectorImpl injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping, + Key targetKey) { + super(injector, key, source, internalFactory, scoping); + this.targetKey = targetKey; + } + + public LinkedBindingImpl(Object source, Key key, Scoping scoping, Key targetKey) { + super(source, key, scoping); + this.targetKey = targetKey; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Key getLinkedKey() { + return targetKey; + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(targetKey)); + } + + public BindingImpl withScoping(Scoping scoping) { + return new LinkedBindingImpl(getSource(), getKey(), scoping, targetKey); + } + + public BindingImpl withKey(Key key) { + return new LinkedBindingImpl(getSource(), key, getScoping(), targetKey); + } + + public void applyTo(Binder binder) { + getScoping().applyTo(binder.withSource(getSource()).bind(getKey()).to(getLinkedKey())); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(LinkedKeyBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .add("target", targetKey) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof LinkedBindingImpl) { + LinkedBindingImpl o = (LinkedBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(targetKey, o.targetKey); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping(), targetKey); + } +} diff --git a/src/main/java/com/google/inject/internal/LinkedProviderBindingImpl.java b/src/main/java/com/google/inject/internal/LinkedProviderBindingImpl.java new file mode 100644 index 0000000..1482ed4 --- /dev/null +++ b/src/main/java/com/google/inject/internal/LinkedProviderBindingImpl.java @@ -0,0 +1,108 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.ProviderKeyBinding; + +import java.util.Set; + +final class LinkedProviderBindingImpl + extends BindingImpl implements ProviderKeyBinding, HasDependencies, DelayedInitialize { + + final Key> providerKey; + final DelayedInitialize delayedInitializer; + + private LinkedProviderBindingImpl(InjectorImpl injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping, + Key> providerKey, + DelayedInitialize delayedInitializer) { + super(injector, key, source, internalFactory, scoping); + this.providerKey = providerKey; + this.delayedInitializer = delayedInitializer; + } + + public LinkedProviderBindingImpl(InjectorImpl injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping, + Key> providerKey) { + this(injector, key, source, internalFactory, scoping, providerKey, null); + } + + LinkedProviderBindingImpl(Object source, Key key, Scoping scoping, + Key> providerKey) { + super(source, key, scoping); + this.providerKey = providerKey; + this.delayedInitializer = null; + } + + static LinkedProviderBindingImpl createWithInitializer(InjectorImpl injector, Key key, + Object source, InternalFactory internalFactory, Scoping scoping, + Key> providerKey, + DelayedInitialize delayedInitializer) { + return new LinkedProviderBindingImpl(injector, key, source, internalFactory, scoping, + providerKey, delayedInitializer); + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Key> getProviderKey() { + return providerKey; + } + + public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { + if (delayedInitializer != null) { + delayedInitializer.initialize(injector, errors); + } + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(providerKey)); + } + + public BindingImpl withScoping(Scoping scoping) { + return new LinkedProviderBindingImpl(getSource(), getKey(), scoping, providerKey); + } + + public BindingImpl withKey(Key key) { + return new LinkedProviderBindingImpl(getSource(), key, getScoping(), providerKey); + } + + public void applyTo(Binder binder) { + getScoping().applyTo(binder.withSource(getSource()) + .bind(getKey()).toProvider(getProviderKey())); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ProviderKeyBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .add("provider", providerKey) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof LinkedProviderBindingImpl) { + LinkedProviderBindingImpl o = (LinkedProviderBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(providerKey, o.providerKey); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping(), providerKey); + } +} diff --git a/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java b/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java new file mode 100644 index 0000000..ba77ef2 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java @@ -0,0 +1,26 @@ +package com.google.inject.internal; + +import com.google.inject.spi.ProvisionListenerBinding; +import com.google.inject.spi.TypeListenerBinding; + +/** + * Handles {@code Binder#bindListener} commands. + */ +final class ListenerBindingProcessor extends AbstractProcessor { + + ListenerBindingProcessor(Errors errors) { + super(errors); + } + + @Override + public Boolean visit(TypeListenerBinding binding) { + injector.state.addTypeListener(binding); + return true; + } + + @Override + public Boolean visit(ProvisionListenerBinding binding) { + injector.state.addProvisionListener(binding); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/LookupProcessor.java b/src/main/java/com/google/inject/internal/LookupProcessor.java new file mode 100644 index 0000000..633eb0a --- /dev/null +++ b/src/main/java/com/google/inject/internal/LookupProcessor.java @@ -0,0 +1,42 @@ +package com.google.inject.internal; + +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import com.google.inject.spi.MembersInjectorLookup; +import com.google.inject.spi.ProviderLookup; + +/** + * Handles {@code Binder.getProvider} and {@code Binder.getMembersInjector(TypeLiteral)} commands. + */ +final class LookupProcessor extends AbstractProcessor { + + LookupProcessor(Errors errors) { + super(errors); + } + + @Override + public Boolean visit(MembersInjectorLookup lookup) { + try { + MembersInjector membersInjector + = injector.membersInjectorStore.get(lookup.getType(), errors); + lookup.initializeDelegate(membersInjector); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); // TODO: source + } + + return true; + } + + @Override + public Boolean visit(ProviderLookup lookup) { + // ensure the provider can be created + try { + Provider provider = injector.getProviderOrThrow(lookup.getDependency(), errors); + lookup.initializeDelegate(provider); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); // TODO: source + } + + return true; + } +} diff --git a/src/main/java/com/google/inject/internal/Lookups.java b/src/main/java/com/google/inject/internal/Lookups.java new file mode 100644 index 0000000..fab0a2d --- /dev/null +++ b/src/main/java/com/google/inject/internal/Lookups.java @@ -0,0 +1,17 @@ +package com.google.inject.internal; + +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; + +/** + * Accessors for providers and members injectors. The returned values will not be functional until + * the injector has been created. + */ +interface Lookups { + + Provider getProvider(Key key); + + MembersInjector getMembersInjector(TypeLiteral type); +} diff --git a/src/main/java/com/google/inject/internal/MembersInjectorImpl.java b/src/main/java/com/google/inject/internal/MembersInjectorImpl.java new file mode 100644 index 0000000..aacb75d --- /dev/null +++ b/src/main/java/com/google/inject/internal/MembersInjectorImpl.java @@ -0,0 +1,138 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.ProvisionListenerStackCallback.ProvisionCallback; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.InjectionPoint; + +/** + * Injects members of instances of a given type. + */ +final class MembersInjectorImpl implements MembersInjector { + private final TypeLiteral typeLiteral; + private final InjectorImpl injector; + private final ImmutableList memberInjectors; + private final ImmutableSet> userMembersInjectors; + private final ImmutableSet> injectionListeners; + + MembersInjectorImpl(InjectorImpl injector, TypeLiteral typeLiteral, + EncounterImpl encounter, ImmutableList memberInjectors) { + this.injector = injector; + this.typeLiteral = typeLiteral; + this.memberInjectors = memberInjectors; + this.userMembersInjectors = encounter.getMembersInjectors(); + this.injectionListeners = encounter.getInjectionListeners(); + } + + public ImmutableList getMemberInjectors() { + return memberInjectors; + } + + public void injectMembers(T instance) { + Errors errors = new Errors(typeLiteral); + try { + injectAndNotify(instance, errors, null, null, typeLiteral, false); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + + errors.throwProvisionExceptionIfErrorsExist(); + } + + void injectAndNotify(final T instance, + final Errors errors, + final Key key, // possibly null! + final ProvisionListenerStackCallback provisionCallback, // possibly null! + final Object source, + final boolean toolableOnly) throws ErrorsException { + if (instance == null) { + return; + } + + injector.callInContext(new ContextualCallable() { + @Override + public Void call(final InternalContext context) throws ErrorsException { + context.pushState(key, source); + try { + if (provisionCallback != null && provisionCallback.hasListeners()) { + provisionCallback.provision(errors, context, new ProvisionCallback() { + @Override + public T call() { + injectMembers(instance, errors, context, toolableOnly); + return instance; + } + }); + } else { + injectMembers(instance, errors, context, toolableOnly); + } + } finally { + context.popState(); + } + return null; + } + }); + + // TODO: We *could* notify listeners too here, + // but it's not clear if we want to. There's no way to know + // if a MembersInjector from the usersMemberInjector list wants + // toolable injections, so do we really want to notify + // about injection? (We could take a strategy of only notifying + // if atleast one InjectionPoint was toolable, in which case + // the above callInContext could return 'true' if it injected + // anything.) + if (!toolableOnly) { + notifyListeners(instance, errors); + } + } + + void notifyListeners(T instance, Errors errors) throws ErrorsException { + int numErrorsBefore = errors.size(); + for (InjectionListener injectionListener : injectionListeners) { + try { + injectionListener.afterInjection(instance); + } catch (RuntimeException e) { + errors.errorNotifyingInjectionListener(injectionListener, typeLiteral, e); + } + } + errors.throwIfNewErrors(numErrorsBefore); + } + + void injectMembers(T t, Errors errors, InternalContext context, boolean toolableOnly) { + // optimization: use manual for/each to save allocating an iterator here + for (int i = 0, size = memberInjectors.size(); i < size; i++) { + SingleMemberInjector injector = memberInjectors.get(i); + if (!toolableOnly || injector.getInjectionPoint().isToolable()) { + injector.inject(errors, context, t); + } + } + + // TODO: There's no way to know if a user's MembersInjector wants toolable injections. + if (!toolableOnly) { + for (MembersInjector userMembersInjector : userMembersInjectors) { + try { + userMembersInjector.injectMembers(t); + } catch (RuntimeException e) { + errors.errorInUserInjector(userMembersInjector, typeLiteral, e); + } + } + } + } + + @Override + public String toString() { + return "MembersInjector<" + typeLiteral + ">"; + } + + public ImmutableSet getInjectionPoints() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (SingleMemberInjector memberInjector : memberInjectors) { + builder.add(memberInjector.getInjectionPoint()); + } + return builder.build(); + } + +} diff --git a/src/main/java/com/google/inject/internal/MembersInjectorStore.java b/src/main/java/com/google/inject/internal/MembersInjectorStore.java new file mode 100644 index 0000000..0524864 --- /dev/null +++ b/src/main/java/com/google/inject/internal/MembersInjectorStore.java @@ -0,0 +1,124 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.inject.ConfigurationException; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.TypeListener; +import com.google.inject.spi.TypeListenerBinding; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Set; + +/** + * Members injectors by type. + */ +final class MembersInjectorStore { + private final InjectorImpl injector; + private final ImmutableList typeListenerBindings; + + private final FailableCache, MembersInjectorImpl> cache + = new FailableCache, MembersInjectorImpl>() { + @Override + protected MembersInjectorImpl create(TypeLiteral type, Errors errors) + throws ErrorsException { + return createWithListeners(type, errors); + } + }; + + MembersInjectorStore(InjectorImpl injector, + List typeListenerBindings) { + this.injector = injector; + this.typeListenerBindings = ImmutableList.copyOf(typeListenerBindings); + } + + /** + * Returns true if any type listeners are installed. Other code may take shortcuts when there + * aren't any type listeners. + */ + public boolean hasTypeListeners() { + return !typeListenerBindings.isEmpty(); + } + + /** + * Returns a new complete members injector with injection listeners registered. + */ + @SuppressWarnings("unchecked") // the MembersInjector type always agrees with the passed type + public MembersInjectorImpl get(TypeLiteral key, Errors errors) throws ErrorsException { + return (MembersInjectorImpl) cache.get(key, errors); + } + + /** + * Purges a type literal from the cache. Use this only if the type is not actually valid for + * binding and needs to be purged. (See issue 319 and + * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and + * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is + * necessary.) + * + * Returns true if the type was stored in the cache, false otherwise. + */ + boolean remove(TypeLiteral type) { + return cache.remove(type); + } + + /** + * Creates a new members injector and attaches both injection listeners and method aspects. + */ + private MembersInjectorImpl createWithListeners(TypeLiteral type, Errors errors) + throws ErrorsException { + int numErrorsBefore = errors.size(); + + Set injectionPoints; + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(type); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + injectionPoints = e.getPartialValue(); + } + ImmutableList injectors = getInjectors(injectionPoints, errors); + errors.throwIfNewErrors(numErrorsBefore); + + EncounterImpl encounter = new EncounterImpl(errors, injector.lookups); + Set alreadySeenListeners = Sets.newHashSet(); + for (TypeListenerBinding binding : typeListenerBindings) { + TypeListener typeListener = binding.getListener(); + if (!alreadySeenListeners.contains(typeListener) && binding.getTypeMatcher().matches(type)) { + alreadySeenListeners.add(typeListener); + try { + typeListener.hear(type, encounter); + } catch (RuntimeException e) { + errors.errorNotifyingTypeListener(binding, type, e); + } + } + } + encounter.invalidate(); + errors.throwIfNewErrors(numErrorsBefore); + + return new MembersInjectorImpl(injector, type, encounter, injectors); + } + + /** + * Returns the injectors for the specified injection points. + */ + ImmutableList getInjectors( + Set injectionPoints, Errors errors) { + List injectors = Lists.newArrayList(); + for (InjectionPoint injectionPoint : injectionPoints) { + try { + Errors errorsForMember = injectionPoint.isOptional() + ? new Errors(injectionPoint) + : errors.withSource(injectionPoint); + SingleMemberInjector injector = injectionPoint.getMember() instanceof Field + ? new SingleFieldInjector(this.injector, injectionPoint, errorsForMember) + : new SingleMethodInjector(this.injector, injectionPoint, errorsForMember); + injectors.add(injector); + } catch (ErrorsException ignoredForNow) { + // ignored for now + } + } + return ImmutableList.copyOf(injectors); + } +} diff --git a/src/main/java/com/google/inject/internal/MessageProcessor.java b/src/main/java/com/google/inject/internal/MessageProcessor.java new file mode 100644 index 0000000..9437891 --- /dev/null +++ b/src/main/java/com/google/inject/internal/MessageProcessor.java @@ -0,0 +1,32 @@ +package com.google.inject.internal; + +import com.google.inject.spi.Message; + +/** + * Handles {@code Binder.addError} commands. + * + */ +final class MessageProcessor extends AbstractProcessor { + + MessageProcessor(Errors errors) { + super(errors); + } + + public static String getRootMessage(Throwable t) { + Throwable cause = t.getCause(); + return cause == null ? t.toString() : getRootMessage(cause); + } + + @Override + public Boolean visit(Message message) { + if (message.getCause() != null) { + String rootMessage = getRootMessage(message.getCause()); + /*logger.log(Level.INFO, + "An exception was caught and reported. Message: " + rootMessage, + message.getCause());*/ + } + + errors.addMessage(message); + return true; + } +} diff --git a/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java b/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java new file mode 100644 index 0000000..f3e48c2 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java @@ -0,0 +1,19 @@ +package com.google.inject.internal; + +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; + +/** + * Handles {@code Binder.scanModulesForAnnotatedMethods} commands. + */ +final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor { + + ModuleAnnotatedMethodScannerProcessor(Errors errors) { + super(errors); + } + + @Override + public Boolean visit(ModuleAnnotatedMethodScannerBinding command) { + injector.state.addScanner(command); + return true; + } +} diff --git a/src/main/java/com/google/inject/internal/MoreTypes.java b/src/main/java/com/google/inject/internal/MoreTypes.java new file mode 100644 index 0000000..ce35127 --- /dev/null +++ b/src/main/java/com/google/inject/internal/MoreTypes.java @@ -0,0 +1,535 @@ +package com.google.inject.internal; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import com.google.inject.ConfigurationException; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.util.Types; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.Map; +import java.util.NoSuchElementException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Static methods for working with types that we aren't publishing in the + * public {@code Types} API. + */ +public class MoreTypes { + + public static final Type[] EMPTY_TYPE_ARRAY = new Type[]{}; + + private MoreTypes() { + } + + private static final Map, TypeLiteral> PRIMITIVE_TO_WRAPPER + = new ImmutableMap.Builder, TypeLiteral>() + .put(TypeLiteral.get(boolean.class), TypeLiteral.get(Boolean.class)) + .put(TypeLiteral.get(byte.class), TypeLiteral.get(Byte.class)) + .put(TypeLiteral.get(short.class), TypeLiteral.get(Short.class)) + .put(TypeLiteral.get(int.class), TypeLiteral.get(Integer.class)) + .put(TypeLiteral.get(long.class), TypeLiteral.get(Long.class)) + .put(TypeLiteral.get(float.class), TypeLiteral.get(Float.class)) + .put(TypeLiteral.get(double.class), TypeLiteral.get(Double.class)) + .put(TypeLiteral.get(char.class), TypeLiteral.get(Character.class)) + .put(TypeLiteral.get(void.class), TypeLiteral.get(Void.class)) + .build(); + + /** + * Returns a key that doesn't hold any references to parent classes. + * This is necessary for anonymous keys, so ensure we don't hold a ref + * to the containing module (or class) forever. + */ + public static Key canonicalizeKey(Key key) { + // If we know this isn't a subclass, return as-is. + // Otherwise, recreate the key to avoid the subclass + if (key.getClass() == Key.class) { + return key; + } else if (key.getAnnotation() != null) { + return Key.get(key.getTypeLiteral(), key.getAnnotation()); + } else if (key.getAnnotationType() != null) { + return Key.get(key.getTypeLiteral(), key.getAnnotationType()); + } else { + return Key.get(key.getTypeLiteral()); + } + } + + /** + * 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 type is a primitive, the corresponding wrapper type will be returned. + * + * @throws ConfigurationException if {@code type} contains a type variable + */ + public static TypeLiteral canonicalizeForKey(TypeLiteral typeLiteral) { + Type type = typeLiteral.getType(); + if (!isFullySpecified(type)) { + Errors errors = new Errors().keyNotFullySpecified(typeLiteral); + throw new ConfigurationException(errors.getMessages()); + } + + if (typeLiteral.getRawType() == javax.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 + @SuppressWarnings("unchecked") + TypeLiteral guiceProviderType = (TypeLiteral) TypeLiteral.get( + Types.providerOf(parameterizedType.getActualTypeArguments()[0])); + return guiceProviderType; + } + + @SuppressWarnings("unchecked") + TypeLiteral wrappedPrimitives = (TypeLiteral) PRIMITIVE_TO_WRAPPER.get(typeLiteral); + if (wrappedPrimitives != null) { + return wrappedPrimitives; + } + + // If we know this isn't a subclass, return as-is. + if (typeLiteral.getClass() == TypeLiteral.class) { + return typeLiteral; + } + + // recreate the TypeLiteral to avoid anonymous TypeLiterals from holding refs to their + // surrounding classes. + @SuppressWarnings("unchecked") + TypeLiteral recreated = (TypeLiteral) TypeLiteral.get(typeLiteral.getType()); + return recreated; + } + + /** + * Returns true if {@code type} is free from type variables. + */ + private static boolean isFullySpecified(Type type) { + if (type instanceof Class) { + return true; + + } else if (type instanceof CompositeType) { + return ((CompositeType) type).isFullySpecified(); + + } else { + return !(type instanceof TypeVariable) && ((CompositeType) canonicalize(type)).isFullySpecified(); + } + } + + /** + * Returns a type that is functionally equal but not necessarily equal + * according to {@link Object#equals(Object) Object.equals()}. + */ + public static Type canonicalize(Type type) { + if (type instanceof Class) { + Class c = (Class) type; + return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c; + + } else if (type instanceof CompositeType) { + return type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) type; + return new ParameterizedTypeImpl(p.getOwnerType(), + p.getRawType(), p.getActualTypeArguments()); + + } else if (type instanceof GenericArrayType) { + GenericArrayType g = (GenericArrayType) type; + return new GenericArrayTypeImpl(g.getGenericComponentType()); + + } else if (type instanceof WildcardType) { + WildcardType w = (WildcardType) type; + return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); + + } else { + return type; + } + } + + public static Class getRawType(Type type) { + if (type instanceof Class) { + // type is a normal class. + return (Class) type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + + // I'm not exactly sure why getRawType() returns Type instead of Class. + // Neal isn't either but suspects some pathological case related + // to nested classes exists. + Type rawType = parameterizedType.getRawType(); + checkArgument(rawType instanceof Class, + "Expected a Class, but <%s> is of type %s", type, type.getClass().getName()); + return (Class) rawType; + + } else if (type instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) type).getGenericComponentType(); + return Array.newInstance(getRawType(componentType), 0).getClass(); + + } else if (type instanceof TypeVariable) { + // we could use the variable's bounds, but that'll won't work if there are multiple. + // having a raw type that's more general than necessary is okay + return Object.class; + + } else { + throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + + "GenericArrayType, but <" + type + "> is of type " + type.getClass().getName()); + } + } + + /** + * Returns true if {@code a} and {@code b} are equal. + */ + public static boolean equals(Type a, Type b) { + if (a == b) { + // also handles (a == null && b == null) + return true; + + } else if (a instanceof Class) { + // Class already specifies equals(). + return a.equals(b); + + } else if (a instanceof ParameterizedType) { + if (!(b instanceof ParameterizedType)) { + return false; + } + + // TODO: save a .clone() call + ParameterizedType pa = (ParameterizedType) a; + ParameterizedType pb = (ParameterizedType) b; + return Objects.equal(pa.getOwnerType(), pb.getOwnerType()) + && pa.getRawType().equals(pb.getRawType()) + && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); + + } else if (a instanceof GenericArrayType) { + if (!(b instanceof GenericArrayType)) { + return false; + } + + GenericArrayType ga = (GenericArrayType) a; + GenericArrayType gb = (GenericArrayType) b; + return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); + + } else if (a instanceof WildcardType) { + if (!(b instanceof WildcardType)) { + return false; + } + + WildcardType wa = (WildcardType) a; + WildcardType wb = (WildcardType) b; + return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) + && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); + + } else if (a instanceof TypeVariable) { + if (!(b instanceof TypeVariable)) { + return false; + } + TypeVariable va = (TypeVariable) a; + TypeVariable vb = (TypeVariable) b; + return va.getGenericDeclaration().equals(vb.getGenericDeclaration()) + && va.getName().equals(vb.getName()); + + } else { + // This isn't a type we support. Could be a generic array type, wildcard type, etc. + return false; + } + } + + private static int hashCodeOrZero(Object o) { + return o != null ? o.hashCode() : 0; + } + + public static String typeToString(Type type) { + return type instanceof Class ? ((Class) type).getName() : type.toString(); + } + + /** + * Returns the generic supertype for {@code type}. For example, given a class {@code IntegerSet}, + * the result for when supertype is {@code Set.class} is {@code Set} and the result + * when the supertype is {@code Collection.class} is {@code Collection}. + */ + public static Type getGenericSupertype(Type type, Class rawType, Class toResolve) { + if (toResolve == rawType) { + return type; + } + + // we skip searching through interfaces if unknown is an interface + if (toResolve.isInterface()) { + Class[] interfaces = rawType.getInterfaces(); + for (int i = 0, length = interfaces.length; i < length; i++) { + if (interfaces[i] == toResolve) { + return rawType.getGenericInterfaces()[i]; + } else if (toResolve.isAssignableFrom(interfaces[i])) { + return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); + } + } + } + + // check our supertypes + if (!rawType.isInterface()) { + while (rawType != Object.class) { + Class rawSupertype = rawType.getSuperclass(); + if (rawSupertype == toResolve) { + return rawType.getGenericSuperclass(); + } else if (toResolve.isAssignableFrom(rawSupertype)) { + return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); + } + rawType = rawSupertype; + } + } + + // we can't resolve this further + return toResolve; + } + + public static Type resolveTypeVariable(Type type, Class rawType, TypeVariable unknown) { + Class declaredByRaw = declaringClassOf(unknown); + + // we can't reduce this further + if (declaredByRaw == null) { + return unknown; + } + + Type declaredBy = getGenericSupertype(type, rawType, declaredByRaw); + if (declaredBy instanceof ParameterizedType) { + int index = indexOf(declaredByRaw.getTypeParameters(), unknown); + return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; + } + + return unknown; + } + + private static int indexOf(Object[] array, Object toFind) { + for (int i = 0; i < array.length; i++) { + if (toFind.equals(array[i])) { + return i; + } + } + throw new NoSuchElementException(); + } + + /** + * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by + * a class. + */ + private static Class declaringClassOf(TypeVariable typeVariable) { + GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); + return genericDeclaration instanceof Class + ? (Class) genericDeclaration + : null; + } + + private static void checkNotPrimitive(Type type, String use) { + checkArgument(!(type instanceof Class) || !((Class) type).isPrimitive(), + "Primitive types are not allowed in %s: %s", use, type); + } + + /** + * A type formed from other types, such as arrays, parameterized types or wildcard types + */ + private interface CompositeType { + /** + * Returns true if there are no type variables in this type. + */ + boolean isFullySpecified(); + } + + public static class ParameterizedTypeImpl + implements ParameterizedType, CompositeType { + private final Type ownerType; + private final Type rawType; + private final Type[] typeArguments; + + public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { + // require an owner type if the raw type needs it + ensureOwnerType(ownerType, rawType); + + this.ownerType = ownerType == null ? null : canonicalize(ownerType); + this.rawType = canonicalize(rawType); + this.typeArguments = typeArguments.clone(); + for (int t = 0; t < this.typeArguments.length; t++) { + checkNotNull(this.typeArguments[t], "type parameter"); + checkNotPrimitive(this.typeArguments[t], "type parameters"); + this.typeArguments[t] = canonicalize(this.typeArguments[t]); + } + } + + private static void ensureOwnerType(Type ownerType, Type rawType) { + if (rawType instanceof Class) { + Class rawTypeAsClass = (Class) rawType; + checkArgument(ownerType != null || rawTypeAsClass.getEnclosingClass() == null, + "No owner type for enclosed %s", rawType); + checkArgument(ownerType == null || rawTypeAsClass.getEnclosingClass() != null, + "Owner type for unenclosed %s", rawType); + } + } + + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + public Type getRawType() { + return rawType; + } + + public Type getOwnerType() { + return ownerType; + } + + public boolean isFullySpecified() { + if (ownerType != null && !MoreTypes.isFullySpecified(ownerType)) { + return false; + } + + if (!MoreTypes.isFullySpecified(rawType)) { + return false; + } + + for (Type type : typeArguments) { + if (!MoreTypes.isFullySpecified(type)) { + return false; + } + } + + return true; + } + + @Override + public boolean equals(Object other) { + return other instanceof ParameterizedType + && MoreTypes.equals(this, (ParameterizedType) other); + } + + @Override + public int hashCode() { + return Arrays.hashCode(typeArguments) + ^ rawType.hashCode() + ^ hashCodeOrZero(ownerType); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1)); + stringBuilder.append(typeToString(rawType)); + + if (typeArguments.length == 0) { + return stringBuilder.toString(); + } + + stringBuilder.append("<").append(typeToString(typeArguments[0])); + for (int i = 1; i < typeArguments.length; i++) { + stringBuilder.append(", ").append(typeToString(typeArguments[i])); + } + return stringBuilder.append(">").toString(); + } + } + + public static class GenericArrayTypeImpl + implements GenericArrayType, CompositeType { + private final Type componentType; + + public GenericArrayTypeImpl(Type componentType) { + this.componentType = canonicalize(componentType); + } + + public Type getGenericComponentType() { + return componentType; + } + + public boolean isFullySpecified() { + return MoreTypes.isFullySpecified(componentType); + } + + @Override + public boolean equals(Object o) { + return o instanceof GenericArrayType + && MoreTypes.equals(this, (GenericArrayType) o); + } + + @Override + public int hashCode() { + return componentType.hashCode(); + } + + @Override + public String toString() { + return typeToString(componentType) + "[]"; + } + } + + /** + * The WildcardType interface supports multiple upper bounds and multiple + * lower bounds. We only support what the Java 6 language needs - at most one + * bound. If a lower bound is set, the upper bound must be Object.class. + */ + public static class WildcardTypeImpl + implements WildcardType, CompositeType { + private final Type upperBound; + private final Type lowerBound; + + public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { + checkArgument(lowerBounds.length <= 1, "Must have at most one lower bound."); + checkArgument(upperBounds.length == 1, "Must have exactly one upper bound."); + + if (lowerBounds.length == 1) { + checkNotNull(lowerBounds[0], "lowerBound"); + checkNotPrimitive(lowerBounds[0], "wildcard bounds"); + checkArgument(upperBounds[0] == Object.class, "bounded both ways"); + this.lowerBound = canonicalize(lowerBounds[0]); + this.upperBound = Object.class; + + } else { + checkNotNull(upperBounds[0], "upperBound"); + checkNotPrimitive(upperBounds[0], "wildcard bounds"); + this.lowerBound = null; + this.upperBound = canonicalize(upperBounds[0]); + } + } + + public Type[] getUpperBounds() { + return new Type[]{upperBound}; + } + + public Type[] getLowerBounds() { + return lowerBound != null ? new Type[]{lowerBound} : EMPTY_TYPE_ARRAY; + } + + public boolean isFullySpecified() { + return MoreTypes.isFullySpecified(upperBound) + && (lowerBound == null || MoreTypes.isFullySpecified(lowerBound)); + } + + @Override + public boolean equals(Object other) { + return other instanceof WildcardType + && MoreTypes.equals(this, (WildcardType) other); + } + + @Override + public int hashCode() { + // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); + return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) + ^ (31 + upperBound.hashCode()); + } + + @Override + public String toString() { + if (lowerBound != null) { + return "? super " + typeToString(lowerBound); + } else if (upperBound == Object.class) { + return "?"; + } else { + return "? extends " + typeToString(upperBound); + } + } + } +} diff --git a/src/main/java/com/google/inject/internal/Nullability.java b/src/main/java/com/google/inject/internal/Nullability.java new file mode 100644 index 0000000..8d9cd4b --- /dev/null +++ b/src/main/java/com/google/inject/internal/Nullability.java @@ -0,0 +1,32 @@ +package com.google.inject.internal; + +import java.lang.annotation.Annotation; + +/** + * Whether a member supports null values injected. + * + *

Support for {@code Nullable} annotations in Guice is loose. + * Any annotation type whose simplename is "Nullable" is sufficient to indicate + * support for null values injected. + * + *

This allows support for JSR-305's + * + * javax.annotation.meta.Nullable annotation and IntelliJ IDEA's + * + * org.jetbrains.annotations.Nullable. + * + */ +public class Nullability { + private Nullability() { + } + + public static boolean allowsNull(Annotation[] annotations) { + for (Annotation a : annotations) { + Class type = a.annotationType(); + if ("Nullable".equals(type.getSimpleName())) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/google/inject/internal/PrivateElementProcessor.java b/src/main/java/com/google/inject/internal/PrivateElementProcessor.java new file mode 100644 index 0000000..1c6c837 --- /dev/null +++ b/src/main/java/com/google/inject/internal/PrivateElementProcessor.java @@ -0,0 +1,31 @@ +package com.google.inject.internal; + +import com.google.common.collect.Lists; +import com.google.inject.spi.PrivateElements; + +import java.util.List; + +/** + * Handles {@code Binder.newPrivateBinder()} elements. + */ +final class PrivateElementProcessor extends AbstractProcessor { + + private final List injectorShellBuilders = Lists.newArrayList(); + + PrivateElementProcessor(Errors errors) { + super(errors); + } + + @Override + public Boolean visit(PrivateElements privateElements) { + InjectorShell.Builder builder = new InjectorShell.Builder() + .parent(injector) + .privateElements(privateElements); + injectorShellBuilders.add(builder); + return true; + } + + public List getInjectorShellBuilders() { + return injectorShellBuilders; + } +} diff --git a/src/main/java/com/google/inject/internal/PrivateElementsImpl.java b/src/main/java/com/google/inject/internal/PrivateElementsImpl.java new file mode 100644 index 0000000..45a434c --- /dev/null +++ b/src/main/java/com/google/inject/internal/PrivateElementsImpl.java @@ -0,0 +1,126 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.PrivateBinder; +import com.google.inject.spi.Element; +import com.google.inject.spi.ElementVisitor; +import com.google.inject.spi.PrivateElements; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class PrivateElementsImpl implements PrivateElements { + + /* + * This class acts as both a value object and as a builder. When getElements() is called, an + * immutable collection of elements is constructed and the original mutable list is nulled out. + * Similarly, the exposed keys are made immutable on access. + */ + + private final Object source; + + private List elementsMutable = Lists.newArrayList(); + private List> exposureBuilders = Lists.newArrayList(); + + /** + * lazily instantiated + */ + private ImmutableList elements; + + /** + * lazily instantiated + */ + private ImmutableMap, Object> exposedKeysToSources; + private Injector injector; + + public PrivateElementsImpl(Object source) { + this.source = checkNotNull(source, "source"); + } + + public Object getSource() { + return source; + } + + public List getElements() { + if (elements == null) { + elements = ImmutableList.copyOf(elementsMutable); + elementsMutable = null; + } + + return elements; + } + + public Injector getInjector() { + return injector; + } + + public void initInjector(Injector injector) { + checkState(this.injector == null, "injector already initialized"); + this.injector = checkNotNull(injector, "injector"); + } + + public Set> getExposedKeys() { + if (exposedKeysToSources == null) { + Map, Object> exposedKeysToSourcesMutable = Maps.newLinkedHashMap(); + for (ExposureBuilder exposureBuilder : exposureBuilders) { + exposedKeysToSourcesMutable.put(exposureBuilder.getKey(), exposureBuilder.getSource()); + } + exposedKeysToSources = ImmutableMap.copyOf(exposedKeysToSourcesMutable); + exposureBuilders = null; + } + + return exposedKeysToSources.keySet(); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public List getElementsMutable() { + return elementsMutable; + } + + public void addExposureBuilder(ExposureBuilder exposureBuilder) { + exposureBuilders.add(exposureBuilder); + } + + public void applyTo(Binder binder) { + PrivateBinder privateBinder = binder.withSource(source).newPrivateBinder(); + + for (Element element : getElements()) { + element.applyTo(privateBinder); + } + + getExposedKeys(); // ensure exposedKeysToSources is populated + for (Map.Entry, Object> entry : exposedKeysToSources.entrySet()) { + privateBinder.withSource(entry.getValue()).expose(entry.getKey()); + } + } + + public Object getExposedSource(Key key) { + getExposedKeys(); // ensure exposedKeysToSources is populated + Object source = exposedKeysToSources.get(key); + checkArgument(source != null, "%s not exposed by %s.", key, this); + return source; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(PrivateElements.class) + .add("exposedKeys", getExposedKeys()) + .add("source", getSource()) + .toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/ProcessedBindingData.java b/src/main/java/com/google/inject/internal/ProcessedBindingData.java new file mode 100644 index 0000000..68f8f57 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProcessedBindingData.java @@ -0,0 +1,36 @@ +package com.google.inject.internal; + +import com.google.common.collect.Lists; + +import java.util.List; + +/** + * Keeps track of creation listeners & uninitialized bindings, + * so they can be processed after bindings are recorded. + */ +class ProcessedBindingData { + + private final List creationListeners = Lists.newArrayList(); + private final List uninitializedBindings = Lists.newArrayList(); + + void addCreationListener(CreationListener listener) { + creationListeners.add(listener); + } + + void addUninitializedBinding(Runnable runnable) { + uninitializedBindings.add(runnable); + } + + void initializeBindings() { + for (Runnable initializer : uninitializedBindings) { + initializer.run(); + } + } + + void runCreationListeners(Errors errors) { + for (CreationListener creationListener : creationListeners) { + creationListener.notify(errors); + } + } + +} diff --git a/src/main/java/com/google/inject/internal/ProvidedByInternalFactory.java b/src/main/java/com/google/inject/internal/ProvidedByInternalFactory.java new file mode 100644 index 0000000..c6ea4dc --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProvidedByInternalFactory.java @@ -0,0 +1,73 @@ +package com.google.inject.internal; + +import com.google.inject.Key; +import com.google.inject.ProvidedBy; +import com.google.inject.Provider; +import com.google.inject.internal.InjectorImpl.JitLimitation; +import com.google.inject.spi.Dependency; + +import static com.google.common.base.Preconditions.checkState; + +/** + * An {@link InternalFactory} for {@literal @}{@link ProvidedBy} bindings. + */ +class ProvidedByInternalFactory extends ProviderInternalFactory + implements DelayedInitialize { + + private final Class rawType; + private final Class> providerType; + private final Key> providerKey; + private BindingImpl> providerBinding; + private ProvisionListenerStackCallback provisionCallback; + + ProvidedByInternalFactory( + Class rawType, + Class> providerType, + Key> providerKey) { + super(providerKey); + this.rawType = rawType; + this.providerType = providerType; + this.providerKey = providerKey; + } + + void setProvisionListenerCallback(ProvisionListenerStackCallback listener) { + provisionCallback = listener; + } + + public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { + providerBinding = + injector.getBindingOrThrow(providerKey, errors, JitLimitation.NEW_OR_EXISTING_JIT); + } + + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) + throws ErrorsException { + checkState(providerBinding != null, "not initialized"); + + context.pushState(providerKey, providerBinding.getSource()); + try { + errors = errors.withSource(providerKey); + Provider provider = providerBinding.getInternalFactory().get( + errors, context, dependency, true); + return circularGet(provider, errors, context, dependency, provisionCallback); + } finally { + context.popState(); + } + } + + @Override + protected T provision(javax.inject.Provider provider, Errors errors, + Dependency dependency, ConstructionContext constructionContext) + throws ErrorsException { + try { + Object o = super.provision(provider, errors, dependency, constructionContext); + if (o != null && !rawType.isInstance(o)) { + throw errors.subtypeNotProvided(providerType, rawType).toException(); + } + @SuppressWarnings("unchecked") // protected by isInstance() check above + T t = (T) o; + return t; + } catch (RuntimeException e) { + throw errors.errorInProvider(e).toException(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ProviderInstanceBindingImpl.java b/src/main/java/com/google/inject/internal/ProviderInstanceBindingImpl.java new file mode 100644 index 0000000..ff5a882 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProviderInstanceBindingImpl.java @@ -0,0 +1,104 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ProviderInstanceBinding; +import com.google.inject.spi.ProviderWithExtensionVisitor; + +import java.util.Set; + +final class ProviderInstanceBindingImpl extends BindingImpl + implements ProviderInstanceBinding { + + final javax.inject.Provider providerInstance; + final ImmutableSet injectionPoints; + + public ProviderInstanceBindingImpl(InjectorImpl injector, Key key, + Object source, InternalFactory internalFactory, Scoping scoping, + javax.inject.Provider providerInstance, + Set injectionPoints) { + super(injector, key, source, internalFactory, scoping); + this.providerInstance = providerInstance; + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + } + + public ProviderInstanceBindingImpl(Object source, Key key, Scoping scoping, + Set injectionPoints, javax.inject.Provider providerInstance) { + super(source, key, scoping); + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + this.providerInstance = providerInstance; + } + + @SuppressWarnings("unchecked") // the extension type is always consistent with the provider type + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + if (providerInstance instanceof ProviderWithExtensionVisitor) { + return ((ProviderWithExtensionVisitor) providerInstance) + .acceptExtensionVisitor(visitor, this); + } else { + return visitor.visit(this); + } + } + + public javax.inject.Provider getUserSuppliedProvider() { + return providerInstance; + } + + public Set getInjectionPoints() { + return injectionPoints; + } + + public Set> getDependencies() { + return providerInstance instanceof HasDependencies + ? ImmutableSet.copyOf(((HasDependencies) providerInstance).getDependencies()) + : Dependency.forInjectionPoints(injectionPoints); + } + + public BindingImpl withScoping(Scoping scoping) { + return new ProviderInstanceBindingImpl( + getSource(), getKey(), scoping, injectionPoints, providerInstance); + } + + public BindingImpl withKey(Key key) { + return new ProviderInstanceBindingImpl( + getSource(), key, getScoping(), injectionPoints, providerInstance); + } + + public void applyTo(Binder binder) { + getScoping().applyTo( + binder.withSource(getSource()).bind(getKey()).toProvider(getUserSuppliedProvider())); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ProviderInstanceBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .add("provider", providerInstance) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ProviderInstanceBindingImpl) { + ProviderInstanceBindingImpl o = (ProviderInstanceBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()) + && Objects.equal(providerInstance, o.providerInstance); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping()); + } +} diff --git a/src/main/java/com/google/inject/internal/ProviderInternalFactory.java b/src/main/java/com/google/inject/internal/ProviderInternalFactory.java new file mode 100644 index 0000000..b0576b4 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProviderInternalFactory.java @@ -0,0 +1,67 @@ +package com.google.inject.internal; + +import com.google.inject.internal.ProvisionListenerStackCallback.ProvisionCallback; +import com.google.inject.spi.Dependency; + +import javax.inject.Provider; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Base class for InternalFactories that are used by Providers, to handle + * circular dependencies. + * + */ +abstract class ProviderInternalFactory implements InternalFactory { + + protected final Object source; + + ProviderInternalFactory(Object source) { + this.source = checkNotNull(source, "source"); + } + + protected T circularGet(final Provider provider, final Errors errors, + InternalContext context, final Dependency dependency, + ProvisionListenerStackCallback provisionCallback) + throws ErrorsException { + final ConstructionContext constructionContext = context.getConstructionContext(this); + + // We have a circular reference between constructors. Return a proxy. + if (constructionContext.isConstructing()) { + Class expectedType = dependency.getKey().getTypeLiteral().getRawType(); + // TODO: if we can't proxy this object, can we proxy the other object? + @SuppressWarnings("unchecked") + T proxyType = (T) constructionContext.createProxy( + errors, context.getInjectorOptions(), expectedType); + return proxyType; + } + + // Optimization: Don't go through the callback stack if no one's listening. + constructionContext.startConstruction(); + try { + if (!provisionCallback.hasListeners()) { + return provision(provider, errors, dependency, constructionContext); + } else { + return provisionCallback.provision(errors, context, new ProvisionCallback() { + public T call() throws ErrorsException { + return provision(provider, errors, dependency, constructionContext); + } + }); + } + } finally { + constructionContext.removeCurrentReference(); + constructionContext.finishConstruction(); + } + } + + /** + * Provisions a new instance. Subclasses should override this to catch + * exceptions & rethrow as ErrorsExceptions. + */ + protected T provision(Provider provider, Errors errors, Dependency dependency, + ConstructionContext constructionContext) throws ErrorsException { + T t = errors.checkForNull(provider.get(), source, dependency); + constructionContext.setProxyDelegates(t); + return t; + } +} diff --git a/src/main/java/com/google/inject/internal/ProviderMethod.java b/src/main/java/com/google/inject/internal/ProviderMethod.java new file mode 100644 index 0000000..8c38d0f --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProviderMethod.java @@ -0,0 +1,220 @@ +package com.google.inject.internal; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Exposed; +import com.google.inject.Key; +import com.google.inject.PrivateBinder; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.internal.util.StackTraceElements; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.ProviderInstanceBinding; +import com.google.inject.spi.ProviderWithExtensionVisitor; +import com.google.inject.spi.ProvidesMethodBinding; +import com.google.inject.spi.ProvidesMethodTargetVisitor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Set; + +/** + * A provider that invokes a method and returns its result. + * + */ +public abstract class ProviderMethod implements ProviderWithExtensionVisitor, HasDependencies, + ProvidesMethodBinding { + + protected final Object instance; + protected final Method method; + private final Key key; + private final Class scopeAnnotation; + private final ImmutableSet> dependencies; + private final List> parameterProviders; + private final boolean exposed; + private final Annotation annotation; + /** + * @param method the method to invoke. It's return type must be the same type as {@code key}. + */ + private ProviderMethod(Key key, Method method, Object instance, + ImmutableSet> dependencies, List> parameterProviders, + Class scopeAnnotation, Annotation annotation) { + this.key = key; + this.scopeAnnotation = scopeAnnotation; + this.instance = instance; + this.dependencies = dependencies; + this.method = method; + this.parameterProviders = parameterProviders; + this.exposed = method.isAnnotationPresent(Exposed.class); + this.annotation = annotation; + } + + /** + * Creates a {@link ProviderMethod}. + */ + static ProviderMethod create(Key key, Method method, Object instance, + ImmutableSet> dependencies, List> parameterProviders, + Class scopeAnnotation, boolean skipFastClassGeneration, + Annotation annotation) { + int modifiers = method.getModifiers(); + + if (!Modifier.isPublic(modifiers) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + method.setAccessible(true); + } + + return new ReflectionProviderMethod(key, + method, + instance, + dependencies, + parameterProviders, + scopeAnnotation, + annotation); + } + + @Override + public Key getKey() { + return key; + } + + @Override + public Method getMethod() { + return method; + } + + // exposed for GIN + public Object getInstance() { + return instance; + } + + @Override + public Object getEnclosingInstance() { + return instance; + } + + @Override + public Annotation getAnnotation() { + return annotation; + } + + public void configure(Binder binder) { + binder = binder.withSource(method); + + if (scopeAnnotation != null) { + binder.bind(key).toProvider(this).in(scopeAnnotation); + } else { + binder.bind(key).toProvider(this); + } + + if (exposed) { + // the cast is safe 'cause the only binder we have implements PrivateBinder. If there's a + // misplaced @Exposed, calling this will add an error to the binder's error queue + ((PrivateBinder) binder).expose(key); + } + } + + @Override + public T get() { + Object[] parameters = new Object[parameterProviders.size()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = parameterProviders.get(i).get(); + } + + try { + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) + T result = (T) doProvision(parameters); + return result; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw Exceptions.rethrowCause(e); + } + } + + /** + * Extension point for our subclasses to implement the provisioning strategy. + */ + abstract Object doProvision(Object[] parameters) + throws IllegalAccessException, InvocationTargetException; + + @Override + public Set> getDependencies() { + return dependencies; + } + + @Override + @SuppressWarnings("unchecked") + public V acceptExtensionVisitor(BindingTargetVisitor visitor, + ProviderInstanceBinding binding) { + if (visitor instanceof ProvidesMethodTargetVisitor) { + return ((ProvidesMethodTargetVisitor) visitor).visit(this); + } + return visitor.visit(binding); + } + + @Override + public String toString() { + String annotationString = annotation.toString(); + // Show @Provides w/o the com.google.inject prefix. + if (annotation.annotationType() == Provides.class) { + annotationString = "@Provides"; + } else if (annotationString.endsWith("()")) { + // Remove the common "()" suffix if there are no values. + annotationString = annotationString.substring(0, annotationString.length() - 2); + } + return annotationString + " " + StackTraceElements.forMember(method); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ProviderMethod) { + ProviderMethod o = (ProviderMethod) obj; + return method.equals(o.method) + && instance.equals(o.instance) + && annotation.equals(o.annotation); + } else { + return false; + } + } + + @Override + public int hashCode() { + // Avoid calling hashCode on 'instance', which is a user-object + // that might not be expecting it. + // (We need to call equals, so we do. But we can avoid hashCode.) + return Objects.hashCode(method, annotation); + } + + /** + * A {@link ProviderMethod} implementation that invokes the method using normal java reflection. + */ + private static final class ReflectionProviderMethod extends ProviderMethod { + ReflectionProviderMethod(Key key, + Method method, + Object instance, + ImmutableSet> dependencies, + List> parameterProviders, + Class scopeAnnotation, + Annotation annotation) { + super(key, + method, + instance, + dependencies, + parameterProviders, + scopeAnnotation, + annotation); + } + + @Override + Object doProvision(Object[] parameters) throws IllegalAccessException, + InvocationTargetException { + return method.invoke(instance, parameters); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ProviderMethodsModule.java b/src/main/java/com/google/inject/internal/ProviderMethodsModule.java new file mode 100644 index 0000000..6db88d0 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProviderMethodsModule.java @@ -0,0 +1,281 @@ +package com.google.inject.internal; + +import com.google.common.base.Optional; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.Message; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; +import com.google.inject.util.Modules; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and + * binding annotations on the provider method to configure the binding. + * + */ +public final class ProviderMethodsModule implements Module { + + private static ModuleAnnotatedMethodScanner PROVIDES_BUILDER = + new ModuleAnnotatedMethodScanner() { + @Override + public Key prepareMethod( + Binder binder, Annotation annotation, Key key, InjectionPoint injectionPoint) { + return key; + } + + @Override + public Set> annotationClasses() { + return ImmutableSet.of(Provides.class); + } + }; + + private final Object delegate; + private final TypeLiteral typeLiteral; + private final boolean skipFastClassGeneration; + private final ModuleAnnotatedMethodScanner scanner; + + private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration, + ModuleAnnotatedMethodScanner scanner) { + this.delegate = checkNotNull(delegate, "delegate"); + this.typeLiteral = TypeLiteral.get(this.delegate.getClass()); + this.skipFastClassGeneration = skipFastClassGeneration; + this.scanner = scanner; + } + + /** + * Returns a module which creates bindings for provider methods from the given module. + */ + public static Module forModule(Module module) { + return forObject(module, false, PROVIDES_BUILDER); + } + + /** + * Returns a module which creates bindings methods in the module that match the scanner. + */ + public static Module forModule(Object module, ModuleAnnotatedMethodScanner scanner) { + return forObject(module, false, scanner); + } + + /** + * Returns a module which creates bindings for provider methods from the given object. + * This is useful notably for GIN + * + *

This will skip bytecode generation for provider methods, since it is assumed that callers + * are only interested in Module metadata. + */ + public static Module forObject(Object object) { + return forObject(object, true, PROVIDES_BUILDER); + } + + private static Module forObject(Object object, boolean skipFastClassGeneration, + ModuleAnnotatedMethodScanner scanner) { + // avoid infinite recursion, since installing a module always installs itself + if (object instanceof ProviderMethodsModule) { + return Modules.EMPTY_MODULE; + } + + return new ProviderMethodsModule(object, skipFastClassGeneration, scanner); + } + + /** + * Returns true if a overrides b, assumes that the signatures match + */ + private static boolean overrides(Method a, Method b) { + // See JLS section 8.4.8.1 + int modifiers = b.getModifiers(); + if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { + return true; + } + if (Modifier.isPrivate(modifiers)) { + return false; + } + // b must be package-private + return a.getDeclaringClass().getPackage().equals(b.getDeclaringClass().getPackage()); + } + + public Object getDelegateModule() { + return delegate; + } + + @Override + public synchronized void configure(Binder binder) { + for (ProviderMethod providerMethod : getProviderMethods(binder)) { + providerMethod.configure(binder); + } + } + + public List> getProviderMethods(Binder binder) { + List> result = Lists.newArrayList(); + Multimap methodsBySignature = HashMultimap.create(); + for (Class c = delegate.getClass(); c != Object.class; c = c.getSuperclass()) { + for (Method method : c.getDeclaredMethods()) { + // private/static methods cannot override or be overridden by other methods, so there is no + // point in indexing them. + // Skip synthetic methods and bridge methods since java will automatically generate + // synthetic overrides in some cases where we don't want to generate an error (e.g. + // increasing visibility of a subclass). + if (((method.getModifiers() & (Modifier.PRIVATE | Modifier.STATIC)) == 0) + && !method.isBridge() && !method.isSynthetic()) { + methodsBySignature.put(new Signature(method), method); + } + Optional annotation = isProvider(binder, method); + if (annotation.isPresent()) { + result.add(createProviderMethod(binder, method, annotation.get())); + } + } + } + // we have found all the providers and now need to identify if any were overridden + // In the worst case this will have O(n^2) in the number of @Provides methods, but that is only + // assuming that every method is an override, in general it should be very quick. + for (ProviderMethod provider : result) { + Method method = provider.getMethod(); + for (Method matchingSignature : methodsBySignature.get(new Signature(method))) { + // matching signature is in the same class or a super class, therefore method cannot be + // overridding it. + if (matchingSignature.getDeclaringClass().isAssignableFrom(method.getDeclaringClass())) { + continue; + } + // now we know matching signature is in a subtype of method.getDeclaringClass() + if (overrides(matchingSignature, method)) { + String annotationString = provider.getAnnotation().annotationType() == Provides.class + ? "@Provides" : "@" + provider.getAnnotation().annotationType().getCanonicalName(); + binder.addError( + "Overriding " + annotationString + " methods is not allowed." + + "\n\t" + annotationString + " method: %s\n\toverridden by: %s", + method, + matchingSignature); + break; + } + } + } + return result; + } + + /** + * Returns true if the method is a provider. + * + * Synthetic bridge methods are excluded. Starting with JDK 8, javac copies annotations onto + * bridge methods (which always have erased signatures). + */ + private Optional isProvider(Binder binder, Method method) { + if (method.isBridge() || method.isSynthetic()) { + return Optional.absent(); + } + Annotation annotation = null; + for (Class annotationClass : scanner.annotationClasses()) { + Annotation foundAnnotation = method.getAnnotation(annotationClass); + if (foundAnnotation != null) { + if (annotation != null) { + binder.addError("More than one annotation claimed by %s on method %s." + + " Methods can only have one annotation claimed per scanner.", + scanner, method); + return Optional.absent(); + } + annotation = foundAnnotation; + } + } + return Optional.fromNullable(annotation); + } + + private ProviderMethod createProviderMethod(Binder binder, Method method, + Annotation annotation) { + binder = binder.withSource(method); + Errors errors = new Errors(method); + + // prepare the parameter providers + InjectionPoint point = InjectionPoint.forMethod(method, typeLiteral); + List> dependencies = point.getDependencies(); + List> parameterProviders = Lists.newArrayList(); + for (Dependency dependency : point.getDependencies()) { + parameterProviders.add(binder.getProvider(dependency)); + } + + @SuppressWarnings("unchecked") // Define T as the method's return type. + TypeLiteral returnType = (TypeLiteral) typeLiteral.getReturnType(method); + Key key = getKey(errors, returnType, method, method.getAnnotations()); + try { + key = scanner.prepareMethod(binder, annotation, key, point); + } catch (Throwable t) { + binder.addError(t); + } + Class scopeAnnotation + = Annotations.findScopeAnnotation(errors, method.getAnnotations()); + for (Message message : errors.getMessages()) { + binder.addError(message); + } + return ProviderMethod.create(key, method, delegate, ImmutableSet.copyOf(dependencies), + parameterProviders, scopeAnnotation, skipFastClassGeneration, annotation); + } + + Key getKey(Errors errors, TypeLiteral type, Member member, Annotation[] annotations) { + Annotation bindingAnnotation = Annotations.findBindingAnnotation(errors, member, annotations); + return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation); + } + + @Override + public boolean equals(Object o) { + return o instanceof ProviderMethodsModule + && ((ProviderMethodsModule) o).delegate == delegate + && ((ProviderMethodsModule) o).scanner == scanner; + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + private final class Signature { + final Class[] parameters; + final String name; + final int hashCode; + + Signature(Method method) { + this.name = method.getName(); + // We need to 'resolve' the parameters against the actual class type in case this method uses + // type parameters. This is so we can detect overrides of generic superclass methods where + // the subclass specifies the type parameter. javac implements these kinds of overrides via + // bridge methods, but we don't want to give errors on bridge methods (but rather the target + // of the bridge). + List> resolvedParameterTypes = typeLiteral.getParameterTypes(method); + this.parameters = new Class[resolvedParameterTypes.size()]; + int i = 0; + for (TypeLiteral type : resolvedParameterTypes) { + parameters[i] = type.getRawType(); + } + this.hashCode = name.hashCode() + 31 * Arrays.hashCode(parameters); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Signature) { + Signature other = (Signature) obj; + return other.name.equals(name) && Arrays.equals(parameters, other.parameters); + } + return false; + } + + @Override + public int hashCode() { + return hashCode; + } + } +} diff --git a/src/main/java/com/google/inject/internal/ProviderToInternalFactoryAdapter.java b/src/main/java/com/google/inject/internal/ProviderToInternalFactoryAdapter.java new file mode 100644 index 0000000..c527bed --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProviderToInternalFactoryAdapter.java @@ -0,0 +1,41 @@ +package com.google.inject.internal; + +import com.google.inject.Provider; +import com.google.inject.ProvisionException; +import com.google.inject.spi.Dependency; + +final class ProviderToInternalFactoryAdapter implements Provider { + + private final InjectorImpl injector; + private final InternalFactory internalFactory; + + public ProviderToInternalFactoryAdapter(InjectorImpl injector, + InternalFactory internalFactory) { + this.injector = injector; + this.internalFactory = internalFactory; + } + + public T get() { + final Errors errors = new Errors(); + try { + T t = injector.callInContext(new ContextualCallable() { + public T call(InternalContext context) throws ErrorsException { + Dependency dependency = context.getDependency(); + // Always pretend that we are a linked binding, to support + // scoping implicit bindings. If we are not actually a linked + // binding, we'll fail properly elsewhere in the chain. + return internalFactory.get(errors, context, dependency, true); + } + }); + errors.throwIfNewErrors(0); + return t; + } catch (ErrorsException e) { + throw new ProvisionException(errors.merge(e.getErrors()).getMessages()); + } + } + + @Override + public String toString() { + return internalFactory.toString(); + } +} diff --git a/src/main/java/com/google/inject/internal/ProvisionListenerCallbackStore.java b/src/main/java/com/google/inject/internal/ProvisionListenerCallbackStore.java new file mode 100644 index 0000000..2160843 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProvisionListenerCallbackStore.java @@ -0,0 +1,114 @@ +package com.google.inject.internal; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Stage; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.spi.ProvisionListenerBinding; + +import java.util.List; +import java.util.Set; + +/** + * {@link ProvisionListenerStackCallback} for each key. + * + */ +final class ProvisionListenerCallbackStore { + + // TODO(sameb): Consider exposing this in the API somehow? Maybe? + // Lots of code often want to skip over the internal stuffs. + private static final Set> INTERNAL_BINDINGS = + ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class)); + + private final ImmutableList listenerBindings; + + private final LoadingCache> cache + = CacheBuilder.newBuilder().build( + new CacheLoader>() { + public ProvisionListenerStackCallback load(KeyBinding key) { + return create(key.binding); + } + }); + + ProvisionListenerCallbackStore(List listenerBindings) { + this.listenerBindings = ImmutableList.copyOf(listenerBindings); + } + + /** + * Returns a new {@link ProvisionListenerStackCallback} for the key. + */ + @SuppressWarnings("unchecked") // the ProvisionListenerStackCallback type always agrees with the passed type + public ProvisionListenerStackCallback get(Binding binding) { + // Never notify any listeners for internal bindings. + if (!INTERNAL_BINDINGS.contains(binding.getKey())) { + return (ProvisionListenerStackCallback) cache.getUnchecked( + new KeyBinding(binding.getKey(), binding)); + } + return ProvisionListenerStackCallback.emptyListener(); + } + + /** + * Purges a key from the cache. Use this only if the type is not actually valid for + * binding and needs to be purged. (See issue 319 and + * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and + * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is + * necessary.) + * + * Returns true if the type was stored in the cache, false otherwise. + */ + boolean remove(Binding type) { + return cache.asMap().remove(type) != null; + } + + /** + * Creates a new {@link ProvisionListenerStackCallback} with the correct listeners + * for the key. + */ + private ProvisionListenerStackCallback create(Binding binding) { + List listeners = null; + for (ProvisionListenerBinding provisionBinding : listenerBindings) { + if (provisionBinding.getBindingMatcher().matches(binding)) { + if (listeners == null) { + listeners = Lists.newArrayList(); + } + listeners.addAll(provisionBinding.getListeners()); + } + } + if (listeners == null || listeners.isEmpty()) { + // Optimization: don't bother constructing the callback if there are + // no listeners. + return ProvisionListenerStackCallback.emptyListener(); + } + return new ProvisionListenerStackCallback(binding, listeners); + } + + /** + * A struct that holds key & binding but uses just key for equality/hashcode. + */ + private static class KeyBinding { + final Key key; + final Binding binding; + + KeyBinding(Key key, Binding binding) { + this.key = key; + this.binding = binding; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof KeyBinding && key.equals(((KeyBinding) obj).key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java b/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java new file mode 100644 index 0000000..7e9f2fc --- /dev/null +++ b/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java @@ -0,0 +1,137 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import com.google.inject.Binding; +import com.google.inject.ProvisionException; +import com.google.inject.spi.DependencyAndSource; +import com.google.inject.spi.ProvisionListener; + +import java.util.List; +import java.util.Set; + +/** + * Intercepts provisions with a stack of listeners. + * + */ +final class ProvisionListenerStackCallback { + + private static final ProvisionListener EMPTY_LISTENER[] = new ProvisionListener[0]; + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final ProvisionListenerStackCallback EMPTY_CALLBACK = + new ProvisionListenerStackCallback(null /* unused, so ok */, ImmutableList.of()); + + private final ProvisionListener[] listeners; + private final Binding binding; + + public ProvisionListenerStackCallback(Binding binding, List listeners) { + this.binding = binding; + if (listeners.isEmpty()) { + this.listeners = EMPTY_LISTENER; + } else { + Set deDuplicated = Sets.newLinkedHashSet(listeners); + this.listeners = deDuplicated.toArray(new ProvisionListener[deDuplicated.size()]); + } + } + + @SuppressWarnings("unchecked") + public static ProvisionListenerStackCallback emptyListener() { + return (ProvisionListenerStackCallback) EMPTY_CALLBACK; + } + + public boolean hasListeners() { + return listeners.length > 0; + } + + public T provision(Errors errors, InternalContext context, ProvisionCallback callable) + throws ErrorsException { + Provision provision = new Provision(errors, context, callable); + RuntimeException caught = null; + try { + provision.provision(); + } catch (RuntimeException t) { + caught = t; + } + + if (provision.exceptionDuringProvision != null) { + throw provision.exceptionDuringProvision; + } else if (caught != null) { + Object listener = provision.erredListener != null ? + provision.erredListener.getClass() : "(unknown)"; + throw errors + .errorInUserCode(caught, "Error notifying ProvisionListener %s of %s.%n" + + " Reason: %s", listener, binding.getKey(), caught) + .toException(); + } else { + return provision.result; + } + } + + // TODO(sameb): Can this be more InternalFactory-like? + public interface ProvisionCallback { + T call() throws ErrorsException; + } + + private class Provision extends ProvisionListener.ProvisionInvocation { + + final Errors errors; + final int numErrorsBefore; + final InternalContext context; + final ProvisionCallback callable; + int index = -1; + T result; + ErrorsException exceptionDuringProvision; + ProvisionListener erredListener; + + public Provision(Errors errors, InternalContext context, ProvisionCallback callable) { + this.callable = callable; + this.context = context; + this.errors = errors; + this.numErrorsBefore = errors.size(); + } + + @Override + public T provision() { + index++; + if (index == listeners.length) { + try { + result = callable.call(); + // Make sure we don't return the provisioned object if there were any errors + // injecting its field/method dependencies. + errors.throwIfNewErrors(numErrorsBefore); + } catch (ErrorsException ee) { + exceptionDuringProvision = ee; + throw new ProvisionException(errors.merge(ee.getErrors()).getMessages()); + } + } else if (index < listeners.length) { + int currentIdx = index; + try { + listeners[index].onProvision(this); + } catch (RuntimeException re) { + erredListener = listeners[currentIdx]; + throw re; + } + if (currentIdx == index) { + // Our listener didn't provision -- do it for them. + provision(); + } + } else { + throw new IllegalStateException("Already provisioned in this listener."); + } + return result; + } + + @Override + public Binding getBinding() { + // TODO(sameb): Because so many places cast directly to BindingImpl & subclasses, + // we can't decorate this to prevent calling getProvider().get(), which means + // if someone calls that they'll get strange errors. + return binding; + } + + @Override + public List getDependencyChain() { + return context.getDependencyChain(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java b/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java new file mode 100644 index 0000000..b702190 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java @@ -0,0 +1,46 @@ +package com.google.inject.internal; + +import com.google.inject.Scope; +import com.google.inject.spi.ScopeBinding; + +import java.lang.annotation.Annotation; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Handles {@code Binder.bindScope} commands. + * + */ +final class ScopeBindingProcessor extends AbstractProcessor { + + ScopeBindingProcessor(Errors errors) { + super(errors); + } + + @Override + public Boolean visit(ScopeBinding command) { + Scope scope = checkNotNull(command.getScope(), "scope"); + Class annotationType = checkNotNull(command.getAnnotationType(), "annotation type"); + + if (!Annotations.isScopeAnnotation(annotationType)) { + errors.missingScopeAnnotation(annotationType); + // Go ahead and bind anyway so we don't get collateral errors. + } + + if (!Annotations.isRetainedAtRuntime(annotationType)) { + errors.missingRuntimeRetention(annotationType); + // Go ahead and bind anyway so we don't get collateral errors. + } + + ScopeBinding existing = injector.state.getScopeBinding(annotationType); + if (existing != null) { + if (!scope.equals(existing.getScope())) { + errors.duplicateScopes(existing, annotationType, scope); + } + } else { + injector.state.putScopeBinding(annotationType, command); + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/internal/Scoping.java b/src/main/java/com/google/inject/internal/Scoping.java new file mode 100644 index 0000000..5e19a64 --- /dev/null +++ b/src/main/java/com/google/inject/internal/Scoping.java @@ -0,0 +1,273 @@ +package com.google.inject.internal; + +import com.google.common.base.Objects; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.Stage; +import com.google.inject.binder.ScopedBindingBuilder; +import com.google.inject.spi.BindingScopingVisitor; +import com.google.inject.spi.ScopeBinding; + +import java.lang.annotation.Annotation; + +/** + * References a scope, either directly (as a scope instance), or indirectly (as a scope annotation). + * The scope's eager or laziness is also exposed. + * + */ +public abstract class Scoping { + + /** + * No scoping annotation has been applied. Note that this is different from {@code + * in(Scopes.NO_SCOPE)}, where the 'NO_SCOPE' has been explicitly applied. + */ + public static final Scoping UNSCOPED = new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitNoScoping(); + } + + @Override + public Scope getScopeInstance() { + return Scopes.NO_SCOPE; + } + + @Override + public String toString() { + return Scopes.NO_SCOPE.toString(); + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + // do nothing + } + }; + + public static final Scoping SINGLETON_ANNOTATION = new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScopeAnnotation(Singleton.class); + } + + @Override + public Class getScopeAnnotation() { + return Singleton.class; + } + + @Override + public String toString() { + return Singleton.class.getName(); + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Singleton.class); + } + }; + + public static final Scoping SINGLETON_INSTANCE = new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScope(Scopes.SINGLETON); + } + + @Override + public Scope getScopeInstance() { + return Scopes.SINGLETON; + } + + @Override + public String toString() { + return Scopes.SINGLETON.toString(); + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Scopes.SINGLETON); + } + }; + + public static final Scoping EAGER_SINGLETON = new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitEagerSingleton(); + } + + @Override + public Scope getScopeInstance() { + return Scopes.SINGLETON; + } + + @Override + public String toString() { + return "eager singleton"; + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.asEagerSingleton(); + } + }; + + private Scoping() { + } + + public static Scoping forAnnotation(final Class scopingAnnotation) { + if (scopingAnnotation == Singleton.class + || scopingAnnotation == javax.inject.Singleton.class) { + return SINGLETON_ANNOTATION; + } + + return new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScopeAnnotation(scopingAnnotation); + } + + @Override + public Class getScopeAnnotation() { + return scopingAnnotation; + } + + @Override + public String toString() { + return scopingAnnotation.getName(); + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(scopingAnnotation); + } + }; + } + + public static Scoping forInstance(final Scope scope) { + if (scope == Scopes.SINGLETON) { + return SINGLETON_INSTANCE; + } + + return new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScope(scope); + } + + @Override + public Scope getScopeInstance() { + return scope; + } + + @Override + public String toString() { + return scope.toString(); + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(scope); + } + }; + } + + /** + * Scopes an internal factory. + */ + static InternalFactory scope(Key key, InjectorImpl injector, + InternalFactory creator, Object source, Scoping scoping) { + + if (scoping.isNoScope()) { + return creator; + } + + Scope scope = scoping.getScopeInstance(); + + Provider scoped + = scope.scope(key, new ProviderToInternalFactoryAdapter(injector, creator)); + return new InternalFactoryToProviderAdapter(scoped, source); + } + + /** + * Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance + * map. If the scope annotation has no corresponding instance, an error will be added and unscoped + * will be retuned. + */ + static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) { + Class scopeAnnotation = scoping.getScopeAnnotation(); + if (scopeAnnotation == null) { + return scoping; + } + + ScopeBinding scope = injector.state.getScopeBinding(scopeAnnotation); + if (scope != null) { + return forInstance(scope.getScope()); + } + + errors.scopeNotFound(scopeAnnotation); + return UNSCOPED; + } + + /** + * Returns true if this scope was explicitly applied. If no scope was explicitly applied then the + * scoping annotation will be used. + */ + public boolean isExplicitlyScoped() { + return this != UNSCOPED; + } + + /** + * Returns true if this is the default scope. In this case a new instance will be provided for + * each injection. + */ + public boolean isNoScope() { + return getScopeInstance() == Scopes.NO_SCOPE; + } + + /** + * Returns true if this scope is a singleton that should be loaded eagerly in {@code stage}. + */ + public boolean isEagerSingleton(Stage stage) { + if (this == EAGER_SINGLETON) { + return true; + } + + return stage == Stage.PRODUCTION && (this == SINGLETON_ANNOTATION || this == SINGLETON_INSTANCE); + + } + + /** + * Returns the scope instance, or {@code null} if that isn't known for this instance. + */ + public Scope getScopeInstance() { + return null; + } + + /** + * Returns the scope annotation, or {@code null} if that isn't known for this instance. + */ + public Class getScopeAnnotation() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Scoping) { + Scoping o = (Scoping) obj; + return Objects.equal(getScopeAnnotation(), o.getScopeAnnotation()) + && Objects.equal(getScopeInstance(), o.getScopeInstance()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getScopeAnnotation(), getScopeInstance()); + } + + public abstract V acceptVisitor(BindingScopingVisitor visitor); + + public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder); +} diff --git a/src/main/java/com/google/inject/internal/SingleFieldInjector.java b/src/main/java/com/google/inject/internal/SingleFieldInjector.java new file mode 100644 index 0000000..d271483 --- /dev/null +++ b/src/main/java/com/google/inject/internal/SingleFieldInjector.java @@ -0,0 +1,48 @@ +package com.google.inject.internal; + +import com.google.inject.internal.InjectorImpl.JitLimitation; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.InjectionPoint; + +import java.lang.reflect.Field; + +/** + * Sets an injectable field. + */ +final class SingleFieldInjector implements SingleMemberInjector { + final Field field; + final InjectionPoint injectionPoint; + final Dependency dependency; + final BindingImpl binding; + + public SingleFieldInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors) + throws ErrorsException { + this.injectionPoint = injectionPoint; + this.field = (Field) injectionPoint.getMember(); + this.dependency = injectionPoint.getDependencies().get(0); + + // Ewwwww... + field.setAccessible(true); + binding = injector.getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT); + } + + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + public void inject(Errors errors, InternalContext context, Object o) { + errors = errors.withSource(dependency); + + Dependency previous = context.pushDependency(dependency, binding.getSource()); + try { + Object value = binding.getInternalFactory().get(errors, context, dependency, false); + field.set(o, value); + } catch (ErrorsException e) { + errors.withSource(injectionPoint).merge(e.getErrors()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); // a security manager is blocking us, we're hosed + } finally { + context.popStateAndSetDependency(previous); + } + } +} diff --git a/src/main/java/com/google/inject/internal/SingleMemberInjector.java b/src/main/java/com/google/inject/internal/SingleMemberInjector.java new file mode 100644 index 0000000..dc02ff5 --- /dev/null +++ b/src/main/java/com/google/inject/internal/SingleMemberInjector.java @@ -0,0 +1,12 @@ +package com.google.inject.internal; + +import com.google.inject.spi.InjectionPoint; + +/** + * Injects a field or method of a given object. + */ +interface SingleMemberInjector { + void inject(Errors errors, InternalContext context, Object o); + + InjectionPoint getInjectionPoint(); +} diff --git a/src/main/java/com/google/inject/internal/SingleMethodInjector.java b/src/main/java/com/google/inject/internal/SingleMethodInjector.java new file mode 100644 index 0000000..6a62c9b --- /dev/null +++ b/src/main/java/com/google/inject/internal/SingleMethodInjector.java @@ -0,0 +1,65 @@ +package com.google.inject.internal; + +import com.google.inject.internal.InjectorImpl.MethodInvoker; +import com.google.inject.spi.InjectionPoint; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Invokes an injectable method. + */ +final class SingleMethodInjector implements SingleMemberInjector { + private final MethodInvoker methodInvoker; + private final SingleParameterInjector[] parameterInjectors; + private final InjectionPoint injectionPoint; + + SingleMethodInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors) + throws ErrorsException { + this.injectionPoint = injectionPoint; + final Method method = (Method) injectionPoint.getMember(); + methodInvoker = createMethodInvoker(method); + parameterInjectors = injector.getParametersInjectors(injectionPoint.getDependencies(), errors); + } + + private MethodInvoker createMethodInvoker(final Method method) { + int modifiers = method.getModifiers(); + if (!Modifier.isPublic(modifiers) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + method.setAccessible(true); + } + + return new MethodInvoker() { + public Object invoke(Object target, Object... parameters) + throws IllegalAccessException, InvocationTargetException { + return method.invoke(target, parameters); + } + }; + } + + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + public void inject(Errors errors, InternalContext context, Object o) { + Object[] parameters; + try { + parameters = SingleParameterInjector.getAll(errors, context, parameterInjectors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + return; + } + + try { + methodInvoker.invoke(o, parameters); + } catch (IllegalAccessException e) { + throw new AssertionError(e); // a security manager is blocking us, we're hosed + } catch (InvocationTargetException userException) { + Throwable cause = userException.getCause() != null + ? userException.getCause() + : userException; + errors.withSource(injectionPoint).errorInjectingMethod(cause); + } + } +} diff --git a/src/main/java/com/google/inject/internal/SingleParameterInjector.java b/src/main/java/com/google/inject/internal/SingleParameterInjector.java new file mode 100644 index 0000000..1d9b3e6 --- /dev/null +++ b/src/main/java/com/google/inject/internal/SingleParameterInjector.java @@ -0,0 +1,55 @@ +package com.google.inject.internal; + +import com.google.inject.spi.Dependency; + +/** + * Resolves a single parameter, to be used in a constructor or method invocation. + */ +final class SingleParameterInjector { + private static final Object[] NO_ARGUMENTS = {}; + + private final Dependency dependency; + private final BindingImpl binding; + + SingleParameterInjector(Dependency dependency, BindingImpl binding) { + this.dependency = dependency; + this.binding = binding; + } + + /** + * Returns an array of parameter values. + */ + static Object[] getAll(Errors errors, InternalContext context, + SingleParameterInjector[] parameterInjectors) throws ErrorsException { + if (parameterInjectors == null) { + return NO_ARGUMENTS; + } + + int numErrorsBefore = errors.size(); + + int size = parameterInjectors.length; + Object[] parameters = new Object[size]; + + // optimization: use manual for/each to save allocating an iterator here + for (int i = 0; i < size; i++) { + SingleParameterInjector parameterInjector = parameterInjectors[i]; + try { + parameters[i] = parameterInjector.inject(errors, context); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + errors.throwIfNewErrors(numErrorsBefore); + return parameters; + } + + private T inject(Errors errors, InternalContext context) throws ErrorsException { + Dependency previous = context.pushDependency(dependency, binding.getSource()); + try { + return binding.getInternalFactory().get(errors.withSource(dependency), context, dependency, false); + } finally { + context.popStateAndSetDependency(previous); + } + } +} diff --git a/src/main/java/com/google/inject/internal/SingletonScope.java b/src/main/java/com/google/inject/internal/SingletonScope.java new file mode 100644 index 0000000..995b932 --- /dev/null +++ b/src/main/java/com/google/inject/internal/SingletonScope.java @@ -0,0 +1,341 @@ +package com.google.inject.internal; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; +import com.google.inject.Scope; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.DependencyAndSource; +import com.google.inject.spi.Message; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * One instance per {@link Injector}. Also see {@code @}{@link Singleton}. + * + * Introduction from the author: + * Implementation of this class seems unreasonably complicated at the first sight. + * I fully agree with you, that the beast below is very complex + * and it's hard to reason on how does it work or not. + * Still I want to assure you that hundreds(?) of hours were thrown + * into making this code simple, while still maintaining Singleton contract. + * + * Anyway, why is it so complex? Singleton scope does not seem to be that unique. + * 1) Guice has never truly expected to be used in multi threading environment + * with many Injectors working alongside each other. There is almost no + * code with Guice that propagates state between threads. And Singleton + * scope is The exception. + * 2) Guice supports circular dependencies and thus manages proxy objects. + * There is no interface that allows user defined Scopes to create proxies, + * it is expected to be done by Guice. Singleton scope needs to be + * able to detect circular dependencies spanning several threads, + * therefore Singleton scope needs to be able to create these proxies. + * 3) To make things worse, Guice has a very tricky definition for a binding + * resolution when Injectors are in in a parent/child relationship. + * And Scope does not have access to this information by design, + * the only real action that Scope can do is to call or not to call a creator. + * 4) There is no readily available code in Guice that can detect a potential + * deadlock, and no code for handling dependency cycles spanning several threads. + * This is significantly harder as all the dependencies in a thread at runtime + * can be represented with a list, where in a multi threaded environment + * we have more complex dependency trees. + * 5) Guice has a pretty strong contract regarding Garbage Collection, + * which often prevents us from linking objects directly. + * So simple domain specific code can not be written and intermediary + * id objects need to be managed. + * 6) Guice is relatively fast and we should not make things worse. + * We're trying our best to optimize synchronization for speed and memory. + * Happy path should be almost as fast as in a single threaded solution + * and should not take much more memory. + * 7) Error message generation in Guice was not meant to be used like this and to work around + * its APIs we need a lot of code. Additional complexity comes from inherent data races + * as message is only generated when failure occurs on proxy object generation. + * Things get ugly pretty fast. + * + */ +public class SingletonScope implements Scope { + + /** + * A sentinel value representing null. + */ + private static final Object NULL = new Object(); + + /** + * Allows us to detect when circular proxies are necessary. It's only used during singleton + * instance initialization, after initialization direct access through volatile field is used. + * + * NB: Factory uses {@link Key}s as a user locks ids, different injectors can + * share them. Cycles are detected properly as cycle detection does not rely on user locks ids, + * but error message generated could be less than ideal. + * + * TODO(user): we may use one factory per injector tree for optimization reasons + */ + private static final CycleDetectingLockFactory> cycleDetectingLockFactory = + new CycleDetectingLockFactory>(); + + /** + * Provides singleton scope with the following properties: + * - creates no more than one instance per Key as a creator is used no more than once, + * - result is cached and returned quickly on subsequent calls, + * - exception in a creator is not treated as instance creation and is not cached, + * - creates singletons in parallel whenever possible, + * - waits for dependent singletons to be created even across threads and when dependencies + * are shared as long as no circular dependencies are detected, + * - returns circular proxy only when circular dependencies are detected, + * - aside from that, blocking synchronization is only used for proxy creation and initialization, + * + * @see CycleDetectingLockFactory + */ + public Provider scope(final Key key, final Provider creator) { + /** + * Locking strategy: + * - volatile instance: double-checked locking for quick exit when scope is initialized, + * - constructionContext: manipulations with proxies list or instance initialization + * - creationLock: singleton instance creation, + * -- allows to guarantee only one instance per singleton, + * -- special type of a lock, that prevents potential deadlocks, + * -- guards constructionContext for all operations except proxy creation + */ + return new Provider() { + /** + * Circular proxies are used when potential deadlocks are detected. Guarded by itself. + * ConstructionContext is not thread-safe, so each call should be synchronized. + */ + final ConstructionContext constructionContext = new ConstructionContext(); + /** For each binding there is a separate lock that we hold during object creation. */ + final CycleDetectingLock> creationLock = cycleDetectingLockFactory.create(key); + /** + * The lazily initialized singleton instance. Once set, this will either have type T or will + * be equal to NULL. Would never be reset to null. + */ + volatile Object instance; + + @SuppressWarnings("DoubleCheckedLocking") + public T get() { + // cache volatile variable for the usual case of already initialized object + final Object initialInstance = instance; + if (initialInstance == null) { + // instance is not initialized yet + + // acquire lock for current binding to initialize an instance + final ListMultimap> locksCycle = + creationLock.lockOrDetectPotentialLocksCycle(); + if (locksCycle.isEmpty()) { + // this thread now owns creation of an instance + try { + // intentionally reread volatile variable to prevent double initialization + if (instance == null) { + // creator throwing an exception can cause circular proxies created in + // different thread to never be resolved, just a warning + T provided = creator.get(); + Object providedNotNull = provided == null ? NULL : provided; + + // scope called recursively can initialize instance as a side effect + if (instance == null) { + // instance is still not initialized, se we can proceed + + // don't remember proxies created by Guice on circular dependency + // detection within the same thread; they are not real instances to cache + if (Scopes.isCircularProxy(provided)) { + return provided; + } + + synchronized (constructionContext) { + // guarantee thread-safety for instance and proxies initialization + instance = providedNotNull; + constructionContext.setProxyDelegates(provided); + } + } else { + // safety assert in case instance was initialized + Preconditions.checkState(instance == providedNotNull, + "Singleton is called recursively returning different results"); + } + } + } catch (RuntimeException e) { + // something went wrong, be sure to clean a construction context + // this helps to prevent potential memory leaks in circular proxies list + synchronized (constructionContext) { + constructionContext.finishConstruction(); + } + throw e; + } finally { + // always release our creation lock, even on failures + creationLock.unlock(); + } + } else { + // potential deadlock detected, creation lock is not taken by this thread + synchronized (constructionContext) { + // guarantee thread-safety for instance and proxies initialization + if (instance == null) { + // InjectorImpl.callInContext() sets this context when scope is called from Guice + Map globalInternalContext = + InjectorImpl.getGlobalInternalContext(); + InternalContext internalContext = globalInternalContext.get(Thread.currentThread()); + + // creating a proxy to satisfy circular dependency across several threads + Dependency dependency = Preconditions.checkNotNull( + internalContext.getDependency(), + "globalInternalContext.get(currentThread()).getDependency()"); + Class rawType = dependency.getKey().getTypeLiteral().getRawType(); + + try { + @SuppressWarnings("unchecked") + T proxy = (T) constructionContext.createProxy( + new Errors(), internalContext.getInjectorOptions(), rawType); + return proxy; + } catch (ErrorsException e) { + // best effort to create a rich error message + List exceptionErrorMessages = e.getErrors().getMessages(); + // we expect an error thrown + Preconditions.checkState(exceptionErrorMessages.size() == 1); + // explicitly copy the map to guarantee iteration correctness + // it's ok to have a data race with other threads that are locked + Message cycleDependenciesMessage = createCycleDependenciesMessage( + ImmutableMap.copyOf(globalInternalContext), + locksCycle, + exceptionErrorMessages.get(0)); + // adding stack trace generated by us in addition to a standard one + throw new ProvisionException(ImmutableList.of( + cycleDependenciesMessage, exceptionErrorMessages.get(0))); + } + } + } + } + // at this point we're sure that singleton was initialized, + // reread volatile variable to catch all corner cases + + // caching volatile variable to minimize number of reads performed + final Object initializedInstance = instance; + Preconditions.checkState(initializedInstance != null, + "Internal error: Singleton is not initialized contrary to our expectations"); + @SuppressWarnings("unchecked") + T initializedTypedInstance = (T) initializedInstance; + return initializedInstance == NULL ? null : initializedTypedInstance; + } else { + // singleton is already initialized and local cache can be used + @SuppressWarnings("unchecked") + T typedInitialIntance = (T) initialInstance; + return initialInstance == NULL ? null : typedInitialIntance; + } + } + + /** + * Helper method to create beautiful and rich error descriptions. Best effort and slow. + * Tries its best to provide dependency information from injectors currently available + * in a global internal context. + * + *

The main thing being done is creating a list of Dependencies involved into + * lock cycle across all the threads involved. This is a structure we're creating: + *

+             * { Current Thread, C.class, B.class, Other Thread, B.class, C.class, Current Thread }
+             * To be inserted in the beginning by Guice: { A.class, B.class, C.class }
+             * 
+ * When we're calling Guice to create A and it fails in the deadlock while trying to + * create C, which is being created by another thread, which waits for B. List would + * be reversed before printing it to the end user. + */ + private Message createCycleDependenciesMessage( + Map globalInternalContext, + ListMultimap> locksCycle, + Message proxyCreationError) { + // this is the main thing that we'll show in an error message, + // current thread is populate by Guice + List sourcesCycle = Lists.newArrayList(); + sourcesCycle.add(Thread.currentThread()); + // temp map to speed up look ups + Map threadById = Maps.newHashMap(); + for (Thread thread : globalInternalContext.keySet()) { + threadById.put(thread.getId(), thread); + } + for (long lockedThreadId : locksCycle.keySet()) { + Thread lockedThread = threadById.get(lockedThreadId); + List> lockedKeys = Collections.unmodifiableList(locksCycle.get(lockedThreadId)); + if (lockedThread == null) { + // thread in a lock cycle is already terminated + continue; + } + List dependencyChain = null; + boolean allLockedKeysAreFoundInDependencies = false; + // thread in a cycle is still present + InternalContext lockedThreadInternalContext = globalInternalContext.get(lockedThread); + if (lockedThreadInternalContext != null) { + dependencyChain = lockedThreadInternalContext.getDependencyChain(); + + // check that all of the keys are still present in dependency chain in order + List> lockedKeysToFind = Lists.newLinkedList(lockedKeys); + // check stack trace of the thread + for (DependencyAndSource d : dependencyChain) { + Dependency dependency = d.getDependency(); + if (dependency == null) { + continue; + } + if (dependency.getKey().equals(lockedKeysToFind.get(0))) { + lockedKeysToFind.remove(0); + if (lockedKeysToFind.isEmpty()) { + // everything is found! + allLockedKeysAreFoundInDependencies = true; + break; + } + } + } + } + if (allLockedKeysAreFoundInDependencies) { + // all keys are present in a dependency chain of a thread's last injector, + // highly likely that we just have discovered a dependency + // chain that is part of a lock cycle starting with the first lock owned + Key firstLockedKey = lockedKeys.get(0); + boolean firstLockedKeyFound = false; + for (DependencyAndSource d : dependencyChain) { + Dependency dependency = d.getDependency(); + if (dependency == null) { + continue; + } + if (firstLockedKeyFound) { + sourcesCycle.add(dependency); + sourcesCycle.add(d.getBindingSource()); + } else if (dependency.getKey().equals(firstLockedKey)) { + firstLockedKeyFound = true; + // for the very first one found we don't care why, so no dependency is added + sourcesCycle.add(d.getBindingSource()); + } + } + } else { + // something went wrong and not all keys are present in a state of an injector + // that was used last for a current thread. + // let's add all keys we're aware of, still better than nothing + sourcesCycle.addAll(lockedKeys); + } + // mentions that a tread is a part of a cycle + sourcesCycle.add(lockedThread); + } + return new Message( + sourcesCycle, + String.format("Encountered circular dependency spanning several threads. %s", + proxyCreationError.getMessage()), + null); + } + + @Override + public String toString() { + return String.format("%s[%s]", creator, Scopes.SINGLETON); + } + }; + } + + @Override + public String toString() { + return "Scopes.SINGLETON"; + } +} diff --git a/src/main/java/com/google/inject/internal/State.java b/src/main/java/com/google/inject/internal/State.java new file mode 100644 index 0000000..9cc4aa4 --- /dev/null +++ b/src/main/java/com/google/inject/internal/State.java @@ -0,0 +1,185 @@ +package com.google.inject.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Scope; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; +import com.google.inject.spi.ProvisionListenerBinding; +import com.google.inject.spi.ScopeBinding; +import com.google.inject.spi.TypeConverterBinding; +import com.google.inject.spi.TypeListenerBinding; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The inheritable data within an injector. This class is intended to allow parent and local + * injector data to be accessed as a unit. + * + */ +interface State { + + static final State NONE = new State() { + public State parent() { + throw new UnsupportedOperationException(); + } + + public BindingImpl getExplicitBinding(Key key) { + return null; + } + + public Map, Binding> getExplicitBindingsThisLevel() { + throw new UnsupportedOperationException(); + } + + public void putBinding(Key key, BindingImpl binding) { + throw new UnsupportedOperationException(); + } + + public ScopeBinding getScopeBinding(Class scopingAnnotation) { + return null; + } + + public void putScopeBinding(Class annotationType, ScopeBinding scope) { + throw new UnsupportedOperationException(); + } + + public void addConverter(TypeConverterBinding typeConverterBinding) { + throw new UnsupportedOperationException(); + } + + public TypeConverterBinding getConverter(String stringValue, TypeLiteral type, Errors errors, + Object source) { + throw new UnsupportedOperationException(); + } + + public Iterable getConvertersThisLevel() { + return ImmutableSet.of(); + } + + public void addTypeListener(TypeListenerBinding typeListenerBinding) { + throw new UnsupportedOperationException(); + } + + public List getTypeListenerBindings() { + return ImmutableList.of(); + } + + public void addProvisionListener(ProvisionListenerBinding provisionListenerBinding) { + throw new UnsupportedOperationException(); + } + + public List getProvisionListenerBindings() { + return ImmutableList.of(); + } + + public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { + throw new UnsupportedOperationException(); + } + + public List getScannerBindings() { + return ImmutableList.of(); + } + + public void blacklist(Key key, State state, Object source) { + } + + public boolean isBlacklisted(Key key) { + return true; + } + + public Set getSourcesForBlacklistedKey(Key key) { + throw new UnsupportedOperationException(); + } + + public Object lock() { + throw new UnsupportedOperationException(); + } + + public Object singletonCreationLock() { + throw new UnsupportedOperationException(); + } + + public Map, Scope> getScopes() { + return ImmutableMap.of(); + } + }; + + State parent(); + + /** + * Gets a binding which was specified explicitly in a module, or null. + */ + BindingImpl getExplicitBinding(Key key); + + /** + * Returns the explicit bindings at this level only. + */ + Map, Binding> getExplicitBindingsThisLevel(); + + void putBinding(Key key, BindingImpl binding); + + ScopeBinding getScopeBinding(Class scopingAnnotation); + + void putScopeBinding(Class annotationType, ScopeBinding scope); + + void addConverter(TypeConverterBinding typeConverterBinding); + + /** + * Returns the matching converter for {@code type}, or null if none match. + */ + TypeConverterBinding getConverter( + String stringValue, TypeLiteral type, Errors errors, Object source); + + /** + * Returns all converters at this level only. + */ + Iterable getConvertersThisLevel(); + + void addTypeListener(TypeListenerBinding typeListenerBinding); + + List getTypeListenerBindings(); + + void addProvisionListener(ProvisionListenerBinding provisionListenerBinding); + + List getProvisionListenerBindings(); + + void addScanner(ModuleAnnotatedMethodScannerBinding scanner); + + List getScannerBindings(); + + /** + * Forbids the corresponding injector from creating a binding to {@code key}. Child injectors + * blacklist their bound keys on their parent injectors to prevent just-in-time bindings on the + * parent injector that would conflict and pass along their state to control the lifetimes. + */ + void blacklist(Key key, State state, Object source); + + /** + * Returns true if {@code key} is forbidden from being bound in this injector. This indicates that + * one of this injector's descendent's has bound the key. + */ + boolean isBlacklisted(Key key); + + /** + * Returns the source of a blacklisted key. + */ + Set getSourcesForBlacklistedKey(Key key); + + /** + * Returns the shared lock for all injector data. This is a low-granularity, high-contention lock + * to be used when reading mutable data (ie. just-in-time bindings, and binding blacklists). + */ + Object lock(); + + /** + * Returns all the scope bindings at this level and parent levels. + */ + Map, Scope> getScopes(); +} diff --git a/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java b/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java new file mode 100644 index 0000000..59bf48a --- /dev/null +++ b/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java @@ -0,0 +1,172 @@ +package com.google.inject.internal; + +import com.google.inject.TypeLiteral; +import com.google.inject.internal.util.SourceProvider; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; +import com.google.inject.matcher.Matchers; +import com.google.inject.spi.TypeConverter; +import com.google.inject.spi.TypeConverterBinding; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * Handles {@code Binder.convertToTypes} commands. + * + */ +final class TypeConverterBindingProcessor extends AbstractProcessor { + + TypeConverterBindingProcessor(Errors errors) { + super(errors); + } + + /** + * Installs default converters for primitives, enums, and class literals. + */ + static void prepareBuiltInConverters(InjectorImpl injector) { + // Configure type converters. + convertToPrimitiveType(injector, int.class, Integer.class); + convertToPrimitiveType(injector, long.class, Long.class); + convertToPrimitiveType(injector, boolean.class, Boolean.class); + convertToPrimitiveType(injector, byte.class, Byte.class); + convertToPrimitiveType(injector, short.class, Short.class); + convertToPrimitiveType(injector, float.class, Float.class); + convertToPrimitiveType(injector, double.class, Double.class); + + convertToClass(injector, Character.class, new TypeConverter() { + public Object convert(String value, TypeLiteral toType) { + value = value.trim(); + if (value.length() != 1) { + throw new RuntimeException("Length != 1."); + } + return value.charAt(0); + } + + @Override + public String toString() { + return "TypeConverter"; + } + }); + + convertToClasses(injector, Matchers.subclassesOf(Enum.class), new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convert(String value, TypeLiteral toType) { + return Enum.valueOf((Class) toType.getRawType(), value); + } + + @Override + public String toString() { + return "TypeConverter>"; + } + }); + + internalConvertToTypes(injector, new AbstractMatcher>() { + public boolean matches(TypeLiteral typeLiteral) { + return typeLiteral.getRawType() == Class.class; + } + + @Override + public String toString() { + return "Class"; + } + }, + new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convert(String value, TypeLiteral toType) { + try { + return Class.forName(value); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e.getMessage()); + } + } + + @Override + public String toString() { + return "TypeConverter>"; + } + } + ); + } + + private static void convertToPrimitiveType(InjectorImpl injector, Class primitiveType, + final Class wrapperType) { + try { + final Method parser = wrapperType.getMethod( + "parse" + capitalize(primitiveType.getName()), String.class); + + TypeConverter typeConverter = new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convert(String value, TypeLiteral toType) { + try { + return parser.invoke(null, value); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getTargetException().getMessage()); + } + } + + @Override + public String toString() { + return "TypeConverter<" + wrapperType.getSimpleName() + ">"; + } + }; + + convertToClass(injector, wrapperType, typeConverter); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + private static void convertToClass(InjectorImpl injector, Class type, + TypeConverter converter) { + convertToClasses(injector, Matchers.identicalTo(type), converter); + } + + private static void convertToClasses(InjectorImpl injector, + final Matcher> typeMatcher, TypeConverter converter) { + internalConvertToTypes(injector, new AbstractMatcher>() { + public boolean matches(TypeLiteral typeLiteral) { + Type type = typeLiteral.getType(); + if (!(type instanceof Class)) { + return false; + } + Class clazz = (Class) type; + return typeMatcher.matches(clazz); + } + + @Override + public String toString() { + return typeMatcher.toString(); + } + }, converter); + } + + private static void internalConvertToTypes(InjectorImpl injector, + Matcher> typeMatcher, + TypeConverter converter) { + injector.state.addConverter( + new TypeConverterBinding(SourceProvider.UNKNOWN_SOURCE, typeMatcher, converter)); + } + + private static String capitalize(String s) { + if (s.length() == 0) { + return s; + } + char first = s.charAt(0); + char capitalized = Character.toUpperCase(first); + return (first == capitalized) + ? s + : capitalized + s.substring(1); + } + + @Override + public Boolean visit(TypeConverterBinding command) { + injector.state.addConverter(new TypeConverterBinding( + command.getSource(), command.getTypeMatcher(), command.getTypeConverter())); + return true; + } + +} diff --git a/src/main/java/com/google/inject/internal/UniqueAnnotations.java b/src/main/java/com/google/inject/internal/UniqueAnnotations.java new file mode 100644 index 0000000..bf3be33 --- /dev/null +++ b/src/main/java/com/google/inject/internal/UniqueAnnotations.java @@ -0,0 +1,58 @@ +package com.google.inject.internal; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +public class UniqueAnnotations { + private static final AtomicInteger nextUniqueValue = new AtomicInteger(1); + + private UniqueAnnotations() { + } + + /** + * Returns an annotation instance that is not equal to any other annotation + * instances, for use in creating distinct {@link com.google.inject.Key}s. + */ + public static Annotation create() { + return create(nextUniqueValue.getAndIncrement()); + } + + static Annotation create(final int value) { + return new Internal() { + public int value() { + return value; + } + + public Class annotationType() { + return Internal.class; + } + + @Override + public String toString() { + return "@" + Internal.class.getName() + "(value=" + value + ")"; + } + + @Override + public boolean equals(Object o) { + return o instanceof Internal + && ((Internal) o).value() == value(); + } + + @Override + public int hashCode() { + return (127 * "value".hashCode()) ^ value; + } + }; + } + + @Retention(RUNTIME) + @BindingAnnotation + @interface Internal { + int value(); + } +} diff --git a/src/main/java/com/google/inject/internal/UntargettedBindingImpl.java b/src/main/java/com/google/inject/internal/UntargettedBindingImpl.java new file mode 100644 index 0000000..907e543 --- /dev/null +++ b/src/main/java/com/google/inject/internal/UntargettedBindingImpl.java @@ -0,0 +1,64 @@ +package com.google.inject.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.spi.BindingTargetVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.UntargettedBinding; + +final class UntargettedBindingImpl extends BindingImpl implements UntargettedBinding { + + UntargettedBindingImpl(InjectorImpl injector, Key key, Object source) { + super(injector, key, source, new InternalFactory() { + public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked) { + throw new AssertionError(); + } + }, Scoping.UNSCOPED); + } + + public UntargettedBindingImpl(Object source, Key key, Scoping scoping) { + super(source, key, scoping); + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public BindingImpl withScoping(Scoping scoping) { + return new UntargettedBindingImpl(getSource(), getKey(), scoping); + } + + public BindingImpl withKey(Key key) { + return new UntargettedBindingImpl(getSource(), key, getScoping()); + } + + public void applyTo(Binder binder) { + getScoping().applyTo(binder.withSource(getSource()).bind(getKey())); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(UntargettedBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof UntargettedBindingImpl) { + UntargettedBindingImpl o = (UntargettedBindingImpl) obj; + return getKey().equals(o.getKey()) + && getScoping().equals(o.getScoping()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey(), getScoping()); + } +} diff --git a/src/main/java/com/google/inject/internal/UntargettedBindingProcessor.java b/src/main/java/com/google/inject/internal/UntargettedBindingProcessor.java new file mode 100644 index 0000000..d7c2f19 --- /dev/null +++ b/src/main/java/com/google/inject/internal/UntargettedBindingProcessor.java @@ -0,0 +1,52 @@ +package com.google.inject.internal; + +import com.google.inject.Binding; +import com.google.inject.spi.UntargettedBinding; + +/** + * Processes just UntargettedBindings. + * + */ +class UntargettedBindingProcessor extends AbstractBindingProcessor { + + UntargettedBindingProcessor(Errors errors, ProcessedBindingData bindingData) { + super(errors, bindingData); + } + + @Override + public Boolean visit(Binding binding) { + return binding.acceptTargetVisitor(new Processor((BindingImpl) binding) { + public Boolean visit(UntargettedBinding untargetted) { + prepareBinding(); + + // Error: Missing implementation. + // Example: bind(Date.class).annotatedWith(Red.class); + // We can't assume abstract types aren't injectable. They may have an + // @ImplementedBy annotation or something. + if (key.getAnnotationType() != null) { + errors.missingImplementation(key); + putBinding(invalidBinding(injector, key, source)); + return true; + } + + // This cast is safe after the preceeding check. + try { + BindingImpl binding = injector.createUninitializedBinding( + key, scoping, source, errors, false); + scheduleInitialization(binding); + putBinding(binding); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + putBinding(invalidBinding(injector, key, source)); + } + + return true; + } + + @Override + protected Boolean visitOther(Binding binding) { + return false; + } + }); + } +} diff --git a/src/main/java/com/google/inject/internal/WeakKeySet.java b/src/main/java/com/google/inject/internal/WeakKeySet.java new file mode 100644 index 0000000..a4a48c4 --- /dev/null +++ b/src/main/java/com/google/inject/internal/WeakKeySet.java @@ -0,0 +1,137 @@ +package com.google.inject.internal; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; +import com.google.common.collect.LinkedHashMultiset; +import com.google.common.collect.Maps; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; +import com.google.inject.Key; +import com.google.inject.internal.util.SourceProvider; + +import java.util.Map; +import java.util.Set; + +/** + * Minimal set that doesn't hold strong references to the contained keys. + */ +final class WeakKeySet { + + /** + * This is already locked externally on add and getSources but we need it to handle clean up in + * the evictionCache's RemovalListener. + */ + private final Object lock; + private Map, Multiset> backingMap; + /** + * Tracks child injector lifetimes and evicts blacklisted keys/sources after the child injector is + * garbage collected. + */ + private final Cache> evictionCache = CacheBuilder.newBuilder() + .weakKeys() + .removalListener( + new RemovalListener>() { + @Override + public void onRemoval(RemovalNotification> notification) { + Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause())); + + cleanUpForCollectedState(notification.getValue()); + } + }) + .build(); + + WeakKeySet(Object lock) { + this.lock = lock; + } + + /** + * There may be multiple child injectors blacklisting a certain key so only remove the source + * that's relevant. + */ + private void cleanUpForCollectedState(Set keysAndSources) { + synchronized (lock) { + for (KeyAndSource keyAndSource : keysAndSources) { + Multiset set = backingMap.get(keyAndSource.key); + if (set != null) { + set.remove(keyAndSource.source); + if (set.isEmpty()) { + backingMap.remove(keyAndSource.key); + } + } + } + } + } + + public void add(Key key, State state, Object source) { + if (backingMap == null) { + backingMap = Maps.newHashMap(); + } + // if it's an instanceof Class, it was a JIT binding, which we don't + // want to retain. + if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) { + source = null; + } + Multiset sources = backingMap.get(key); + if (sources == null) { + sources = LinkedHashMultiset.create(); + backingMap.put(key, sources); + } + Object convertedSource = Errors.convert(source); + sources.add(convertedSource); + + // Avoid all the extra work if we can. + if (state.parent() != State.NONE) { + Set keyAndSources = evictionCache.getIfPresent(state); + if (keyAndSources == null) { + evictionCache.put(state, keyAndSources = Sets.newHashSet()); + } + keyAndSources.add(new KeyAndSource(key, convertedSource)); + } + } + + public boolean contains(Key key) { + evictionCache.cleanUp(); + return backingMap != null && backingMap.containsKey(key); + } + + public Set getSources(Key key) { + evictionCache.cleanUp(); + Multiset sources = (backingMap == null) ? null : backingMap.get(key); + return (sources == null) ? null : sources.elementSet(); + } + + private static final class KeyAndSource { + final Key key; + final Object source; + + KeyAndSource(Key key, Object source) { + this.key = key; + this.source = source; + } + + @Override + public int hashCode() { + return Objects.hashCode(key, source); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof KeyAndSource)) { + return false; + } + + KeyAndSource other = (KeyAndSource) obj; + return Objects.equal(key, other.key) + && Objects.equal(source, other.source); + } + } +} diff --git a/src/main/java/com/google/inject/internal/util/Classes.java b/src/main/java/com/google/inject/internal/util/Classes.java new file mode 100644 index 0000000..d84c6f0 --- /dev/null +++ b/src/main/java/com/google/inject/internal/util/Classes.java @@ -0,0 +1,64 @@ +package com.google.inject.internal.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Class utilities. + */ +public final class Classes { + + public static boolean isInnerClass(Class clazz) { + return !Modifier.isStatic(clazz.getModifiers()) + && clazz.getEnclosingClass() != null; + } + + public static boolean isConcrete(Class clazz) { + int modifiers = clazz.getModifiers(); + return !clazz.isInterface() && !Modifier.isAbstract(modifiers); + } + + /** + * Formats a member as concise string, such as {@code java.util.ArrayList.size}, + * {@code java.util.ArrayList()} or {@code java.util.List.remove()}. + */ + public static String toString(Member member) { + Class memberType = Classes.memberType(member); + + if (memberType == Method.class) { + return member.getDeclaringClass().getName() + "." + member.getName() + "()"; + } else if (memberType == Field.class) { + return member.getDeclaringClass().getName() + "." + member.getName(); + } else if (memberType == Constructor.class) { + return member.getDeclaringClass().getName() + ".()"; + } else { + throw new AssertionError(); + } + } + + /** + * Returns {@code Field.class}, {@code Method.class} or {@code Constructor.class}. + */ + public static Class memberType(Member member) { + checkNotNull(member, "member"); + + if (member instanceof Field) { + return Field.class; + + } else if (member instanceof Method) { + return Method.class; + + } else if (member instanceof Constructor) { + return Constructor.class; + + } else { + throw new IllegalArgumentException( + "Unsupported implementation class for Member, " + member.getClass()); + } + } +} diff --git a/src/main/java/com/google/inject/internal/util/SourceProvider.java b/src/main/java/com/google/inject/internal/util/SourceProvider.java new file mode 100644 index 0000000..a068ca2 --- /dev/null +++ b/src/main/java/com/google/inject/internal/util/SourceProvider.java @@ -0,0 +1,93 @@ +package com.google.inject.internal.util; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +import java.util.List; + +/** + * Provides access to the calling line of code. + */ +public final class SourceProvider { + + /** + * Indicates that the source is unknown. + */ + public static final Object UNKNOWN_SOURCE = "[unknown source]"; + public static final SourceProvider DEFAULT_INSTANCE + = new SourceProvider(ImmutableSet.of(SourceProvider.class.getName())); + private final SourceProvider parent; + private final ImmutableSet classNamesToSkip; + + private SourceProvider(Iterable classesToSkip) { + this(null, classesToSkip); + } + + private SourceProvider(SourceProvider parent, Iterable classesToSkip) { + this.parent = parent; + + ImmutableSet.Builder classNamesToSkipBuilder = ImmutableSet.builder(); + for (String classToSkip : classesToSkip) { + if (parent == null || !parent.shouldBeSkipped(classToSkip)) { + classNamesToSkipBuilder.add(classToSkip); + } + } + this.classNamesToSkip = classNamesToSkipBuilder.build(); + } + + /** + * Returns the class names as Strings + */ + private static List asStrings(Class... classes) { + List strings = Lists.newArrayList(); + for (Class c : classes) { + strings.add(c.getName()); + } + return strings; + } + + /** + * Returns a new instance that also skips {@code moreClassesToSkip}. + */ + public SourceProvider plusSkippedClasses(Class... moreClassesToSkip) { + return new SourceProvider(this, asStrings(moreClassesToSkip)); + } + + /** + * Returns true if the className should be skipped. + */ + private boolean shouldBeSkipped(String className) { + return (parent != null && parent.shouldBeSkipped(className)) + || classNamesToSkip.contains(className); + } + + /** + * Returns the calling line of code. The selected line is the nearest to the top of the stack that + * is not skipped. + */ + public StackTraceElement get(StackTraceElement[] stackTraceElements) { + Preconditions.checkNotNull(stackTraceElements, "The stack trace elements cannot be null."); + for (final StackTraceElement element : stackTraceElements) { + String className = element.getClassName(); + + if (!shouldBeSkipped(className)) { + return element; + } + } + throw new AssertionError(); + } + + /** + * Returns the non-skipped module class name. + */ + public Object getFromClassNames(List moduleClassNames) { + Preconditions.checkNotNull(moduleClassNames, "The list of module class names cannot be null."); + for (final String moduleClassName : moduleClassNames) { + if (!shouldBeSkipped(moduleClassName)) { + return new StackTraceElement(moduleClassName, "configure", null, -1); + } + } + return UNKNOWN_SOURCE; + } +} diff --git a/src/main/java/com/google/inject/internal/util/StackTraceElements.java b/src/main/java/com/google/inject/internal/util/StackTraceElements.java new file mode 100644 index 0000000..10183be --- /dev/null +++ b/src/main/java/com/google/inject/internal/util/StackTraceElements.java @@ -0,0 +1,165 @@ +package com.google.inject.internal.util; + +import com.google.common.collect.MapMaker; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.util.Map; + +/** + * Creates stack trace elements for members. + */ +public class StackTraceElements { + + private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; + private static final InMemoryStackTraceElement[] EMPTY_INMEMORY_STACK_TRACE = + new InMemoryStackTraceElement[0]; + private static final String UNKNOWN_SOURCE = "Unknown Source"; + private static Map cache = new MapMaker().makeMap(); + + public static Object forMember(Member member) { + if (member == null) { + return SourceProvider.UNKNOWN_SOURCE; + } + + Class declaringClass = member.getDeclaringClass(); + + String fileName = null; + int lineNumber = -1; + + Class memberType = Classes.memberType(member); + String memberName = memberType == Constructor.class ? "" : member.getName(); + return new StackTraceElement(declaringClass.getName(), memberName, fileName, lineNumber); + } + + public static Object forType(Class implementation) { + String fileName = null; + int lineNumber = -1; + + return new StackTraceElement(implementation.getName(), "class", fileName, lineNumber); + } + + /** + * Clears the internal cache for {@link StackTraceElement StackTraceElements}. + */ + public static void clearCache() { + cache.clear(); + } + + /** + * Returns encoded in-memory version of {@link StackTraceElement StackTraceElements}. + */ + public static InMemoryStackTraceElement[] convertToInMemoryStackTraceElement( + StackTraceElement[] stackTraceElements) { + if (stackTraceElements.length == 0) { + return EMPTY_INMEMORY_STACK_TRACE; + } + InMemoryStackTraceElement[] inMemoryStackTraceElements = + new InMemoryStackTraceElement[stackTraceElements.length]; + for (int i = 0; i < stackTraceElements.length; i++) { + inMemoryStackTraceElements[i] = + weakIntern(new InMemoryStackTraceElement(stackTraceElements[i])); + } + return inMemoryStackTraceElements; + } + + /** + * Decodes in-memory stack trace elements to regular {@link StackTraceElement StackTraceElements}. + */ + public static StackTraceElement[] convertToStackTraceElement( + InMemoryStackTraceElement[] inMemoryStackTraceElements) { + if (inMemoryStackTraceElements.length == 0) { + return EMPTY_STACK_TRACE; + } + StackTraceElement[] stackTraceElements = + new StackTraceElement[inMemoryStackTraceElements.length]; + for (int i = 0; i < inMemoryStackTraceElements.length; i++) { + String declaringClass = inMemoryStackTraceElements[i].getClassName(); + String methodName = inMemoryStackTraceElements[i].getMethodName(); + int lineNumber = inMemoryStackTraceElements[i].getLineNumber(); + stackTraceElements[i] = + new StackTraceElement(declaringClass, methodName, UNKNOWN_SOURCE, lineNumber); + } + return stackTraceElements; + } + + private static InMemoryStackTraceElement weakIntern( + InMemoryStackTraceElement inMemoryStackTraceElement) { + InMemoryStackTraceElement cached = + (InMemoryStackTraceElement) cache.get(inMemoryStackTraceElement); + if (cached != null) { + return cached; + } + inMemoryStackTraceElement = new InMemoryStackTraceElement( + weakIntern(inMemoryStackTraceElement.getClassName()), + weakIntern(inMemoryStackTraceElement.getMethodName()), + inMemoryStackTraceElement.getLineNumber()); + cache.put(inMemoryStackTraceElement, inMemoryStackTraceElement); + return inMemoryStackTraceElement; + } + + private static String weakIntern(String s) { + String cached = (String) cache.get(s); + if (cached != null) { + return cached; + } + cache.put(s, s); + return s; + } + + /** + * In-Memory version of {@link StackTraceElement} that does not store the file name. + */ + public static class InMemoryStackTraceElement { + private String declaringClass; + private String methodName; + private int lineNumber; + + InMemoryStackTraceElement(StackTraceElement ste) { + this(ste.getClassName(), ste.getMethodName(), ste.getLineNumber()); + } + + InMemoryStackTraceElement(String declaringClass, String methodName, int lineNumber) { + this.declaringClass = declaringClass; + this.methodName = methodName; + this.lineNumber = lineNumber; + } + + String getClassName() { + return declaringClass; + } + + String getMethodName() { + return methodName; + } + + int getLineNumber() { + return lineNumber; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof InMemoryStackTraceElement)) { + return false; + } + InMemoryStackTraceElement e = (InMemoryStackTraceElement) obj; + return e.declaringClass.equals(declaringClass) && e.lineNumber == lineNumber && + methodName.equals(e.methodName); + } + + @Override + public int hashCode() { + int result = 31 * declaringClass.hashCode() + methodName.hashCode(); + result = 31 * result + lineNumber; + return result; + } + + @Override + public String toString() { + return declaringClass + "." + methodName + "(" + lineNumber + ")"; + } + } +} diff --git a/src/main/java/com/google/inject/internal/util/Stopwatch.java b/src/main/java/com/google/inject/internal/util/Stopwatch.java new file mode 100644 index 0000000..e8c692a --- /dev/null +++ b/src/main/java/com/google/inject/internal/util/Stopwatch.java @@ -0,0 +1,31 @@ +package com.google.inject.internal.util; + +import java.util.logging.Logger; + +/** + * Enables simple performance monitoring. + */ +public final class Stopwatch { + private static final Logger logger = Logger.getLogger(Stopwatch.class.getName()); + + private long start = System.currentTimeMillis(); + + /** + * Resets and returns elapsed time in milliseconds. + */ + public long reset() { + long now = System.currentTimeMillis(); + try { + return now - start; + } finally { + start = now; + } + } + + /** + * Resets and logs elapsed time in milliseconds. + */ + public void resetAndLog(String label) { + logger.fine(label + ": " + reset() + "ms"); + } +} diff --git a/src/main/java/com/google/inject/matcher/AbstractMatcher.java b/src/main/java/com/google/inject/matcher/AbstractMatcher.java new file mode 100644 index 0000000..2267d67 --- /dev/null +++ b/src/main/java/com/google/inject/matcher/AbstractMatcher.java @@ -0,0 +1,76 @@ +package com.google.inject.matcher; + +/** + * Implements {@code and()} and {@code or()}. + * + */ +public abstract class AbstractMatcher implements Matcher { + + public Matcher and(final Matcher other) { + return new AndMatcher(this, other); + } + + public Matcher or(Matcher other) { + return new OrMatcher(this, other); + } + + private static class AndMatcher extends AbstractMatcher { + private final Matcher a, b; + + public AndMatcher(Matcher a, Matcher b) { + this.a = a; + this.b = b; + } + + public boolean matches(T t) { + return a.matches(t) && b.matches(t); + } + + @Override + public boolean equals(Object other) { + return other instanceof AndMatcher + && ((AndMatcher) other).a.equals(a) + && ((AndMatcher) other).b.equals(b); + } + + @Override + public int hashCode() { + return 41 * (a.hashCode() ^ b.hashCode()); + } + + @Override + public String toString() { + return "and(" + a + ", " + b + ")"; + } + } + + private static class OrMatcher extends AbstractMatcher { + private final Matcher a, b; + + public OrMatcher(Matcher a, Matcher b) { + this.a = a; + this.b = b; + } + + public boolean matches(T t) { + return a.matches(t) || b.matches(t); + } + + @Override + public boolean equals(Object other) { + return other instanceof OrMatcher + && ((OrMatcher) other).a.equals(a) + && ((OrMatcher) other).b.equals(b); + } + + @Override + public int hashCode() { + return 37 * (a.hashCode() ^ b.hashCode()); + } + + @Override + public String toString() { + return "or(" + a + ", " + b + ")"; + } + } +} diff --git a/src/main/java/com/google/inject/matcher/Matcher.java b/src/main/java/com/google/inject/matcher/Matcher.java new file mode 100644 index 0000000..51d73a6 --- /dev/null +++ b/src/main/java/com/google/inject/matcher/Matcher.java @@ -0,0 +1,24 @@ +package com.google.inject.matcher; + +/** + * Returns {@code true} or {@code false} for a given input. + */ +public interface Matcher { + + /** + * Returns {@code true} if this matches {@code t}, {@code false} otherwise. + */ + boolean matches(T t); + + /** + * Returns a new matcher which returns {@code true} if both this and the + * given matcher return {@code true}. + */ + Matcher and(Matcher other); + + /** + * Returns a new matcher which returns {@code true} if either this or the + * given matcher return {@code true}. + */ + Matcher or(Matcher other); +} diff --git a/src/main/java/com/google/inject/matcher/Matchers.java b/src/main/java/com/google/inject/matcher/Matchers.java new file mode 100644 index 0000000..8be3f8a --- /dev/null +++ b/src/main/java/com/google/inject/matcher/Matchers.java @@ -0,0 +1,385 @@ +package com.google.inject.matcher; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Matcher implementations. Supports matching classes and methods. + * + */ +public class Matchers { + private static final Matcher ANY = new Any(); + + private Matchers() { + } + + /** + * Returns a matcher which matches any input. + */ + public static Matcher any() { + return ANY; + } + + /** + * Inverts the given matcher. + */ + public static Matcher not(final Matcher p) { + return new Not(p); + } + + private static void checkForRuntimeRetention( + Class annotationType) { + Retention retention = annotationType.getAnnotation(Retention.class); + checkArgument(retention != null && retention.value() == RetentionPolicy.RUNTIME, + "Annotation %s is missing RUNTIME retention", annotationType.getSimpleName()); + } + + /** + * Returns a matcher which matches elements (methods, classes, etc.) + * with a given annotation. + */ + public static Matcher annotatedWith( + final Class annotationType) { + return new AnnotatedWithType(annotationType); + } + + /** + * Returns a matcher which matches elements (methods, classes, etc.) + * with a given annotation. + */ + public static Matcher annotatedWith( + final Annotation annotation) { + return new AnnotatedWith(annotation); + } + + /** + * Returns a matcher which matches subclasses of the given type (as well as + * the given type). + */ + public static Matcher subclassesOf(final Class superclass) { + return new SubclassesOf(superclass); + } + + /** + * Returns a matcher which matches objects equal to the given object. + */ + public static Matcher only(Object value) { + return new Only(value); + } + + /** + * Returns a matcher which matches only the given object. + */ + public static Matcher identicalTo(final Object value) { + return new IdenticalTo(value); + } + + /** + * Returns a matcher which matches classes in the given package. Packages are specific to their + * classloader, so classes with the same package name may not have the same package at runtime. + */ + public static Matcher inPackage(final Package targetPackage) { + return new InPackage(targetPackage); + } + + /** + * Returns a matcher which matches classes in the given package and its subpackages. Unlike + * {@link #inPackage(Package) inPackage()}, this matches classes from any classloader. + */ + public static Matcher inSubpackage(final String targetPackageName) { + return new InSubpackage(targetPackageName); + } + + /** + * Returns a matcher which matches methods with matching return types. + */ + public static Matcher returns( + final Matcher> returnType) { + return new Returns(returnType); + } + + private static class Any extends AbstractMatcher { + + public boolean matches(Object o) { + return true; + } + + @Override + public String toString() { + return "any()"; + } + + public Object readResolve() { + return any(); + } + } + + private static class Not extends AbstractMatcher { + final Matcher delegate; + + private Not(Matcher delegate) { + this.delegate = checkNotNull(delegate, "delegate"); + } + + public boolean matches(T t) { + return !delegate.matches(t); + } + + @Override + public boolean equals(Object other) { + return other instanceof Not + && ((Not) other).delegate.equals(delegate); + } + + @Override + public int hashCode() { + return -delegate.hashCode(); + } + + @Override + public String toString() { + return "not(" + delegate + ")"; + } + } + + private static class AnnotatedWithType extends AbstractMatcher { + private final Class annotationType; + + public AnnotatedWithType(Class annotationType) { + this.annotationType = checkNotNull(annotationType, "annotation type"); + checkForRuntimeRetention(annotationType); + } + + public boolean matches(AnnotatedElement element) { + return element.isAnnotationPresent(annotationType); + } + + @Override + public boolean equals(Object other) { + return other instanceof AnnotatedWithType + && ((AnnotatedWithType) other).annotationType.equals(annotationType); + } + + @Override + public int hashCode() { + return 37 * annotationType.hashCode(); + } + + @Override + public String toString() { + return "annotatedWith(" + annotationType.getSimpleName() + ".class)"; + } + } + + private static class AnnotatedWith extends AbstractMatcher { + private final Annotation annotation; + + public AnnotatedWith(Annotation annotation) { + this.annotation = checkNotNull(annotation, "annotation"); + checkForRuntimeRetention(annotation.annotationType()); + } + + public boolean matches(AnnotatedElement element) { + Annotation fromElement = element.getAnnotation(annotation.annotationType()); + return fromElement != null && annotation.equals(fromElement); + } + + @Override + public boolean equals(Object other) { + return other instanceof AnnotatedWith + && ((AnnotatedWith) other).annotation.equals(annotation); + } + + @Override + public int hashCode() { + return 37 * annotation.hashCode(); + } + + @Override + public String toString() { + return "annotatedWith(" + annotation + ")"; + } + } + + private static class SubclassesOf extends AbstractMatcher { + private final Class superclass; + + public SubclassesOf(Class superclass) { + this.superclass = checkNotNull(superclass, "superclass"); + } + + public boolean matches(Class subclass) { + return superclass.isAssignableFrom(subclass); + } + + @Override + public boolean equals(Object other) { + return other instanceof SubclassesOf + && ((SubclassesOf) other).superclass.equals(superclass); + } + + @Override + public int hashCode() { + return 37 * superclass.hashCode(); + } + + @Override + public String toString() { + return "subclassesOf(" + superclass.getSimpleName() + ".class)"; + } + } + + private static class Only extends AbstractMatcher { + private final Object value; + + public Only(Object value) { + this.value = checkNotNull(value, "value"); + } + + public boolean matches(Object other) { + return value.equals(other); + } + + @Override + public boolean equals(Object other) { + return other instanceof Only + && ((Only) other).value.equals(value); + } + + @Override + public int hashCode() { + return 37 * value.hashCode(); + } + + @Override + public String toString() { + return "only(" + value + ")"; + } + } + + private static class IdenticalTo extends AbstractMatcher { + private final Object value; + + public IdenticalTo(Object value) { + this.value = checkNotNull(value, "value"); + } + + public boolean matches(Object other) { + return value == other; + } + + @Override + public boolean equals(Object other) { + return other instanceof IdenticalTo + && ((IdenticalTo) other).value == value; + } + + @Override + public int hashCode() { + return 37 * System.identityHashCode(value); + } + + @Override + public String toString() { + return "identicalTo(" + value + ")"; + } + } + + private static class InPackage extends AbstractMatcher { + private final transient Package targetPackage; + private final String packageName; + + public InPackage(Package targetPackage) { + this.targetPackage = checkNotNull(targetPackage, "package"); + this.packageName = targetPackage.getName(); + } + + public boolean matches(Class c) { + return c.getPackage().equals(targetPackage); + } + + @Override + public boolean equals(Object other) { + return other instanceof InPackage + && ((InPackage) other).targetPackage.equals(targetPackage); + } + + @Override + public int hashCode() { + return 37 * targetPackage.hashCode(); + } + + @Override + public String toString() { + return "inPackage(" + targetPackage.getName() + ")"; + } + + public Object readResolve() { + return inPackage(Package.getPackage(packageName)); + } + } + + private static class InSubpackage extends AbstractMatcher { + private final String targetPackageName; + + public InSubpackage(String targetPackageName) { + this.targetPackageName = targetPackageName; + } + + public boolean matches(Class c) { + String classPackageName = c.getPackage().getName(); + return classPackageName.equals(targetPackageName) + || classPackageName.startsWith(targetPackageName + "."); + } + + @Override + public boolean equals(Object other) { + return other instanceof InSubpackage + && ((InSubpackage) other).targetPackageName.equals(targetPackageName); + } + + @Override + public int hashCode() { + return 37 * targetPackageName.hashCode(); + } + + @Override + public String toString() { + return "inSubpackage(" + targetPackageName + ")"; + } + } + + private static class Returns extends AbstractMatcher { + private final Matcher> returnType; + + public Returns(Matcher> returnType) { + this.returnType = checkNotNull(returnType, "return type matcher"); + } + + public boolean matches(Method m) { + return returnType.matches(m.getReturnType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof Returns + && ((Returns) other).returnType.equals(returnType); + } + + @Override + public int hashCode() { + return 37 * returnType.hashCode(); + } + + @Override + public String toString() { + return "returns(" + returnType + ")"; + } + } +} diff --git a/src/main/java/com/google/inject/name/Named.java b/src/main/java/com/google/inject/name/Named.java new file mode 100644 index 0000000..70a6a19 --- /dev/null +++ b/src/main/java/com/google/inject/name/Named.java @@ -0,0 +1,19 @@ +package com.google.inject.name; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotates named things. + */ +@Retention(RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) +@BindingAnnotation +public @interface Named { + String value(); +} diff --git a/src/main/java/com/google/inject/name/NamedImpl.java b/src/main/java/com/google/inject/name/NamedImpl.java new file mode 100644 index 0000000..f90f018 --- /dev/null +++ b/src/main/java/com/google/inject/name/NamedImpl.java @@ -0,0 +1,40 @@ +package com.google.inject.name; + +import java.lang.annotation.Annotation; + +import static com.google.common.base.Preconditions.checkNotNull; + +class NamedImpl implements Named { + + private final String value; + + public NamedImpl(String value) { + this.value = checkNotNull(value, "name"); + } + + public String value() { + return this.value; + } + + public int hashCode() { + // This is specified in java.lang.Annotation. + return (127 * "value".hashCode()) ^ value.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof Named)) { + return false; + } + + Named other = (Named) o; + return value.equals(other.value()); + } + + public String toString() { + return "@" + Named.class.getName() + "(value=" + value + ")"; + } + + public Class annotationType() { + return Named.class; + } +} diff --git a/src/main/java/com/google/inject/name/Names.java b/src/main/java/com/google/inject/name/Names.java new file mode 100644 index 0000000..045a717 --- /dev/null +++ b/src/main/java/com/google/inject/name/Names.java @@ -0,0 +1,53 @@ +package com.google.inject.name; + +import com.google.inject.Binder; +import com.google.inject.Key; + +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; + +/** + * Utility methods for use with {@code @}{@link Named}. + */ +public class Names { + + private Names() { + } + + /** + * Creates a {@link Named} annotation with {@code name} as the value. + */ + public static Named named(String name) { + return new NamedImpl(name); + } + + /** + * Creates a constant binding to {@code @Named(key)} for each entry in + * {@code properties}. + */ + public static void bindProperties(Binder binder, Map properties) { + binder = binder.skipSources(Names.class); + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + binder.bind(Key.get(String.class, new NamedImpl(key))).toInstance(value); + } + } + + /** + * Creates a constant binding to {@code @Named(key)} for each property. This + * method binds all properties including those inherited from + * {@link Properties#defaults defaults}. + */ + public static void bindProperties(Binder binder, Properties properties) { + binder = binder.skipSources(Names.class); + + // use enumeration to include the default properties + for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) { + String propertyName = (String) e.nextElement(); + String value = properties.getProperty(propertyName); + binder.bind(Key.get(String.class, new NamedImpl(propertyName))).toInstance(value); + } + } +} diff --git a/src/main/java/com/google/inject/spi/BindingScopingVisitor.java b/src/main/java/com/google/inject/spi/BindingScopingVisitor.java new file mode 100644 index 0000000..d976d53 --- /dev/null +++ b/src/main/java/com/google/inject/spi/BindingScopingVisitor.java @@ -0,0 +1,40 @@ +package com.google.inject.spi; + +import com.google.inject.Scope; + +import java.lang.annotation.Annotation; + +/** + * Visits each of the strategies used to scope an injection. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + */ +public interface BindingScopingVisitor { + + /** + * Visit an eager singleton or single instance. This scope strategy is found on both module and + * injector bindings. + */ + V visitEagerSingleton(); + + /** + * Visit a scope instance. This scope strategy is found on both module and injector bindings. + */ + V visitScope(Scope scope); + + /** + * Visit a scope annotation. This scope strategy is found only on module bindings. The instance + * that implements this scope is registered by {@link com.google.inject.Binder#bindScope(Class, + * Scope) Binder.bindScope()}. + */ + V visitScopeAnnotation(Class scopeAnnotation); + + /** + * Visit an unspecified or unscoped strategy. On a module, this strategy indicates that the + * injector should use scoping annotations to find a scope. On an injector, it indicates that + * no scope is applied to the binding. An unscoped binding will behave like a scoped one when it + * is linked to a scoped binding. + */ + V visitNoScoping(); +} diff --git a/src/main/java/com/google/inject/spi/BindingTargetVisitor.java b/src/main/java/com/google/inject/spi/BindingTargetVisitor.java new file mode 100644 index 0000000..a68b2cb --- /dev/null +++ b/src/main/java/com/google/inject/spi/BindingTargetVisitor.java @@ -0,0 +1,66 @@ +package com.google.inject.spi; + +/** + * Visits each of the strategies used to find an instance to satisfy an injection. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + */ +public interface BindingTargetVisitor { + + /** + * Visit a instance binding. The same instance is returned for every injection. This target is + * found in both module and injector bindings. + */ + V visit(InstanceBinding binding); + + /** + * Visit a provider instance binding. The provider's {@code get} method is invoked to resolve + * injections. This target is found in both module and injector bindings. + */ + V visit(ProviderInstanceBinding binding); + + /** + * Visit a provider key binding. To resolve injections, the provider key is first resolved, then + * that provider's {@code get} method is invoked. This target is found in both module and injector + * bindings. + */ + V visit(ProviderKeyBinding binding); + + /** + * Visit a linked key binding. The other key's binding is used to resolve injections. This + * target is found in both module and injector bindings. + */ + V visit(LinkedKeyBinding binding); + + /** + * Visit a binding to a key exposed from an enclosed private environment. This target is only + * found in injector bindings. + */ + V visit(ExposedBinding binding); + + /** + * Visit an untargetted binding. This target is found only on module bindings. It indicates + * that the injector should use its implicit binding strategies to resolve injections. + */ + V visit(UntargettedBinding binding); + + /** + * Visit a constructor binding. To resolve injections, an instance is instantiated by invoking + * {@code constructor}. This target is found only on injector bindings. + */ + V visit(ConstructorBinding binding); + + /** + * Visit a binding created from converting a bound instance to a new type. The source binding + * has the same binding annotation but a different type. This target is found only on injector + * bindings. + */ + V visit(ConvertedConstantBinding binding); + + /** + * Visit a binding to a {@link com.google.inject.Provider} that delegates to the binding for the + * provided type. This target is found only on injector bindings. + */ + V visit(ProviderBinding binding); +} diff --git a/src/main/java/com/google/inject/spi/ConstructorBinding.java b/src/main/java/com/google/inject/spi/ConstructorBinding.java new file mode 100644 index 0000000..6233923 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ConstructorBinding.java @@ -0,0 +1,28 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; + +import java.util.Set; + +/** + * A binding to the constructor of a concrete clss. To resolve injections, an instance is + * instantiated by invoking the constructor. + * + */ +public interface ConstructorBinding extends Binding, HasDependencies { + + /** + * Gets the constructor this binding injects. + */ + InjectionPoint getConstructor(); + + /** + * Returns all instance method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + */ + Set getInjectableMembers(); + +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/ConvertedConstantBinding.java b/src/main/java/com/google/inject/spi/ConvertedConstantBinding.java new file mode 100644 index 0000000..9a27ec1 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ConvertedConstantBinding.java @@ -0,0 +1,34 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; +import com.google.inject.Key; + +import java.util.Set; + +/** + * A binding created from converting a bound instance to a new type. The source binding has the same + * binding annotation but a different type. + */ +public interface ConvertedConstantBinding extends Binding, HasDependencies { + + /** + * Returns the converted value. + */ + T getValue(); + + /** + * Returns the type converter binding used to convert the constant. + */ + TypeConverterBinding getTypeConverterBinding(); + + /** + * Returns the key for the source binding. That binding can be retrieved from an injector using + * {@link com.google.inject.Injector#getBinding(Key) Injector.getBinding(key)}. + */ + Key getSourceKey(); + + /** + * Returns a singleton set containing only the converted key. + */ + Set> getDependencies(); +} diff --git a/src/main/java/com/google/inject/spi/DefaultBindingScopingVisitor.java b/src/main/java/com/google/inject/spi/DefaultBindingScopingVisitor.java new file mode 100644 index 0000000..d5eb38e --- /dev/null +++ b/src/main/java/com/google/inject/spi/DefaultBindingScopingVisitor.java @@ -0,0 +1,38 @@ +package com.google.inject.spi; + +import com.google.inject.Scope; + +import java.lang.annotation.Annotation; + +/** + * No-op visitor for subclassing. All interface methods simply delegate to + * {@link #visitOther()}, returning its result. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + */ +public class DefaultBindingScopingVisitor implements BindingScopingVisitor { + + /** + * Default visit implementation. Returns {@code null}. + */ + protected V visitOther() { + return null; + } + + public V visitEagerSingleton() { + return visitOther(); + } + + public V visitScope(Scope scope) { + return visitOther(); + } + + public V visitScopeAnnotation(Class scopeAnnotation) { + return visitOther(); + } + + public V visitNoScoping() { + return visitOther(); + } +} diff --git a/src/main/java/com/google/inject/spi/DefaultBindingTargetVisitor.java b/src/main/java/com/google/inject/spi/DefaultBindingTargetVisitor.java new file mode 100644 index 0000000..6e351a9 --- /dev/null +++ b/src/main/java/com/google/inject/spi/DefaultBindingTargetVisitor.java @@ -0,0 +1,57 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; + +/** + * No-op visitor for subclassing. All interface methods simply delegate to {@link + * #visitOther(Binding)}, returning its result. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + */ +public abstract class DefaultBindingTargetVisitor implements BindingTargetVisitor { + + /** + * Default visit implementation. Returns {@code null}. + */ + protected V visitOther(Binding binding) { + return null; + } + + public V visit(InstanceBinding instanceBinding) { + return visitOther(instanceBinding); + } + + public V visit(ProviderInstanceBinding providerInstanceBinding) { + return visitOther(providerInstanceBinding); + } + + public V visit(ProviderKeyBinding providerKeyBinding) { + return visitOther(providerKeyBinding); + } + + public V visit(LinkedKeyBinding linkedKeyBinding) { + return visitOther(linkedKeyBinding); + } + + public V visit(ExposedBinding exposedBinding) { + return visitOther(exposedBinding); + } + + public V visit(UntargettedBinding untargettedBinding) { + return visitOther(untargettedBinding); + } + + public V visit(ConstructorBinding constructorBinding) { + return visitOther(constructorBinding); + } + + public V visit(ConvertedConstantBinding convertedConstantBinding) { + return visitOther(convertedConstantBinding); + } + + @SuppressWarnings("unchecked") + public V visit(ProviderBinding providerBinding) { + return visitOther((Binding) (Binding) providerBinding); + } +} diff --git a/src/main/java/com/google/inject/spi/DefaultElementVisitor.java b/src/main/java/com/google/inject/spi/DefaultElementVisitor.java new file mode 100644 index 0000000..6d0effb --- /dev/null +++ b/src/main/java/com/google/inject/spi/DefaultElementVisitor.java @@ -0,0 +1,84 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; + +/** + * No-op visitor for subclassing. All interface methods simply delegate to + * {@link #visitOther(Element)}, returning its result. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + */ +public abstract class DefaultElementVisitor implements ElementVisitor { + + /** + * Default visit implementation. Returns {@code null}. + */ + protected V visitOther(Element element) { + return null; + } + + public V visit(Message message) { + return visitOther(message); + } + + public V visit(Binding binding) { + return visitOther(binding); + } + + public V visit(ScopeBinding scopeBinding) { + return visitOther(scopeBinding); + } + + public V visit(TypeConverterBinding typeConverterBinding) { + return visitOther(typeConverterBinding); + } + + public V visit(ProviderLookup providerLookup) { + return visitOther(providerLookup); + } + + public V visit(InjectionRequest injectionRequest) { + return visitOther(injectionRequest); + } + + public V visit(StaticInjectionRequest staticInjectionRequest) { + return visitOther(staticInjectionRequest); + } + + public V visit(PrivateElements privateElements) { + return visitOther(privateElements); + } + + public V visit(MembersInjectorLookup lookup) { + return visitOther(lookup); + } + + public V visit(TypeListenerBinding binding) { + return visitOther(binding); + } + + public V visit(ProvisionListenerBinding binding) { + return visitOther(binding); + } + + public V visit(DisableCircularProxiesOption option) { + return visitOther(option); + } + + public V visit(RequireExplicitBindingsOption option) { + return visitOther(option); + } + + public V visit(RequireAtInjectOnConstructorsOption option) { + return visitOther(option); + } + + public V visit(RequireExactBindingAnnotationsOption option) { + return visitOther(option); + } + + public V visit(ModuleAnnotatedMethodScannerBinding binding) { + return visitOther(binding); + } +} diff --git a/src/main/java/com/google/inject/spi/Dependency.java b/src/main/java/com/google/inject/spi/Dependency.java new file mode 100644 index 0000000..d76e025 --- /dev/null +++ b/src/main/java/com/google/inject/spi/Dependency.java @@ -0,0 +1,112 @@ +package com.google.inject.spi; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.Key; +import com.google.inject.internal.MoreTypes; + +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A variable that can be resolved by an injector. + * + *

Use {@link #get} to build a freestanding dependency, or {@link InjectionPoint} to build one + * that's attached to a constructor, method or field. + */ +public final class Dependency { + private final InjectionPoint injectionPoint; + private final Key key; + private final boolean nullable; + private final int parameterIndex; + + Dependency(InjectionPoint injectionPoint, Key key, boolean nullable, int parameterIndex) { + this.injectionPoint = injectionPoint; + this.key = checkNotNull(key, "key"); + this.nullable = nullable; + this.parameterIndex = parameterIndex; + } + + /** + * Returns a new dependency that is not attached to an injection point. The returned dependency is + * nullable. + */ + public static Dependency get(Key key) { + return new Dependency(null, MoreTypes.canonicalizeKey(key), true, -1); + } + + /** + * Returns the dependencies from the given injection points. + */ + public static Set> forInjectionPoints(Set injectionPoints) { + List> dependencies = Lists.newArrayList(); + for (InjectionPoint injectionPoint : injectionPoints) { + dependencies.addAll(injectionPoint.getDependencies()); + } + return ImmutableSet.copyOf(dependencies); + } + + /** + * Returns the key to the binding that satisfies this dependency. + */ + public Key getKey() { + return this.key; + } + + /** + * Returns true if null is a legal value for this dependency. + */ + public boolean isNullable() { + return nullable; + } + + /** + * Returns the injection point to which this dependency belongs, or null if this dependency isn't + * attached to a particular injection point. + */ + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + /** + * Returns the index of this dependency in the injection point's parameter list, or {@code -1} if + * this dependency does not belong to a parameter list. Only method and constuctor dependencies + * are elements in a parameter list. + */ + public int getParameterIndex() { + return parameterIndex; + } + + @Override + public int hashCode() { + return Objects.hashCode(injectionPoint, parameterIndex, key); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Dependency) { + Dependency dependency = (Dependency) o; + return Objects.equal(injectionPoint, dependency.injectionPoint) + && Objects.equal(parameterIndex, dependency.parameterIndex) + && Objects.equal(key, dependency.key); + } else { + return false; + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(key); + if (injectionPoint != null) { + builder.append("@").append(injectionPoint); + if (parameterIndex != -1) { + builder.append("[").append(parameterIndex).append("]"); + } + } + return builder.toString(); + } +} diff --git a/src/main/java/com/google/inject/spi/DependencyAndSource.java b/src/main/java/com/google/inject/spi/DependencyAndSource.java new file mode 100644 index 0000000..eb17f91 --- /dev/null +++ b/src/main/java/com/google/inject/spi/DependencyAndSource.java @@ -0,0 +1,60 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.internal.util.StackTraceElements; + +import java.lang.reflect.Member; + +/** + * A combination of a {@link Dependency} and the {@link Binding#getSource() + * source} where the dependency was bound. + */ +public final class DependencyAndSource { + private final Dependency dependency; + private final Object source; + + public DependencyAndSource(Dependency dependency, Object source) { + this.dependency = dependency; + this.source = source; + } + + /** + * Returns the Dependency, if one exists. For anything that can be referenced + * by {@link Injector#getBinding}, a dependency exists. A dependency will not + * exist (and this will return null) for types initialized with + * {@link Binder#requestInjection} or {@link Injector#injectMembers(Object)}, + * nor will it exist for objects injected into Providers bound with + * LinkedBindingBuilder#toProvider(Provider). + */ + public Dependency getDependency() { + return dependency; + } + + /** + * Returns a string describing where this dependency was bound. If the binding + * was just-in-time, there is no valid binding source, so this describes the + * class in question. + */ + public String getBindingSource() { + if (source instanceof Class) { + return StackTraceElements.forType((Class) source).toString(); + } else if (source instanceof Member) { + return StackTraceElements.forMember((Member) source).toString(); + } else { + return source.toString(); + } + } + + @Override + public String toString() { + Dependency dep = getDependency(); + Object source = getBindingSource(); + if (dep != null) { + return "Dependency: " + dep + ", source: " + source; + } else { + return "Source: " + source; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/DisableCircularProxiesOption.java b/src/main/java/com/google/inject/spi/DisableCircularProxiesOption.java new file mode 100644 index 0000000..d407354 --- /dev/null +++ b/src/main/java/com/google/inject/spi/DisableCircularProxiesOption.java @@ -0,0 +1,28 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A request to disable circular proxies. + */ +public final class DisableCircularProxiesOption implements Element { + private final Object source; + + DisableCircularProxiesOption(Object source) { + this.source = checkNotNull(source, "source"); + } + + public Object getSource() { + return source; + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).disableCircularProxies(); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/com/google/inject/spi/Element.java b/src/main/java/com/google/inject/spi/Element.java new file mode 100644 index 0000000..db4b706 --- /dev/null +++ b/src/main/java/com/google/inject/spi/Element.java @@ -0,0 +1,45 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; + +/** + * A core component of a module or injector. + * + *

The elements of a module can be inspected, validated and rewritten. Use {@link + * Elements#getElements(com.google.inject.Module[]) Elements.getElements()} to read the elements + * from a module, and {@link Elements#getModule(Iterable) Elements.getModule()} to rewrite them. + * This can be used for static analysis and generation of Guice modules. + * + *

The elements of an injector can be inspected and exercised. Use {@link + * com.google.inject.Injector#getBindings Injector.getBindings()} to reflect on Guice injectors. + * + */ +public interface Element { + + /** + * Returns an arbitrary object containing information about the "place" where this element was + * configured. Used by Guice in the production of descriptive error messages. + * + *

Tools might specially handle types they know about; {@code StackTraceElement} is a good + * example. Tools should simply call {@code toString()} on the source object if the type is + * unfamiliar. + */ + Object getSource(); + + /** + * Accepts an element visitor. Invokes the visitor method specific to this element's type. + * + * @param visitor to call back on + */ + T acceptVisitor(ElementVisitor visitor); + + /** + * Writes this module element to the given binder (optional operation). + * + * @param binder to apply configuration element to + * @throws UnsupportedOperationException if the {@code applyTo} method is not supported by this + * element. + */ + void applyTo(Binder binder); + +} diff --git a/src/main/java/com/google/inject/spi/ElementSource.java b/src/main/java/com/google/inject/spi/ElementSource.java new file mode 100644 index 0000000..91e1640 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ElementSource.java @@ -0,0 +1,168 @@ +package com.google.inject.spi; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.inject.internal.util.StackTraceElements; +import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement; + +import java.util.List; + +/** + * Contains information about where and how an {@link Element element} was + * bound. + *

+ * The {@link #getDeclaringSource() declaring source} refers to a location in + * source code that defines the Guice {@link Element element}. For example, if + * the element is created from a method annotated by {@literal @Provides}, the + * declaring source of element would be the method itself. + *

+ * The {@link #getStackTrace()} refers to the sequence of calls ends at one of + * {@link com.google.inject.Binder} {@code bindXXX()} methods and eventually + * defines the element. Note that {@link #getStackTrace()} lists + * {@link StackTraceElement StackTraceElements} in reverse chronological order. + * The first element (index zero) is the last method call and the last element + * is the first method invocation. By default, the stack trace is not collected. + * The default behavior can be changed by setting the + * {@code guice_include_stack_traces} flag value. The value can be either + * {@code OFF}, {@code ONLY_FOR_DECLARING_SOURCE} or {@code COMPLETE}. Note that + * collecting stack traces for every binding can cause a performance hit when + * the injector is created. + *

+ * The sequence of class names of {@link com.google.inject.Module modules} + * involved in the element creation can be retrieved by + * {@link #getModuleClassNames()}. Similar to {@link #getStackTrace()}, the + * order is reverse chronological. The first module (index 0) is the module that + * installs the {@link Element element}. The last module is the root module. + *

+ * In order to support the cases where a Guice {@link Element element} is + * created from another Guice {@link Element element} (original) (e.g., by + * {@link Element#applyTo}), it also provides a reference to the original + * element source ({@link #getOriginalElementSource()}). + * + */ +public final class ElementSource { + + /** + * The {@link ElementSource source} of element that this element created from (if there is any), + * otherwise {@code null}. + */ + final ElementSource originalElementSource; + + /** + * The {@link ModuleSource source} of module creates the element. + */ + final ModuleSource moduleSource; + + final InMemoryStackTraceElement[] partialCallStack; + + final Object declaringSource; + + /** + * Creates a new {@link ElementSource} from the given parameters. + * + * @param originalSource The source of element that this element created from (if there is + * any), otherwise {@code null}. + * @param declaringSource the source (in)directly declared the element. + * @param moduleSource the moduleSource when the element is bound + * @param partialCallStack the partial call stack from the top module to where the element is + * bound + */ + ElementSource(ElementSource originalSource, Object declaringSource, + ModuleSource moduleSource, StackTraceElement[] partialCallStack) { + Preconditions.checkNotNull(declaringSource, "declaringSource cannot be null."); + Preconditions.checkNotNull(moduleSource, "moduleSource cannot be null."); + Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); + this.originalElementSource = originalSource; + this.declaringSource = declaringSource; + this.moduleSource = moduleSource; + this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack); + } + + /** + * Returns the {@link ElementSource} of the element this was created or copied from. If this was + * not created or copied from another element, returns {@code null}. + */ + public ElementSource getOriginalElementSource() { + return originalElementSource; + } + + /** + * Returns a single location in source code that defines the element. It can be any object + * such as {@link java.lang.reflect.Constructor}, {@link java.lang.reflect.Method}, + * {@link java.lang.reflect.Field}, {@link StackTraceElement}, etc. For + * example, if the element is created from a method annotated by {@literal @Provides}, the + * declaring source of element would be the method itself. + */ + public Object getDeclaringSource() { + return declaringSource; + } + + /** + * Returns the class names of modules involved in creating this {@link Element}. The first + * element (index 0) is the class name of module that defined the element, and the last element + * is the class name of root module. + */ + public List getModuleClassNames() { + return moduleSource.getModuleClassNames(); + } + + /** + * Returns the position of {@link com.google.inject.Module#configure configure(Binder)} method + * call in the {@link #getStackTrace stack trace} for modules that their classes returned by + * {@link #getModuleClassNames}. For example, if the stack trace looks like the following: + *

+ * {@code + * 0 - Binder.bind(), + * 1 - ModuleTwo.configure(), + * 2 - Binder.install(), + * 3 - ModuleOne.configure(), + * 4 - theRest(). + * } + *

+ * 1 and 3 are returned. + *

+ * In the cases where stack trace is not available (i.e., the stack trace was not collected), + * it returns -1 for all module positions. + */ + public List getModuleConfigurePositionsInStackTrace() { + int size = moduleSource.size(); + Integer[] positions = new Integer[size]; + int chunkSize = partialCallStack.length; + positions[0] = chunkSize - 1; + ModuleSource current = moduleSource; + for (int cursor = 1; cursor < size; cursor++) { + chunkSize = current.getPartialCallStackSize(); + positions[cursor] = positions[cursor - 1] + chunkSize; + current = current.getParent(); + } + return ImmutableList.copyOf(positions); + } + + /** + * Returns the sequence of method calls that ends at one of {@link com.google.inject.Binder} + * {@code bindXXX()} methods and eventually defines the element. Note that + * this method lists {@link StackTraceElement StackTraceElements} in reverse + * chronological order. The first element (index zero) is the last method call and the last + * element is the first method invocation. In the cases where stack trace is not available + * (i.e.,the stack trace was not collected), it returns an empty array. + */ + public StackTraceElement[] getStackTrace() { + int modulesCallStackSize = moduleSource.getStackTraceSize(); + int chunkSize = partialCallStack.length; + int size = moduleSource.getStackTraceSize() + chunkSize; + StackTraceElement[] callStack = new StackTraceElement[size]; + System.arraycopy( + StackTraceElements.convertToStackTraceElement(partialCallStack), 0, callStack, 0, + chunkSize); + System.arraycopy(moduleSource.getStackTrace(), 0, callStack, chunkSize, modulesCallStackSize); + return callStack; + } + + /** + * Returns {@code getDeclaringSource().toString()} value. + */ + @Override + public String toString() { + return getDeclaringSource().toString(); + } +} diff --git a/src/main/java/com/google/inject/spi/ElementVisitor.java b/src/main/java/com/google/inject/spi/ElementVisitor.java new file mode 100644 index 0000000..f12a8c9 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ElementVisitor.java @@ -0,0 +1,96 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Inject; + +/** + * Visit elements. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + */ +public interface ElementVisitor { + + /** + * Visit a mapping from a key (type and optional annotation) to the strategy for getting + * instances of the type. + */ + V visit(Binding binding); + + /** + * Visit a registration of a scope annotation with the scope that implements it. + */ + V visit(ScopeBinding binding); + + /** + * Visit a registration of type converters for matching target types. + */ + V visit(TypeConverterBinding binding); + + /** + * Visit a request to inject the instance fields and methods of an instance. + */ + V visit(InjectionRequest request); + + /** + * Visit a request to inject the static fields and methods of type. + */ + V visit(StaticInjectionRequest request); + + /** + * Visit a lookup of the provider for a type. + */ + V visit(ProviderLookup lookup); + + /** + * Visit a lookup of the members injector. + */ + V visit(MembersInjectorLookup lookup); + + /** + * Visit an error message and the context in which it occured. + */ + V visit(Message message); + + /** + * Visit a collection of configuration elements for a {@linkplain com.google.inject.PrivateBinder + * private binder}. + */ + V visit(PrivateElements elements); + + /** + * Visit an injectable type listener binding. + */ + V visit(TypeListenerBinding binding); + + /** + * Visit a provision listener binding. + */ + V visit(ProvisionListenerBinding binding); + + /** + * Visit a require explicit bindings command. + */ + V visit(RequireExplicitBindingsOption option); + + /** + * Visit a disable circular proxies command. + */ + V visit(DisableCircularProxiesOption option); + + /** + * Visit a require explicit {@literal @}{@link Inject} command. + */ + V visit(RequireAtInjectOnConstructorsOption option); + + /** + * Visit a require exact binding annotations command. + */ + V visit(RequireExactBindingAnnotationsOption option); + + /** + * Visits a {@link Binder#scanModulesForAnnotatedMethods} command. + */ + V visit(ModuleAnnotatedMethodScannerBinding binding); +} diff --git a/src/main/java/com/google/inject/spi/Elements.java b/src/main/java/com/google/inject/spi/Elements.java new file mode 100644 index 0000000..c1c26af --- /dev/null +++ b/src/main/java/com/google/inject/spi/Elements.java @@ -0,0 +1,547 @@ +package com.google.inject.spi; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Module; +import com.google.inject.PrivateBinder; +import com.google.inject.PrivateModule; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.AnnotatedConstantBindingBuilder; +import com.google.inject.binder.AnnotatedElementBuilder; +import com.google.inject.internal.AbstractBindingBuilder; +import com.google.inject.internal.BindingBuilder; +import com.google.inject.internal.ConstantBindingBuilderImpl; +import com.google.inject.internal.Errors; +import com.google.inject.internal.ExposureBuilder; +import com.google.inject.internal.InternalFlags.IncludeStackTraceOption; +import com.google.inject.internal.MoreTypes; +import com.google.inject.internal.PrivateElementsImpl; +import com.google.inject.internal.ProviderMethodsModule; +import com.google.inject.internal.util.SourceProvider; +import com.google.inject.internal.util.StackTraceElements; +import com.google.inject.matcher.Matcher; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption; + +/** + * Exposes elements of a module so they can be inspected, validated or {@link + * Element#applyTo(Binder) rewritten}. + * + */ +public final class Elements { + + private static final BindingTargetVisitor GET_INSTANCE_VISITOR + = new DefaultBindingTargetVisitor() { + @Override + public Object visit(InstanceBinding binding) { + return binding.getInstance(); + } + + @Override + protected Object visitOther(Binding binding) { + throw new IllegalArgumentException(); + } + }; + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Module... modules) { + return getElements(Stage.DEVELOPMENT, Arrays.asList(modules)); + } + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Stage stage, Module... modules) { + return getElements(stage, Arrays.asList(modules)); + } + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Iterable modules) { + return getElements(Stage.DEVELOPMENT, modules); + } + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Stage stage, Iterable modules) { + RecordingBinder binder = new RecordingBinder(stage); + for (Module module : modules) { + binder.install(module); + } + binder.scanForAnnotatedMethods(); + for (RecordingBinder child : binder.privateBinders) { + child.scanForAnnotatedMethods(); + } + // Free the memory consumed by the stack trace elements cache + StackTraceElements.clearCache(); + return Collections.unmodifiableList(binder.elements); + } + + /** + * Returns the module composed of {@code elements}. + */ + public static Module getModule(final Iterable elements) { + return new ElementsAsModule(elements); + } + + @SuppressWarnings("unchecked") + static BindingTargetVisitor getInstanceVisitor() { + return (BindingTargetVisitor) GET_INSTANCE_VISITOR; + } + + private static class ElementsAsModule implements Module { + private final Iterable elements; + + ElementsAsModule(Iterable elements) { + this.elements = elements; + } + + @Override + public void configure(Binder binder) { + for (Element element : elements) { + element.applyTo(binder); + } + } + } + + private static class ModuleInfo { + private final Binder binder; + private final ModuleSource moduleSource; + private final boolean skipScanning; + + private ModuleInfo(Binder binder, ModuleSource moduleSource, boolean skipScanning) { + this.binder = binder; + this.moduleSource = moduleSource; + this.skipScanning = skipScanning; + } + } + + private static class RecordingBinder implements Binder, PrivateBinder { + private final Stage stage; + private final Map modules; + private final List elements; + private final Object source; + private final SourceProvider sourceProvider; + private final Set scanners; + /** + * The binder where exposed bindings will be created + */ + private final RecordingBinder parent; + private final PrivateElementsImpl privateElements; + /** + * All children private binders, so we can scan through them. + */ + private final List privateBinders; + /** + * The current modules stack + */ + private ModuleSource moduleSource = null; + + private RecordingBinder(Stage stage) { + this.stage = stage; + this.modules = Maps.newLinkedHashMap(); + this.scanners = Sets.newLinkedHashSet(); + this.elements = Lists.newArrayList(); + this.source = null; + this.sourceProvider = SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses( + Elements.class, RecordingBinder.class, AbstractModule.class, + ConstantBindingBuilderImpl.class, AbstractBindingBuilder.class, BindingBuilder.class); + this.parent = null; + this.privateElements = null; + this.privateBinders = Lists.newArrayList(); + } + + /** + * Creates a recording binder that's backed by {@code prototype}. + */ + private RecordingBinder( + RecordingBinder prototype, Object source, SourceProvider sourceProvider) { + checkArgument(source == null ^ sourceProvider == null); + + this.stage = prototype.stage; + this.modules = prototype.modules; + this.elements = prototype.elements; + this.scanners = prototype.scanners; + this.source = source; + this.moduleSource = prototype.moduleSource; + this.sourceProvider = sourceProvider; + this.parent = prototype.parent; + this.privateElements = prototype.privateElements; + this.privateBinders = prototype.privateBinders; + } + + /** + * Creates a private recording binder. + */ + private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) { + this.stage = parent.stage; + this.modules = Maps.newLinkedHashMap(); + this.scanners = Sets.newLinkedHashSet(parent.scanners); + this.elements = privateElements.getElementsMutable(); + this.source = parent.source; + this.moduleSource = parent.moduleSource; + this.sourceProvider = parent.sourceProvider; + this.parent = parent; + this.privateElements = privateElements; + this.privateBinders = parent.privateBinders; + } + + @Override + public void bindScope(Class annotationType, Scope scope) { + elements.add(new ScopeBinding(getElementSource(), annotationType, scope)); + } + + @Override + @SuppressWarnings("unchecked") // it is safe to use the type literal for the raw type + public void requestInjection(Object instance) { + requestInjection((TypeLiteral) TypeLiteral.get(instance.getClass()), instance); + } + + @Override + public void requestInjection(TypeLiteral type, T instance) { + elements.add(new InjectionRequest(getElementSource(), MoreTypes.canonicalizeForKey(type), + instance)); + } + + @Override + public MembersInjector getMembersInjector(final TypeLiteral typeLiteral) { + final MembersInjectorLookup element = new MembersInjectorLookup(getElementSource(), + MoreTypes.canonicalizeForKey(typeLiteral)); + elements.add(element); + return element.getMembersInjector(); + } + + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } + + public void bindListener(Matcher> typeMatcher, TypeListener listener) { + elements.add(new TypeListenerBinding(getElementSource(), listener, typeMatcher)); + } + + public void bindListener(Matcher> bindingMatcher, + ProvisionListener... listeners) { + elements.add(new ProvisionListenerBinding(getElementSource(), bindingMatcher, listeners)); + } + + public void requestStaticInjection(Class... types) { + for (Class type : types) { + elements.add(new StaticInjectionRequest(getElementSource(), type)); + } + } + + /** + * Applies all scanners to the modules we've installed. We skip certain + * PrivateModules because store them in more than one Modules map and only + * want to process them through one of the maps. (They're stored in both + * maps to prevent a module from being installed more than once.) + */ + void scanForAnnotatedMethods() { + for (ModuleAnnotatedMethodScanner scanner : scanners) { + // Note: we must iterate over a copy of the modules because calling install(..) + // will mutate modules, otherwise causing a ConcurrentModificationException. + for (Map.Entry entry : Maps.newLinkedHashMap(modules).entrySet()) { + Module module = entry.getKey(); + ModuleInfo info = entry.getValue(); + if (info.skipScanning) { + continue; + } + moduleSource = entry.getValue().moduleSource; + try { + info.binder.install(ProviderMethodsModule.forModule(module, scanner)); + } catch (RuntimeException e) { + Collection messages = Errors.getMessagesFromThrowable(e); + if (!messages.isEmpty()) { + elements.addAll(messages); + } else { + addError(e); + } + } + } + } + moduleSource = null; + } + + public void install(Module module) { + if (!modules.containsKey(module)) { + RecordingBinder binder = this; + boolean unwrapModuleSource = false; + // Update the module source for the new module + if (module instanceof ProviderMethodsModule) { + // There are two reason's we'd want to get the module source in a ProviderMethodsModule. + // ModuleAnnotatedMethodScanner lets users scan their own modules for @Provides-like + // bindings. If they install the module at a top-level, then moduleSource can be null. + // Also, if they pass something other than 'this' to it, we'd have the wrong source. + Object delegate = ((ProviderMethodsModule) module).getDelegateModule(); + if (moduleSource == null + || !moduleSource.getModuleClassName().equals(delegate.getClass().getName())) { + moduleSource = getModuleSource(delegate); + unwrapModuleSource = true; + } + } else { + moduleSource = getModuleSource(module); + unwrapModuleSource = true; + } + boolean skipScanning = false; + if (module instanceof PrivateModule) { + binder = (RecordingBinder) binder.newPrivateBinder(); + // Store the module in the private binder too so we scan for it. + binder.modules.put(module, new ModuleInfo(binder, moduleSource, false)); + skipScanning = true; // don't scan this module in the parent's module set. + } + // Always store this in the parent binder (even if it was a private module) + // so that we know not to process it again, and so that scanners inherit down. + modules.put(module, new ModuleInfo(binder, moduleSource, skipScanning)); + try { + module.configure(binder); + } catch (RuntimeException e) { + Collection messages = Errors.getMessagesFromThrowable(e); + if (!messages.isEmpty()) { + elements.addAll(messages); + } else { + addError(e); + } + } + binder.install(ProviderMethodsModule.forModule(module)); + // We are done with this module, so undo module source change + if (unwrapModuleSource) { + moduleSource = moduleSource.getParent(); + } + } + } + + public Stage currentStage() { + return stage; + } + + public void addError(String message, Object... arguments) { + elements.add(new Message(getElementSource(), Errors.format(message, arguments))); + } + + public void addError(Throwable t) { + String message = "An exception was caught and reported. Message: " + t.getMessage(); + elements.add(new Message(ImmutableList.of((Object) getElementSource()), message, t)); + } + + public void addError(Message message) { + elements.add(message); + } + + public AnnotatedBindingBuilder bind(Key key) { + return new BindingBuilder(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key)); + } + + public AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) { + return bind(Key.get(typeLiteral)); + } + + public AnnotatedBindingBuilder bind(Class type) { + return bind(Key.get(type)); + } + + public AnnotatedConstantBindingBuilder bindConstant() { + return new ConstantBindingBuilderImpl(this, elements, getElementSource()); + } + + public Provider getProvider(final Key key) { + return getProvider(Dependency.get(key)); + } + + public Provider getProvider(final Dependency dependency) { + final ProviderLookup element = new ProviderLookup(getElementSource(), dependency); + elements.add(element); + return element.getProvider(); + } + + public Provider getProvider(Class type) { + return getProvider(Key.get(type)); + } + + public void convertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + elements.add(new TypeConverterBinding(getElementSource(), typeMatcher, converter)); + } + + public RecordingBinder withSource(final Object source) { + return source == this.source ? this : new RecordingBinder(this, source, null); + } + + public RecordingBinder skipSources(Class... classesToSkip) { + // if a source is specified explicitly, we don't need to skip sources + if (source != null) { + return this; + } + + SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip); + return new RecordingBinder(this, null, newSourceProvider); + } + + @Override + public PrivateBinder newPrivateBinder() { + PrivateElementsImpl privateElements = new PrivateElementsImpl(getElementSource()); + RecordingBinder binder = new RecordingBinder(this, privateElements); + privateBinders.add(binder); + elements.add(privateElements); + return binder; + } + + @Override + public void disableCircularProxies() { + elements.add(new DisableCircularProxiesOption(getElementSource())); + } + + @Override + public void requireExplicitBindings() { + elements.add(new RequireExplicitBindingsOption(getElementSource())); + } + + @Override + public void requireAtInjectOnConstructors() { + elements.add(new RequireAtInjectOnConstructorsOption(getElementSource())); + } + + @Override + public void requireExactBindingAnnotations() { + elements.add(new RequireExactBindingAnnotationsOption(getElementSource())); + } + + @Override + public void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner) { + scanners.add(scanner); + elements.add(new ModuleAnnotatedMethodScannerBinding(getElementSource(), scanner)); + } + + public void expose(Key key) { + exposeInternal(key); + } + + @Override + public AnnotatedElementBuilder expose(Class type) { + return exposeInternal(Key.get(type)); + } + + @Override + public AnnotatedElementBuilder expose(TypeLiteral type) { + return exposeInternal(Key.get(type)); + } + + private AnnotatedElementBuilder exposeInternal(Key key) { + if (privateElements == null) { + addError("Cannot expose %s on a standard binder. " + + "Exposed bindings are only applicable to private binders.", key); + return new AnnotatedElementBuilder() { + @Override + public void annotatedWith(Class annotationType) { + } + + @Override + public void annotatedWith(Annotation annotation) { + } + }; + } + + ExposureBuilder builder = + new ExposureBuilder(this, getElementSource(), MoreTypes.canonicalizeKey(key)); + privateElements.addExposureBuilder(builder); + return builder; + } + + private ModuleSource getModuleSource(Object module) { + StackTraceElement[] partialCallStack; + if (getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE) { + partialCallStack = getPartialCallStack(new Throwable().getStackTrace()); + } else { + partialCallStack = new StackTraceElement[0]; + } + if (moduleSource == null) { + return new ModuleSource(module, partialCallStack); + } + return moduleSource.createChild(module, partialCallStack); + } + + private ElementSource getElementSource() { + // Full call stack + StackTraceElement[] callStack = null; + // The call stack starts from current top module configure and ends at this method caller + StackTraceElement[] partialCallStack = new StackTraceElement[0]; + // The element original source + ElementSource originalSource = null; + // The element declaring source + Object declaringSource = source; + if (declaringSource instanceof ElementSource) { + originalSource = (ElementSource) declaringSource; + declaringSource = originalSource.getDeclaringSource(); + } + IncludeStackTraceOption stackTraceOption = getIncludeStackTraceOption(); + if (stackTraceOption == IncludeStackTraceOption.COMPLETE || + (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE + && declaringSource == null)) { + callStack = new Throwable().getStackTrace(); + } + if (stackTraceOption == IncludeStackTraceOption.COMPLETE) { + partialCallStack = getPartialCallStack(callStack); + } + if (declaringSource == null) { + // So 'source' and 'originalSource' are null otherwise declaringSource has some value + if (stackTraceOption == IncludeStackTraceOption.COMPLETE || + stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE) { + // With the above conditions and assignments 'callStack' is non-null + declaringSource = sourceProvider.get(callStack); + } else { // or if (stackTraceOption == IncludeStackTraceOptions.OFF) + // As neither 'declaring source' nor 'call stack' is available use 'module source' + declaringSource = sourceProvider.getFromClassNames(moduleSource.getModuleClassNames()); + } + } + // Build the binding call stack + return new ElementSource( + originalSource, declaringSource, moduleSource, partialCallStack); + } + + /** + * Removes the {@link #moduleSource} call stack from the beginning of current call stack. It + * also removes the last two elements in order to make {@link #install(Module)} the last call + * in the call stack. + */ + private StackTraceElement[] getPartialCallStack(StackTraceElement[] callStack) { + int toSkip = 0; + if (moduleSource != null) { + toSkip = moduleSource.getStackTraceSize(); + } + // -1 for skipping 'getModuleSource' and 'getElementSource' calls + int chunkSize = callStack.length - toSkip - 1; + + StackTraceElement[] partialCallStack = new StackTraceElement[chunkSize]; + System.arraycopy(callStack, 1, partialCallStack, 0, chunkSize); + return partialCallStack; + } + + @Override + public String toString() { + return "Binder"; + } + } +} diff --git a/src/main/java/com/google/inject/spi/ExposedBinding.java b/src/main/java/com/google/inject/spi/ExposedBinding.java new file mode 100644 index 0000000..5627b03 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ExposedBinding.java @@ -0,0 +1,21 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.Binding; + +/** + * A binding to a key exposed from an enclosed private environment. + * + */ +public interface ExposedBinding extends Binding, HasDependencies { + + /** + * Returns the enclosed environment that holds the original binding. + */ + PrivateElements getPrivateElements(); + + /** + * Unsupported. Always throws {@link UnsupportedOperationException}. + */ + void applyTo(Binder binder); +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/HasDependencies.java b/src/main/java/com/google/inject/spi/HasDependencies.java new file mode 100644 index 0000000..9ddc16a --- /dev/null +++ b/src/main/java/com/google/inject/spi/HasDependencies.java @@ -0,0 +1,19 @@ +package com.google.inject.spi; + +import java.util.Set; + +/** + * Implemented by {@link com.google.inject.Binding bindings}, {@link com.google.inject.Provider + * providers} and instances that expose their dependencies explicitly. + */ +public interface HasDependencies { + + /** + * Returns the known dependencies for this type. If this has dependencies whose values are not + * known statically, a dependency for the {@link com.google.inject.Injector Injector} will be + * included in the returned set. + * + * @return a possibly empty set + */ + Set> getDependencies(); +} diff --git a/src/main/java/com/google/inject/spi/InjectionListener.java b/src/main/java/com/google/inject/spi/InjectionListener.java new file mode 100644 index 0000000..0b6d608 --- /dev/null +++ b/src/main/java/com/google/inject/spi/InjectionListener.java @@ -0,0 +1,15 @@ +package com.google.inject.spi; + +/** + * Listens for injections into instances of type {@code I}. Useful for performing further + * injections, post-injection initialization, and more. + */ +public interface InjectionListener { + + /** + * Invoked by Guice after it injects the fields and methods of instance. + * + * @param injectee instance that Guice injected dependencies into + */ + void afterInjection(I injectee); +} diff --git a/src/main/java/com/google/inject/spi/InjectionPoint.java b/src/main/java/com/google/inject/spi/InjectionPoint.java new file mode 100644 index 0000000..e3cd5dc --- /dev/null +++ b/src/main/java/com/google/inject/spi/InjectionPoint.java @@ -0,0 +1,856 @@ +package com.google.inject.spi; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.ConfigurationException; +import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.Annotations; +import com.google.inject.internal.Errors; +import com.google.inject.internal.ErrorsException; +import com.google.inject.internal.Nullability; +import com.google.inject.internal.util.Classes; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.inject.internal.MoreTypes.getRawType; + +/** + * A constructor, field or method that can receive injections. Typically this is a member with the + * {@literal @}{@link Inject} annotation. For non-private, no argument constructors, the member may + * omit the annotation. + */ +public final class InjectionPoint { + + private final boolean optional; + private final Member member; + private final TypeLiteral declaringType; + private final ImmutableList> dependencies; + + InjectionPoint(TypeLiteral declaringType, Method method, boolean optional) { + this.member = method; + this.declaringType = declaringType; + this.optional = optional; + this.dependencies = forMember(method, declaringType, method.getParameterAnnotations()); + } + + InjectionPoint(TypeLiteral declaringType, Constructor constructor) { + this.member = constructor; + this.declaringType = declaringType; + this.optional = false; + this.dependencies = forMember( + constructor, declaringType, constructor.getParameterAnnotations()); + } + + InjectionPoint(TypeLiteral declaringType, Field field, boolean optional) { + this.member = field; + this.declaringType = declaringType; + this.optional = optional; + + Annotation[] annotations = field.getAnnotations(); + + Errors errors = new Errors(field); + Key key = null; + try { + key = Annotations.getKey(declaringType.getFieldType(field), field, annotations, errors); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + errors.throwConfigurationExceptionIfErrorsExist(); + + this.dependencies = ImmutableList.>of( + newDependency(key, Nullability.allowsNull(annotations), -1)); + } + + /** + * Returns a new injection point for the specified constructor. If the declaring type of {@code + * constructor} is parameterized (such as {@code List}), prefer the overload that includes a + * type literal. + * + * @param constructor any single constructor present on {@code type}. + */ + public static InjectionPoint forConstructor(Constructor constructor) { + return new InjectionPoint(TypeLiteral.get(constructor.getDeclaringClass()), constructor); + } + + /** + * Returns a new injection point for the specified constructor of {@code type}. + * + * @param constructor any single constructor present on {@code type}. + * @param type the concrete type that defines {@code constructor}. + */ + public static InjectionPoint forConstructor( + Constructor constructor, TypeLiteral type) { + if (type.getRawType() != constructor.getDeclaringClass()) { + new Errors(type) + .constructorNotDefinedByType(constructor, type) + .throwConfigurationExceptionIfErrorsExist(); + } + + return new InjectionPoint(type, constructor); + } + + /** + * Returns a new injection point for the injectable constructor of {@code type}. + * + * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject}, + * or a no-arguments constructor that is not private. + * @throws ConfigurationException if there is no injectable constructor, more than one injectable + * constructor, or if parameters of the injectable constructor are malformed, such as + * a + * parameter with multiple binding annotations. + */ + public static InjectionPoint forConstructorOf(TypeLiteral type) { + Class rawType = getRawType(type.getType()); + Errors errors = new Errors(rawType); + + Constructor injectableConstructor = null; + for (Constructor constructor : rawType.getDeclaredConstructors()) { + + boolean optional; + Inject guiceInject = constructor.getAnnotation(Inject.class); + if (guiceInject == null) { + javax.inject.Inject javaxInject = constructor.getAnnotation(javax.inject.Inject.class); + if (javaxInject == null) { + continue; + } + optional = false; + } else { + optional = guiceInject.optional(); + } + + if (optional) { + errors.optionalConstructor(constructor); + } + + if (injectableConstructor != null) { + errors.tooManyConstructors(rawType); + } + + injectableConstructor = constructor; + checkForMisplacedBindingAnnotations(injectableConstructor, errors); + } + + errors.throwConfigurationExceptionIfErrorsExist(); + + if (injectableConstructor != null) { + return new InjectionPoint(type, injectableConstructor); + } + + // If no annotated constructor is found, look for a no-arg constructor instead. + try { + Constructor noArgConstructor = rawType.getDeclaredConstructor(); + + // Disallow private constructors on non-private classes (unless they have @Inject) + if (Modifier.isPrivate(noArgConstructor.getModifiers()) + && !Modifier.isPrivate(rawType.getModifiers())) { + errors.missingConstructor(rawType); + throw new ConfigurationException(errors.getMessages()); + } + + checkForMisplacedBindingAnnotations(noArgConstructor, errors); + return new InjectionPoint(type, noArgConstructor); + } catch (NoSuchMethodException e) { + errors.missingConstructor(rawType); + throw new ConfigurationException(errors.getMessages()); + } + } + + /** + * Returns a new injection point for the injectable constructor of {@code type}. + * + * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject}, + * or a no-arguments constructor that is not private. + * @throws ConfigurationException if there is no injectable constructor, more than one injectable + * constructor, or if parameters of the injectable constructor are malformed, such as + * a + * parameter with multiple binding annotations. + */ + public static InjectionPoint forConstructorOf(Class type) { + return forConstructorOf(TypeLiteral.get(type)); + } + + /** + * Returns a new injection point for the specified method of {@code type}. + * This is useful for extensions that need to build dependency graphs from + * arbitrary methods. + * + * @param method any single method present on {@code type}. + * @param type the concrete type that defines {@code method}. + */ + public static InjectionPoint forMethod(Method method, TypeLiteral type) { + return new InjectionPoint(type, method, false); + } + + /** + * Returns all static method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code + * Set} + * of the valid injection points. + */ + public static Set forStaticMethodsAndFields(TypeLiteral type) { + Errors errors = new Errors(); + + Set result; + + if (type.getRawType().isInterface()) { + errors.staticInjectionOnInterface(type.getRawType()); + result = null; + } else { + result = getInjectionPoints(type, true, errors); + } + + if (errors.hasErrors()) { + throw new ConfigurationException(errors.getMessages()).withPartialValue(result); + } + return result; + } + + /** + * Returns all static method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code + * Set} + * of the valid injection points. + */ + public static Set forStaticMethodsAndFields(Class type) { + return forStaticMethodsAndFields(TypeLiteral.get(type)); + } + + /** + * Returns all instance method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code + * Set} + * of the valid injection points. + */ + public static Set forInstanceMethodsAndFields(TypeLiteral type) { + Errors errors = new Errors(); + Set result = getInjectionPoints(type, false, errors); + if (errors.hasErrors()) { + throw new ConfigurationException(errors.getMessages()).withPartialValue(result); + } + return result; + } + + /** + * Returns all instance method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code + * Set} + * of the valid injection points. + */ + public static Set forInstanceMethodsAndFields(Class type) { + return forInstanceMethodsAndFields(TypeLiteral.get(type)); + } + + /** + * Returns true if the binding annotation is in the wrong place. + */ + private static boolean checkForMisplacedBindingAnnotations(Member member, Errors errors) { + Annotation misplacedBindingAnnotation = Annotations.findBindingAnnotation( + errors, member, ((AnnotatedElement) member).getAnnotations()); + if (misplacedBindingAnnotation == null) { + return false; + } + + // don't warn about misplaced binding annotations on methods when there's a field with the same + // name. In Scala, fields always get accessor methods (that we need to ignore). See bug 242. + if (member instanceof Method) { + try { + if (member.getDeclaringClass().getDeclaredField(member.getName()) != null) { + return false; + } + } catch (NoSuchFieldException ignore) { + } + } + + errors.misplacedBindingAnnotation(member, misplacedBindingAnnotation); + return true; + } + + static Annotation getAtInject(AnnotatedElement member) { + Annotation a = member.getAnnotation(javax.inject.Inject.class); + return a == null ? member.getAnnotation(Inject.class) : a; + } + + /** + * Returns an ordered, immutable set of injection points for the given type. Members in + * superclasses come before members in subclasses. Within a class, fields come before methods. + * Overridden methods are filtered out. + * + * @param statics true is this method should return static members, false for instance members + * @param errors used to record errors + */ + private static Set getInjectionPoints(final TypeLiteral type, + boolean statics, Errors errors) { + InjectableMembers injectableMembers = new InjectableMembers(); + OverrideIndex overrideIndex = null; + + List> hierarchy = hierarchyFor(type); + int topIndex = hierarchy.size() - 1; + for (int i = topIndex; i >= 0; i--) { + if (overrideIndex != null && i < topIndex) { + // Knowing the position within the hierarchy helps us make optimizations. + if (i == 0) { + overrideIndex.position = Position.BOTTOM; + } else { + overrideIndex.position = Position.MIDDLE; + } + } + + TypeLiteral current = hierarchy.get(i); + + for (Field field : current.getRawType().getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) == statics) { + Annotation atInject = getAtInject(field); + if (atInject != null) { + InjectableField injectableField = new InjectableField(current, field, atInject); + if (injectableField.jsr330 && Modifier.isFinal(field.getModifiers())) { + errors.cannotInjectFinalField(field); + } + injectableMembers.add(injectableField); + } + } + } + + for (Method method : current.getRawType().getDeclaredMethods()) { + if (isEligibleForInjection(method, statics)) { + Annotation atInject = getAtInject(method); + if (atInject != null) { + InjectableMethod injectableMethod = new InjectableMethod( + current, method, atInject); + if (checkForMisplacedBindingAnnotations(method, errors) + || !isValidMethod(injectableMethod, errors)) { + if (overrideIndex != null) { + boolean removed = overrideIndex.removeIfOverriddenBy(method, false, injectableMethod); + if (removed) { + /*logger.log(Level.WARNING, "Method: {0} is not a valid injectable method (" + + "because it either has misplaced binding annotations " + + "or specifies type parameters) but is overriding a method that is valid. " + + "Because it is not valid, the method will not be injected. " + + "To fix this, make the method a valid injectable method.", method);*/ + } + } + continue; + } + if (statics) { + injectableMembers.add(injectableMethod); + } else { + if (overrideIndex == null) { + /* + * Creating the override index lazily means that the first type in the hierarchy + * with injectable methods (not necessarily the top most type) will be treated as + * the TOP position and will enjoy the same optimizations (no checks for overridden + * methods, etc.). + */ + overrideIndex = new OverrideIndex(injectableMembers); + } else { + // Forcibly remove the overriden method, otherwise we'll inject + // it twice. + overrideIndex.removeIfOverriddenBy(method, true, injectableMethod); + } + overrideIndex.add(injectableMethod); + } + } else { + if (overrideIndex != null) { + boolean removed = overrideIndex.removeIfOverriddenBy(method, false, null); + if (removed) { + /*logger.log(Level.WARNING, "Method: {0} is not annotated with @Inject but " + + "is overriding a method that is annotated with @javax.inject.Inject. Because " + + "it is not annotated with @Inject, the method will not be injected. " + + "To fix this, annotate the method with @Inject.", method);*/ + } + } + } + } + } + } + + if (injectableMembers.isEmpty()) { + return Collections.emptySet(); + } + + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (InjectableMember im = injectableMembers.head; im != null; + im = im.next) { + try { + builder.add(im.toInjectionPoint()); + } catch (ConfigurationException ignorable) { + if (!im.optional) { + errors.merge(ignorable.getErrorMessages()); + } + } + } + return builder.build(); + } + + /** + * Returns true if the method is eligible to be injected. This is different than + * {@link #isValidMethod}, because ineligibility will not drop a method + * from being injected if a superclass was eligible & valid. + * Bridge & synthetic methods are excluded from eligibility for two reasons: + * + *

Prior to Java8, javac would generate these methods in subclasses without + * annotations, which means this would accidentally stop injecting a method + * annotated with {@link javax.inject.Inject}, since the spec says to stop + * injecting if a subclass isn't annotated with it. + * + *

Starting at Java8, javac copies the annotations to the generated subclass + * method, except it leaves out the generic types. If this considered it a valid + * injectable method, this would eject the parent's overridden method that had the + * proper generic types, and would use invalid injectable parameters as a result. + * + *

The fix for both is simply to ignore these synthetic bridge methods. + */ + private static boolean isEligibleForInjection(Method method, boolean statics) { + return Modifier.isStatic(method.getModifiers()) == statics + && !method.isBridge() + && !method.isSynthetic(); + } + + private static boolean isValidMethod(InjectableMethod injectableMethod, + Errors errors) { + boolean result = true; + if (injectableMethod.jsr330) { + Method method = injectableMethod.method; + if (Modifier.isAbstract(method.getModifiers())) { + errors.cannotInjectAbstractMethod(method); + result = false; + } + if (method.getTypeParameters().length > 0) { + errors.cannotInjectMethodWithTypeParameters(method); + result = false; + } + } + return result; + } + + private static List> hierarchyFor(TypeLiteral type) { + List> hierarchy = new ArrayList>(); + TypeLiteral current = type; + while (current.getRawType() != Object.class) { + hierarchy.add(current); + current = current.getSupertype(current.getRawType().getSuperclass()); + } + return hierarchy; + } + + /** + * Returns true if a overrides b. Assumes signatures of a and b are the same and a's declaring + * class is a subclass of b's declaring class. + */ + private static boolean overrides(Method a, Method b) { + // See JLS section 8.4.8.1 + int modifiers = b.getModifiers(); + if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { + return true; + } + if (Modifier.isPrivate(modifiers)) { + return false; + } + // b must be package-private + return a.getDeclaringClass().getPackage().equals(b.getDeclaringClass().getPackage()); + } + + private ImmutableList> forMember(Member member, TypeLiteral type, + Annotation[][] paramterAnnotations) { + Errors errors = new Errors(member); + Iterator annotationsIterator = Arrays.asList(paramterAnnotations).iterator(); + + List> dependencies = Lists.newArrayList(); + int index = 0; + + for (TypeLiteral parameterType : type.getParameterTypes(member)) { + try { + Annotation[] parameterAnnotations = annotationsIterator.next(); + Key key = Annotations.getKey(parameterType, member, parameterAnnotations, errors); + dependencies.add(newDependency(key, Nullability.allowsNull(parameterAnnotations), index)); + index++; + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + errors.throwConfigurationExceptionIfErrorsExist(); + return ImmutableList.copyOf(dependencies); + } + + // This metohd is necessary to create a Dependency with proper generic type information + private Dependency newDependency(Key key, boolean allowsNull, int parameterIndex) { + return new Dependency(this, key, allowsNull, parameterIndex); + } + + /** + * Returns the injected constructor, field, or method. + */ + public Member getMember() { + // TODO: Don't expose the original member (which probably has setAccessible(true)). + return member; + } + + /** + * Returns the dependencies for this injection point. If the injection point is for a method or + * constructor, the dependencies will correspond to that member's parameters. Field injection + * points always have a single dependency for the field itself. + * + * @return a possibly-empty list + */ + public List> getDependencies() { + return dependencies; + } + + /** + * Returns true if this injection point shall be skipped if the injector cannot resolve bindings + * for all required dependencies. Both explicit bindings (as specified in a module), and implicit + * bindings ({@literal @}{@link com.google.inject.ImplementedBy ImplementedBy}, default + * constructors etc.) may be used to satisfy optional injection points. + */ + public boolean isOptional() { + return optional; + } + + /** + * Returns true if the element is annotated with {@literal @}{@link Toolable}. + */ + public boolean isToolable() { + return ((AnnotatedElement) member).isAnnotationPresent(Toolable.class); + } + + /** + * Returns the generic type that defines this injection point. If the member exists on a + * parameterized type, the result will include more type information than the member's {@link + * Member#getDeclaringClass() raw declaring class}. + */ + public TypeLiteral getDeclaringType() { + return declaringType; + } + + @Override + public boolean equals(Object o) { + return o instanceof InjectionPoint + && member.equals(((InjectionPoint) o).member) + && declaringType.equals(((InjectionPoint) o).declaringType); + } + + @Override + public int hashCode() { + return member.hashCode() ^ declaringType.hashCode(); + } + + @Override + public String toString() { + return Classes.toString(member); + } + + /** + * Position in type hierarchy. + */ + enum Position { + TOP, // No need to check for overridden methods + MIDDLE, + BOTTOM // Methods won't be overridden + } + + /** + * Node in the doubly-linked list of injectable members (fields and methods). + */ + static abstract class InjectableMember { + final TypeLiteral declaringType; + final boolean optional; + final boolean jsr330; + InjectableMember previous; + InjectableMember next; + + InjectableMember(TypeLiteral declaringType, Annotation atInject) { + this.declaringType = declaringType; + + if (atInject.annotationType() == javax.inject.Inject.class) { + optional = false; + jsr330 = true; + return; + } + + jsr330 = false; + optional = ((Inject) atInject).optional(); + } + + abstract InjectionPoint toInjectionPoint(); + } + + static class InjectableField extends InjectableMember { + final Field field; + + InjectableField(TypeLiteral declaringType, Field field, + Annotation atInject) { + super(declaringType, atInject); + this.field = field; + } + + @Override + InjectionPoint toInjectionPoint() { + return new InjectionPoint(declaringType, field, optional); + } + } + + static class InjectableMethod extends InjectableMember { + final Method method; + /** + * true if this method overrode a method that was annotated + * with com.google.inject.Inject. used to allow different + * override behavior for guice inject vs javax.inject.Inject + */ + boolean overrodeGuiceInject; + + InjectableMethod(TypeLiteral declaringType, Method method, + Annotation atInject) { + super(declaringType, atInject); + this.method = method; + } + + @Override + InjectionPoint toInjectionPoint() { + return new InjectionPoint(declaringType, method, optional); + } + + public boolean isFinal() { + return Modifier.isFinal(method.getModifiers()); + } + } + + /** + * Linked list of injectable members. + */ + static class InjectableMembers { + InjectableMember head; + InjectableMember tail; + + void add(InjectableMember member) { + if (head == null) { + head = tail = member; + } else { + member.previous = tail; + tail.next = member; + tail = member; + } + } + + void remove(InjectableMember member) { + if (member.previous != null) { + member.previous.next = member.next; + } + if (member.next != null) { + member.next.previous = member.previous; + } + if (head == member) { + head = member.next; + } + if (tail == member) { + tail = member.previous; + } + } + + boolean isEmpty() { + return head == null; + } + } + + /** + * Keeps track of injectable methods so we can remove methods that get overridden in O(1) time. + * Uses our position in the type hierarchy to perform optimizations. + */ + static class OverrideIndex { + final InjectableMembers injectableMembers; + Map> bySignature; + Position position = Position.TOP; + /* Caches the signature for the last method. */ + Method lastMethod; + Signature lastSignature; + OverrideIndex(InjectableMembers injectableMembers) { + this.injectableMembers = injectableMembers; + } + + /** + * Removes a method overridden by the given method, if present. In order to + * remain backwards compatible with prior Guice versions, this will *not* + * remove overridden methods if 'alwaysRemove' is false and the overridden + * signature was annotated with a com.google.inject.Inject. + * + * @param method The method used to determine what is overridden and should be + * removed. + * @param alwaysRemove true if overridden methods should be removed even if they were + * guice @Inject + * @param injectableMethod if this method overrode any guice @Inject methods, + * {@link InjectableMethod#overrodeGuiceInject} is set to true + */ + boolean removeIfOverriddenBy(Method method, boolean alwaysRemove, + InjectableMethod injectableMethod) { + if (position == Position.TOP) { + // If we're at the top of the hierarchy, there's nothing to override. + return false; + } + + if (bySignature == null) { + // We encountered a method in a subclass. Time to index the + // methods in the parent class. + bySignature = new HashMap>(); + for (InjectableMember member = injectableMembers.head; member != null; + member = member.next) { + if (!(member instanceof InjectableMethod)) { + continue; + } + InjectableMethod im = (InjectableMethod) member; + if (im.isFinal()) { + continue; + } + List methods = new ArrayList(); + methods.add(im); + bySignature.put(new Signature(im.method), methods); + } + } + + lastMethod = method; + Signature signature = lastSignature = new Signature(method); + List methods = bySignature.get(signature); + boolean removed = false; + if (methods != null) { + for (Iterator iterator = methods.iterator(); + iterator.hasNext(); ) { + InjectableMethod possiblyOverridden = iterator.next(); + if (overrides(method, possiblyOverridden.method)) { + boolean wasGuiceInject = + !possiblyOverridden.jsr330 || possiblyOverridden.overrodeGuiceInject; + if (injectableMethod != null) { + injectableMethod.overrodeGuiceInject = wasGuiceInject; + } + // Only actually remove the methods if we want to force + // remove or if the signature never specified @com.google.inject.Inject + // somewhere. + if (alwaysRemove || !wasGuiceInject) { + removed = true; + iterator.remove(); + injectableMembers.remove(possiblyOverridden); + } + } + } + } + return removed; + } + + /** + * Adds the given method to the list of injection points. Keeps track of it in this index + * in case it gets overridden. + */ + void add(InjectableMethod injectableMethod) { + injectableMembers.add(injectableMethod); + if (position == Position.BOTTOM + || injectableMethod.isFinal()) { + // This method can't be overridden, so there's no need to index it. + return; + } + if (bySignature != null) { + // Try to reuse the signature we created during removal + Signature signature = injectableMethod.method == lastMethod + ? lastSignature : new Signature(injectableMethod.method); + List methods = bySignature.get(signature); + if (methods == null) { + methods = new ArrayList(); + bySignature.put(signature, methods); + } + methods.add(injectableMethod); + } + } + } + + /** + * A method signature. Used to handle method overridding. + */ + static class Signature { + + final String name; + final Class[] parameterTypes; + final int hash; + + Signature(Method method) { + this.name = method.getName(); + this.parameterTypes = method.getParameterTypes(); + + int h = name.hashCode(); + h = h * 31 + parameterTypes.length; + for (Class parameterType : parameterTypes) { + h = h * 31 + parameterType.hashCode(); + } + this.hash = h; + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Signature)) { + return false; + } + + Signature other = (Signature) o; + if (!name.equals(other.name)) { + return false; + } + + if (parameterTypes.length != other.parameterTypes.length) { + return false; + } + + for (int i = 0; i < parameterTypes.length; i++) { + if (parameterTypes[i] != other.parameterTypes[i]) { + return false; + } + } + + return true; + } + } +} diff --git a/src/main/java/com/google/inject/spi/InjectionRequest.java b/src/main/java/com/google/inject/spi/InjectionRequest.java new file mode 100644 index 0000000..16aeaa9 --- /dev/null +++ b/src/main/java/com/google/inject/spi/InjectionRequest.java @@ -0,0 +1,68 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.ConfigurationException; +import com.google.inject.TypeLiteral; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A request to inject the instance fields and methods of an instance. Requests are created + * explicitly in a module using {@link com.google.inject.Binder#requestInjection(Object) + * requestInjection()} statements: + *

+ *     requestInjection(serviceInstance);
+ * + */ +public final class InjectionRequest implements Element { + + private final Object source; + private final TypeLiteral type; + private final T instance; + + public InjectionRequest(Object source, TypeLiteral type, T instance) { + this.source = checkNotNull(source, "source"); + this.type = checkNotNull(type, "type"); + this.instance = checkNotNull(instance, "instance"); + } + + public Object getSource() { + return source; + } + + public T getInstance() { + return instance; + } + + public TypeLiteral getType() { + return type; + } + + /** + * Returns the instance methods and fields of {@code instance} that will be injected to fulfill + * this request. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on the class of {@code + * instance}, such as a field with multiple binding annotations. The exception's + * {@link + * ConfigurationException#getPartialValue() partial value} is a {@code + * Set} + * of the valid injection points. + */ + public Set getInjectionPoints() throws ConfigurationException { + return InjectionPoint.forInstanceMethodsAndFields(instance.getClass()); + } + + public R acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requestInjection(type, instance); + } +} diff --git a/src/main/java/com/google/inject/spi/InstanceBinding.java b/src/main/java/com/google/inject/spi/InstanceBinding.java new file mode 100644 index 0000000..e3df76e --- /dev/null +++ b/src/main/java/com/google/inject/spi/InstanceBinding.java @@ -0,0 +1,26 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; + +import java.util.Set; + +/** + * A binding to a single instance. The same instance is returned for every injection. + * + */ +public interface InstanceBinding extends Binding, HasDependencies { + + /** + * Returns the user-supplied instance. + */ + T getInstance(); + + /** + * Returns the field and method injection points of the instance, injected at injector-creation + * time only. + * + * @return a possibly empty set + */ + Set getInjectionPoints(); + +} diff --git a/src/main/java/com/google/inject/spi/LinkedKeyBinding.java b/src/main/java/com/google/inject/spi/LinkedKeyBinding.java new file mode 100644 index 0000000..21831c3 --- /dev/null +++ b/src/main/java/com/google/inject/spi/LinkedKeyBinding.java @@ -0,0 +1,17 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; +import com.google.inject.Key; + +/** + * A binding to a linked key. The other key's binding is used to resolve injections. + */ +public interface LinkedKeyBinding extends Binding { + + /** + * Returns the linked key used to resolve injections. That binding can be retrieved from an + * injector using {@link com.google.inject.Injector#getBinding(Key) Injector.getBinding(key)}. + */ + Key getLinkedKey(); + +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/MembersInjectorLookup.java b/src/main/java/com/google/inject/spi/MembersInjectorLookup.java new file mode 100644 index 0000000..a43c0e1 --- /dev/null +++ b/src/main/java/com/google/inject/spi/MembersInjectorLookup.java @@ -0,0 +1,86 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.MembersInjector; +import com.google.inject.TypeLiteral; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * A lookup of the members injector for a type. Lookups are created explicitly in a module using + * {@link com.google.inject.Binder#getMembersInjector(Class) getMembersInjector()} statements: + *
+ *     MembersInjector<PaymentService> membersInjector
+ *         = getMembersInjector(PaymentService.class);
+ * + */ +public final class MembersInjectorLookup implements Element { + + private final Object source; + private final TypeLiteral type; + private MembersInjector delegate; + + public MembersInjectorLookup(Object source, TypeLiteral type) { + this.source = checkNotNull(source, "source"); + this.type = checkNotNull(type, "type"); + } + + public Object getSource() { + return source; + } + + /** + * Gets the type containing the members to be injected. + */ + public TypeLiteral getType() { + return type; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + /** + * Sets the actual members injector. + * + * @throws IllegalStateException if the delegate is already set + */ + public void initializeDelegate(MembersInjector delegate) { + checkState(this.delegate == null, "delegate already initialized"); + this.delegate = checkNotNull(delegate, "delegate"); + } + + public void applyTo(Binder binder) { + initializeDelegate(binder.withSource(getSource()).getMembersInjector(type)); + } + + /** + * Returns the delegate members injector, or {@code null} if it has not yet been initialized. + * The delegate will be initialized when this element is processed, or otherwise used to create + * an injector. + */ + public MembersInjector getDelegate() { + return delegate; + } + + /** + * Returns the looked up members injector. The result is not valid until this lookup has been + * initialized, which usually happens when the injector is created. The members injector will + * throw an {@code IllegalStateException} if you try to use it beforehand. + */ + public MembersInjector getMembersInjector() { + return new MembersInjector() { + public void injectMembers(T instance) { + checkState(delegate != null, + "This MembersInjector cannot be used until the Injector has been created."); + delegate.injectMembers(instance); + } + + @Override + public String toString() { + return "MembersInjector<" + type + ">"; + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/Message.java b/src/main/java/com/google/inject/spi/Message.java new file mode 100644 index 0000000..70f3af8 --- /dev/null +++ b/src/main/java/com/google/inject/spi/Message.java @@ -0,0 +1,100 @@ +package com.google.inject.spi; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.internal.Errors; +import com.google.inject.internal.util.SourceProvider; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An error message and the context in which it occured. Messages are usually created internally by + * Guice and its extensions. Messages can be created explicitly in a module using {@link + * com.google.inject.Binder#addError(Throwable) addError()} statements: + *
+ *     try {
+ *       bindPropertiesFromFile();
+ *     } catch (IOException e) {
+ *       addError(e);
+ *     }
+ * + */ +public final class Message implements Element { + private final String message; + private final Throwable cause; + private final List sources; + + public Message(List sources, String message, Throwable cause) { + this.sources = ImmutableList.copyOf(sources); + this.message = checkNotNull(message, "message"); + this.cause = cause; + } + + public Message(String message, Throwable cause) { + this(ImmutableList.of(), message, cause); + } + + public Message(Object source, String message) { + this(ImmutableList.of(source), message, null); + } + + public Message(String message) { + this(ImmutableList.of(), message, null); + } + + public String getSource() { + return sources.isEmpty() + ? SourceProvider.UNKNOWN_SOURCE.toString() + : Errors.convert(sources.get(sources.size() - 1)).toString(); + } + + public List getSources() { + return sources; + } + + /** + * Gets the error message text. + */ + public String getMessage() { + return message; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + /** + * Returns the throwable that caused this message, or {@code null} if this + * message was not caused by a throwable. + */ + public Throwable getCause() { + return cause; + } + + @Override + public String toString() { + return message; + } + + @Override + public int hashCode() { + return message.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Message)) { + return false; + } + Message e = (Message) o; + return message.equals(e.message) && Objects.equal(cause, e.cause) && sources.equals(e.sources); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).addError(this); + } + +} diff --git a/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScanner.java b/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScanner.java new file mode 100644 index 0000000..4b2ff40 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScanner.java @@ -0,0 +1,37 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.Key; + +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Allows extensions to scan modules for annotated methods and bind those methods + * as providers, similar to {@code @Provides} methods. + */ +public abstract class ModuleAnnotatedMethodScanner { + + /** + * Returns the annotations this should scan for. Every method in the module that has one of these + * annotations will create a Provider binding, with the return value of the binding being what's + * provided and the parameters of the method being dependencies of the provider. + */ + public abstract Set> annotationClasses(); + + /** + * Prepares a method for binding. This {@code key} parameter is the key discovered from looking at + * the binding annotation and return value of the method. Implementations can modify the key to + * instead bind to another key. For example, Multibinder may want to change + * {@code @SetProvides String provideFoo()} to bind into a unique Key within the multibinder + * instead of binding {@code String}. + * + *

The injection point and annotation are provided in case the implementation wants to set the + * key based on the property of the annotation or if any additional preparation is needed for any + * of the dependencies. The annotation is guaranteed to be an instance of one the classes returned + * by {@link #annotationClasses}. + */ + public abstract Key prepareMethod(Binder binder, Annotation annotation, Key key, + InjectionPoint injectionPoint); + +} diff --git a/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java b/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java new file mode 100644 index 0000000..8ff988a --- /dev/null +++ b/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java @@ -0,0 +1,41 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.internal.Errors; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Represents a call to {@link Binder#scanModulesForAnnotatedMethods} in a module. + */ +public final class ModuleAnnotatedMethodScannerBinding implements Element { + private final Object source; + private final ModuleAnnotatedMethodScanner scanner; + + public ModuleAnnotatedMethodScannerBinding(Object source, ModuleAnnotatedMethodScanner scanner) { + this.source = checkNotNull(source, "source"); + this.scanner = checkNotNull(scanner, "scanner"); + } + + public Object getSource() { + return source; + } + + public ModuleAnnotatedMethodScanner getScanner() { + return scanner; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).scanModulesForAnnotatedMethods(scanner); + } + + @Override + public String toString() { + return scanner + " which scans for " + scanner.annotationClasses() + + " (bound at " + Errors.convert(source) + ")"; + } +} diff --git a/src/main/java/com/google/inject/spi/ModuleSource.java b/src/main/java/com/google/inject/spi/ModuleSource.java new file mode 100644 index 0000000..9ca8a77 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ModuleSource.java @@ -0,0 +1,170 @@ +package com.google.inject.spi; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.internal.util.StackTraceElements; +import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement; + +import java.util.List; + +/** + * Associated to a {@link Module module}, provides the module class name, the parent module {@link + * ModuleSource source}, and the call stack that ends just before the module {@link + * Module#configure(Binder)} method invocation. + */ +final class ModuleSource { + + /** + * The class name of module that this {@link ModuleSource} associated to. + */ + private final String moduleClassName; + + /** + * The parent {@link ModuleSource module source}. + */ + private final ModuleSource parent; + + /** + * The chunk of call stack that starts from the parent module {@link Module#configure(Binder) + * configure(Binder)} call and ends just before the module {@link Module#configure(Binder) + * configure(Binder)} method invocation. For a module without a parent module the chunk starts + * from the bottom of call stack. The array is non-empty if stack trace collection is on. + */ + private final InMemoryStackTraceElement[] partialCallStack; + + /** + * Creates a new {@link ModuleSource} with a {@literal null} parent. + * + * @param module the corresponding module + * @param partialCallStack the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation + */ + ModuleSource(Object module, StackTraceElement[] partialCallStack) { + this(null, module, partialCallStack); + } + + /** + * Creates a new {@link ModuleSource} Object. + * + * @param parent the parent module {@link ModuleSource source} + * @param module the corresponding module + * @param partialCallStack the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation + */ + private ModuleSource( + /* @Nullable */ ModuleSource parent, Object module, StackTraceElement[] partialCallStack) { + Preconditions.checkNotNull(module, "module cannot be null."); + Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); + this.parent = parent; + this.moduleClassName = module.getClass().getName(); + this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack); + } + + /** + * Returns the corresponding module class name. + * + * @see Class#getName() + */ + String getModuleClassName() { + return moduleClassName; + } + + /** + * Returns the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation. The return array is non-empty + * only if stack trace collection is on. + */ + StackTraceElement[] getPartialCallStack() { + return StackTraceElements.convertToStackTraceElement(partialCallStack); + } + + /** + * Returns the size of partial call stack if stack trace collection is on otherwise zero. + */ + int getPartialCallStackSize() { + return partialCallStack.length; + } + + /** + * Creates and returns a child {@link ModuleSource} corresponding to the {@link Module module}. + * + * @param module the corresponding module + * @param partialCallStack the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation + */ + ModuleSource createChild(Object module, StackTraceElement[] partialCallStack) { + return new ModuleSource(this, module, partialCallStack); + } + + /** + * Returns the parent module {@link ModuleSource source}. + */ + ModuleSource getParent() { + return parent; + } + + /** + * Returns the class names of modules in this module source. The first element (index 0) is filled + * by this object {@link #getModuleClassName()}. The second element is filled by the parent's + * {@link #getModuleClassName()} and so on. + */ + List getModuleClassNames() { + ImmutableList.Builder classNames = ImmutableList.builder(); + ModuleSource current = this; + while (current != null) { + String className = current.moduleClassName; + classNames.add(className); + current = current.parent; + } + return classNames.build(); + } + + /** + * Returns the size of {@link ModuleSource ModuleSources} chain (all parents) that ends at this + * object. + */ + int size() { + if (parent == null) { + return 1; + } + return parent.size() + 1; + } + + /** + * Returns the size of call stack that ends just before the module {@link Module#configure(Binder) + * configure(Binder)} method invocation (see {@link #getStackTrace()}). + */ + int getStackTraceSize() { + if (parent == null) { + return partialCallStack.length; + } + return parent.getStackTraceSize() + partialCallStack.length; + } + + /** + * Returns the full call stack that ends just before the module {@link Module#configure(Binder) + * configure(Binder)} method invocation. The return array is non-empty if stack trace collection + * on. + */ + StackTraceElement[] getStackTrace() { + int stackTraceSize = getStackTraceSize(); + StackTraceElement[] callStack = new StackTraceElement[stackTraceSize]; + int cursor = 0; + ModuleSource current = this; + while (current != null) { + StackTraceElement[] chunk = + StackTraceElements.convertToStackTraceElement(current.partialCallStack); + int chunkSize = chunk.length; + System.arraycopy(chunk, 0, callStack, cursor, chunkSize); + current = current.parent; + cursor = cursor + chunkSize; + } + return callStack; + } +} diff --git a/src/main/java/com/google/inject/spi/PrivateElements.java b/src/main/java/com/google/inject/spi/PrivateElements.java new file mode 100644 index 0000000..9bf5c9d --- /dev/null +++ b/src/main/java/com/google/inject/spi/PrivateElements.java @@ -0,0 +1,42 @@ +package com.google.inject.spi; + +import com.google.inject.Injector; +import com.google.inject.Key; + +import java.util.List; +import java.util.Set; + +/** + * A private collection of elements that are hidden from the enclosing injector or module by + * default. See {@link com.google.inject.PrivateModule PrivateModule} for details. + */ +public interface PrivateElements extends Element { + + /** + * Returns the configuration information in this private environment. + */ + List getElements(); + + /** + * Returns the child injector that hosts these private elements, or null if the elements haven't + * been used to create an injector. + */ + Injector getInjector(); + + /** + * Returns the unique exposed keys for these private elements. + */ + Set> getExposedKeys(); + + /** + * Returns an arbitrary object containing information about the "place" where this key was + * exposed. Used by Guice in the production of descriptive error messages. + * + *

Tools might specially handle types they know about; {@code StackTraceElement} is a good + * example. Tools should simply call {@code toString()} on the source object if the type is + * unfamiliar. + * + * @param key one of the keys exposed by this module. + */ + Object getExposedSource(Key key); +} diff --git a/src/main/java/com/google/inject/spi/ProviderBinding.java b/src/main/java/com/google/inject/spi/ProviderBinding.java new file mode 100644 index 0000000..69fd748 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProviderBinding.java @@ -0,0 +1,19 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Provider; + +/** + * A binding to a {@link Provider} that delegates to the binding for the provided type. This binding + * is used whenever a {@code Provider} is injected (as opposed to injecting {@code T} directly). + */ +public interface ProviderBinding> extends Binding { + + /** + * Returns the key whose binding is used to {@link Provider#get provide instances}. That binding + * can be retrieved from an injector using {@link com.google.inject.Injector#getBinding(Key) + * Injector.getBinding(providedKey)} + */ + Key getProvidedKey(); +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/ProviderInstanceBinding.java b/src/main/java/com/google/inject/spi/ProviderInstanceBinding.java new file mode 100644 index 0000000..371a7f1 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProviderInstanceBinding.java @@ -0,0 +1,26 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; + +import java.util.Set; + +/** + * A binding to a provider instance. The provider's {@code get} method is invoked to resolve + * injections. + */ +public interface ProviderInstanceBinding extends Binding, HasDependencies { + + /** + * Returns the user-supplied, unscoped provider. + */ + javax.inject.Provider getUserSuppliedProvider(); + + /** + * Returns the field and method injection points of the provider, injected at injector-creation + * time only. + * + * @return a possibly empty set + */ + Set getInjectionPoints(); + +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/ProviderKeyBinding.java b/src/main/java/com/google/inject/spi/ProviderKeyBinding.java new file mode 100644 index 0000000..af74647 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProviderKeyBinding.java @@ -0,0 +1,19 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; +import com.google.inject.Key; + +/** + * A binding to a provider key. To resolve injections, the provider key is first resolved, then that + * provider's {@code get} method is invoked. + */ +public interface ProviderKeyBinding extends Binding { + + /** + * Returns the key used to resolve the provider's binding. That binding can be retrieved from an + * injector using {@link com.google.inject.Injector#getBinding(Key) + * Injector.getBinding(providerKey)} + */ + Key> getProviderKey(); + +} \ No newline at end of file diff --git a/src/main/java/com/google/inject/spi/ProviderLookup.java b/src/main/java/com/google/inject/spi/ProviderLookup.java new file mode 100644 index 0000000..911d72f --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProviderLookup.java @@ -0,0 +1,100 @@ +package com.google.inject.spi; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.util.Types; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * A lookup of the provider for a type. Lookups are created explicitly in a module using + * {@link com.google.inject.Binder#getProvider(Class) getProvider()} statements: + *

+ *     Provider<PaymentService> paymentServiceProvider
+ *         = getProvider(PaymentService.class);
+ * + */ +public final class ProviderLookup implements Element { + private final Object source; + private final Dependency dependency; + private Provider delegate; + + public ProviderLookup(Object source, Key key) { + this(source, Dependency.get(checkNotNull(key, "key"))); + } + + public ProviderLookup(Object source, Dependency dependency) { + this.source = checkNotNull(source, "source"); + this.dependency = checkNotNull(dependency, "dependency"); + } + + public Object getSource() { + return source; + } + + public Key getKey() { + return dependency.getKey(); + } + + public Dependency getDependency() { + return dependency; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + /** + * Sets the actual provider. + * + * @throws IllegalStateException if the delegate is already set + */ + public void initializeDelegate(Provider delegate) { + checkState(this.delegate == null, "delegate already initialized"); + this.delegate = checkNotNull(delegate, "delegate"); + } + + public void applyTo(Binder binder) { + initializeDelegate(binder.withSource(getSource()).getProvider(dependency)); + } + + /** + * Returns the delegate provider, or {@code null} if it has not yet been initialized. The delegate + * will be initialized when this element is processed, or otherwise used to create an injector. + */ + public Provider getDelegate() { + return delegate; + } + + /** + * Returns the looked up provider. The result is not valid until this lookup has been initialized, + * which usually happens when the injector is created. The provider will throw an {@code + * IllegalStateException} if you try to use it beforehand. + */ + public Provider getProvider() { + return new ProviderWithDependencies() { + public T get() { + checkState(delegate != null, + "This Provider cannot be used until the Injector has been created."); + return delegate.get(); + } + + public Set> getDependencies() { + // We depend on Provider, not T directly. This is an important distinction + // for dependency analysis tools that short-circuit on providers. + Key providerKey = getKey().ofType(Types.providerOf(getKey().getTypeLiteral().getType())); + return ImmutableSet.>of(Dependency.get(providerKey)); + } + + @Override + public String toString() { + return "Provider<" + getKey().getTypeLiteral() + ">"; + } + }; + } +} diff --git a/src/main/java/com/google/inject/spi/ProviderWithDependencies.java b/src/main/java/com/google/inject/spi/ProviderWithDependencies.java new file mode 100644 index 0000000..e77a7b0 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProviderWithDependencies.java @@ -0,0 +1,10 @@ +package com.google.inject.spi; + +import com.google.inject.Provider; + +/** + * A provider with dependencies on other injected types. If a {@link Provider} has dependencies that + * aren't specified in injections, this interface should be used to expose all dependencies. + */ +public interface ProviderWithDependencies extends Provider, HasDependencies { +} diff --git a/src/main/java/com/google/inject/spi/ProviderWithExtensionVisitor.java b/src/main/java/com/google/inject/spi/ProviderWithExtensionVisitor.java new file mode 100644 index 0000000..e62d1e5 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProviderWithExtensionVisitor.java @@ -0,0 +1,44 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; +import com.google.inject.Provider; + +/** + * A Provider that is part of an extension which supports a custom + * BindingTargetVisitor. + *

+ * When an extension binds a provider instance, the provider can implement this + * interface to allow users using the + * {@link Binding#acceptTargetVisitor(BindingTargetVisitor)} method to visit a + * custom visitor designed for that extension. A typical implementation within + * the extension would look like + *

+ * <V, B> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B>
+ * binding) {
+ *   if(visitor instanceof MyCustomExtensionVisitor) {
+ *     return ((MyCustomExtensionVisitor<B, V>)visitor).visitCustomExtension(customProperties, binding);
+ *   } else {
+ *     return visitor.visit(binding);
+ *   }
+ * }
+ * 'MyCustomExtensionVisitor' in the example above would be an interface the + * extension provides that users can implement in order to be notified of custom + * extension information. These visitor interfaces must extend from + * BindingTargetVisitor. + * + */ +public interface ProviderWithExtensionVisitor extends Provider { + + /** + * Instructs the extension determine if the visitor is an instance of a custom + * extension visitor, and if so, visit it using that method. If the visitor is + * not an instance of the custom extension visitor, this method MUST + * call visitor.visit(binding). + *

+ * Due to issues with generics, the type parameters of this method do not + * relate to the type of the provider. In practice, the 'B' type will always + * be a supertype of 'T'. + */ + V acceptExtensionVisitor(BindingTargetVisitor visitor, + ProviderInstanceBinding binding); +} diff --git a/src/main/java/com/google/inject/spi/ProvidesMethodBinding.java b/src/main/java/com/google/inject/spi/ProvidesMethodBinding.java new file mode 100644 index 0000000..60f25a3 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProvidesMethodBinding.java @@ -0,0 +1,38 @@ +package com.google.inject.spi; + +import com.google.inject.Key; +import com.google.inject.Provides; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * An {@literal @}{@link Provides} binding or binding produced by a + * {@link ModuleAnnotatedMethodScanner}. + * + */ +public interface ProvidesMethodBinding extends HasDependencies { + + /** + * Returns the method this binding uses. + */ + Method getMethod(); + + /** + * Returns the instance of the object the method is defined in. + */ + Object getEnclosingInstance(); + + /** + * Returns the key of the binding. + */ + Key getKey(); + + /** + * Returns the annotation that caused this binding to be created. For {@code @Provides} methods, + * this is an instance of the {@code @Provides} annotation. For bindings from + * {@link ModuleAnnotatedMethodScanner}, this is the annotation that caused the scanner to produce + * the binding. + */ + Annotation getAnnotation(); +} diff --git a/src/main/java/com/google/inject/spi/ProvidesMethodTargetVisitor.java b/src/main/java/com/google/inject/spi/ProvidesMethodTargetVisitor.java new file mode 100644 index 0000000..5d0b8bf --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProvidesMethodTargetVisitor.java @@ -0,0 +1,17 @@ +package com.google.inject.spi; + +import com.google.inject.Provides; + +/** + * A visitor for the {@literal @}{@link Provides} bindings. + *

+ * If your {@link BindingTargetVisitor} implements this interface, bindings created by using + * {@code @Provides} will be visited through this interface. + */ +public interface ProvidesMethodTargetVisitor extends BindingTargetVisitor { + + /** + * Visits an {@link ProvidesMethodBinding} created with an {@literal @}{@link Provides} method. + */ + V visit(ProvidesMethodBinding providesMethodBinding); +} diff --git a/src/main/java/com/google/inject/spi/ProvisionListener.java b/src/main/java/com/google/inject/spi/ProvisionListener.java new file mode 100644 index 0000000..463dbff --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProvisionListener.java @@ -0,0 +1,58 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; +import com.google.inject.Provider; +import com.google.inject.Scope; + +import java.util.List; + +/** + * Listens for provisioning of objects. Useful for gathering timing information + * about provisioning, post-provision initialization, and more. + * + */ +public interface ProvisionListener { + + /** + * Invoked by Guice when an object requires provisioning. Provisioning occurs + * when Guice locates and injects the dependencies for a binding. For types + * bound to a Provider, provisioning encapsulates the {@link Provider#get} + * method. For toInstance or constant bindings, provisioning encapsulates + * the injecting of {@literal @}{@code Inject}ed fields or methods. + * For other types, provisioning encapsulates the construction of the + * object. If a type is bound within a {@link Scope}, provisioning depends on + * the scope. Types bound in Singleton scope will only be provisioned once. + * Types bound in no scope will be provisioned every time they are injected. + * Other scopes define their own behavior for provisioning. + *

+ * To perform the provision, call {@link ProvisionInvocation#provision()}. + * If you do not explicitly call provision, it will be automatically done after + * this method returns. It is an error to call provision more than once. + */ + void onProvision(ProvisionInvocation provision); + + /** + * Encapsulates a single act of provisioning. + */ + public abstract static class ProvisionInvocation { + + /** + * Returns the Binding this is provisioning. + *

+ * You must not call {@link Provider#get()} on the provider returned by + * {@link Binding#getProvider}, otherwise you will get confusing error messages. + */ + public abstract Binding getBinding(); + + /** + * Performs the provision, returning the object provisioned. + */ + public abstract T provision(); + + /** + * Returns the dependency chain that led to this object being provisioned. + */ + public abstract List getDependencyChain(); + + } +} diff --git a/src/main/java/com/google/inject/spi/ProvisionListenerBinding.java b/src/main/java/com/google/inject/spi/ProvisionListenerBinding.java new file mode 100644 index 0000000..8d8b719 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ProvisionListenerBinding.java @@ -0,0 +1,54 @@ +package com.google.inject.spi; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.matcher.Matcher; + +import java.util.List; + +/** + * Binds keys (picked using a Matcher) to a provision listener. Listeners are created explicitly in + * a module using {@link Binder#bindListener(Matcher, ProvisionListener...)} statements: + */ +public final class ProvisionListenerBinding implements Element { + + private final Object source; + private final Matcher> bindingMatcher; + private final List listeners; + + ProvisionListenerBinding(Object source, + Matcher> bindingMatcher, + ProvisionListener[] listeners) { + this.source = source; + this.bindingMatcher = bindingMatcher; + this.listeners = ImmutableList.copyOf(listeners); + } + + /** + * Returns the registered listeners. + */ + public List getListeners() { + return listeners; + } + + /** + * Returns the binding matcher which chooses which bindings the listener should be notified of. + */ + public Matcher> getBindingMatcher() { + return bindingMatcher; + } + + public Object getSource() { + return source; + } + + public R acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).bindListener(bindingMatcher, + listeners.toArray(new ProvisionListener[listeners.size()])); + } +} diff --git a/src/main/java/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java b/src/main/java/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java new file mode 100644 index 0000000..d17db13 --- /dev/null +++ b/src/main/java/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java @@ -0,0 +1,29 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.Inject; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A request to require explicit {@literal @}{@link Inject} annotations on constructors. + */ +public final class RequireAtInjectOnConstructorsOption implements Element { + private final Object source; + + RequireAtInjectOnConstructorsOption(Object source) { + this.source = checkNotNull(source, "source"); + } + + public Object getSource() { + return source; + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requireAtInjectOnConstructors(); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/com/google/inject/spi/RequireExactBindingAnnotationsOption.java b/src/main/java/com/google/inject/spi/RequireExactBindingAnnotationsOption.java new file mode 100644 index 0000000..41fb488 --- /dev/null +++ b/src/main/java/com/google/inject/spi/RequireExactBindingAnnotationsOption.java @@ -0,0 +1,28 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A request to require exact binding annotations. + */ +public final class RequireExactBindingAnnotationsOption implements Element { + private final Object source; + + RequireExactBindingAnnotationsOption(Object source) { + this.source = checkNotNull(source, "source"); + } + + public Object getSource() { + return source; + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requireExactBindingAnnotations(); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/com/google/inject/spi/RequireExplicitBindingsOption.java b/src/main/java/com/google/inject/spi/RequireExplicitBindingsOption.java new file mode 100644 index 0000000..194c0a1 --- /dev/null +++ b/src/main/java/com/google/inject/spi/RequireExplicitBindingsOption.java @@ -0,0 +1,28 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A request to require explicit bindings. + */ +public final class RequireExplicitBindingsOption implements Element { + private final Object source; + + RequireExplicitBindingsOption(Object source) { + this.source = checkNotNull(source, "source"); + } + + public Object getSource() { + return source; + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requireExplicitBindings(); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/com/google/inject/spi/ScopeBinding.java b/src/main/java/com/google/inject/spi/ScopeBinding.java new file mode 100644 index 0000000..b338b84 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ScopeBinding.java @@ -0,0 +1,49 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.Scope; + +import java.lang.annotation.Annotation; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Registration of a scope annotation with the scope that implements it. Instances are created + * explicitly in a module using {@link com.google.inject.Binder#bindScope(Class, Scope) bindScope()} + * statements: + *

+ *     Scope recordScope = new RecordScope();
+ *     bindScope(RecordScoped.class, new RecordScope());
+ * + */ +public final class ScopeBinding implements Element { + private final Object source; + private final Class annotationType; + private final Scope scope; + + ScopeBinding(Object source, Class annotationType, Scope scope) { + this.source = checkNotNull(source, "source"); + this.annotationType = checkNotNull(annotationType, "annotationType"); + this.scope = checkNotNull(scope, "scope"); + } + + public Object getSource() { + return source; + } + + public Class getAnnotationType() { + return annotationType; + } + + public Scope getScope() { + return scope; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).bindScope(annotationType, scope); + } +} diff --git a/src/main/java/com/google/inject/spi/StaticInjectionRequest.java b/src/main/java/com/google/inject/spi/StaticInjectionRequest.java new file mode 100644 index 0000000..56f8e29 --- /dev/null +++ b/src/main/java/com/google/inject/spi/StaticInjectionRequest.java @@ -0,0 +1,59 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.ConfigurationException; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A request to inject the static fields and methods of a type. Requests are created + * explicitly in a module using {@link com.google.inject.Binder#requestStaticInjection(Class[]) + * requestStaticInjection()} statements: + *
+ *     requestStaticInjection(MyLegacyService.class);
+ * + */ +public final class StaticInjectionRequest implements Element { + private final Object source; + private final Class type; + + StaticInjectionRequest(Object source, Class type) { + this.source = checkNotNull(source, "source"); + this.type = checkNotNull(type, "type"); + } + + public Object getSource() { + return source; + } + + public Class getType() { + return type; + } + + /** + * Returns the static methods and fields of {@code type} that will be injected to fulfill this + * request. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code + * Set} + * of the valid injection points. + */ + public Set getInjectionPoints() throws ConfigurationException { + return InjectionPoint.forStaticMethodsAndFields(type); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requestStaticInjection(type); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/com/google/inject/spi/Toolable.java b/src/main/java/com/google/inject/spi/Toolable.java new file mode 100644 index 0000000..6bcbffb --- /dev/null +++ b/src/main/java/com/google/inject/spi/Toolable.java @@ -0,0 +1,27 @@ +package com.google.inject.spi; + +import com.google.inject.Injector; +import com.google.inject.Stage; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Instructs an {@link Injector} running in {@link Stage#TOOL} that a method should be injected. + * This is typically useful for for extensions to Guice that perform additional validation in an + * injected method or field. This only applies to objects that are already constructed when + * bindings are created (ie., something bound using {@link + * com.google.inject.binder.LinkedBindingBuilder#toProvider toProvider}, {@link + * com.google.inject.binder.LinkedBindingBuilder#toInstance toInstance}, or {@link + * com.google.inject.Binder#requestInjection requestInjection}. + * + */ +@Target({METHOD}) +@Retention(RUNTIME) +@Documented +public @interface Toolable { +} diff --git a/src/main/java/com/google/inject/spi/TypeConverter.java b/src/main/java/com/google/inject/spi/TypeConverter.java new file mode 100644 index 0000000..d0a0bce --- /dev/null +++ b/src/main/java/com/google/inject/spi/TypeConverter.java @@ -0,0 +1,14 @@ +package com.google.inject.spi; + +import com.google.inject.TypeLiteral; + +/** + * Converts constant string values to a different type. + */ +public interface TypeConverter { + + /** + * Converts a string value. Throws an exception if a conversion error occurs. + */ + Object convert(String value, TypeLiteral toType); +} diff --git a/src/main/java/com/google/inject/spi/TypeConverterBinding.java b/src/main/java/com/google/inject/spi/TypeConverterBinding.java new file mode 100644 index 0000000..f6e35f5 --- /dev/null +++ b/src/main/java/com/google/inject/spi/TypeConverterBinding.java @@ -0,0 +1,55 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.Errors; +import com.google.inject.matcher.Matcher; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Registration of type converters for matching target types. Instances are created + * explicitly in a module using {@link com.google.inject.Binder#convertToTypes(Matcher, + * TypeConverter) convertToTypes()} statements: + *
+ *     convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter());
+ * + */ +public final class TypeConverterBinding implements Element { + private final Object source; + private final Matcher> typeMatcher; + private final TypeConverter typeConverter; + + public TypeConverterBinding(Object source, Matcher> typeMatcher, + TypeConverter typeConverter) { + this.source = checkNotNull(source, "source"); + this.typeMatcher = checkNotNull(typeMatcher, "typeMatcher"); + this.typeConverter = checkNotNull(typeConverter, "typeConverter"); + } + + public Object getSource() { + return source; + } + + public Matcher> getTypeMatcher() { + return typeMatcher; + } + + public TypeConverter getTypeConverter() { + return typeConverter; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).convertToTypes(typeMatcher, typeConverter); + } + + @Override + public String toString() { + return typeConverter + " which matches " + typeMatcher + + " (bound at " + Errors.convert(source) + ")"; + } +} diff --git a/src/main/java/com/google/inject/spi/TypeEncounter.java b/src/main/java/com/google/inject/spi/TypeEncounter.java new file mode 100644 index 0000000..6d96ae5 --- /dev/null +++ b/src/main/java/com/google/inject/spi/TypeEncounter.java @@ -0,0 +1,84 @@ +package com.google.inject.spi; + +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; + +/** + * Context of an injectable type encounter. Enables reporting errors, registering injection + * listeners and binding method interceptors for injectable type {@code I}. It is an error to use + * an encounter after the {@link TypeListener#hear(TypeLiteral, TypeEncounter) hear()} method has + * returned. + * + * @param the injectable type encountered + */ +public interface TypeEncounter { + + /** + * Records an error message for type {@code I} which will be presented to the user at a later + * time. Unlike throwing an exception, this enable us to continue configuring the Injector and + * discover more errors. Uses {@link String#format(String, Object[])} to insert the arguments + * into the message. + */ + void addError(String message, Object... arguments); + + /** + * Records an exception for type {@code I}, the full details of which will be logged, and the + * message of which will be presented to the user at a later time. If your type listener calls + * something that you worry may fail, you should catch the exception and pass it to this method. + */ + void addError(Throwable t); + + /** + * Records an error message to be presented to the user at a later time. + */ + void addError(Message message); + + /** + * Returns the provider used to obtain instances for the given injection key. The returned + * provider will not be valid until the injector has been created. The provider will throw an + * {@code IllegalStateException} if you try to use it beforehand. + */ + Provider getProvider(Key key); + + /** + * Returns the provider used to obtain instances for the given injection type. The returned + * provider will not be valid until the injector has been created. The provider will throw an + * {@code IllegalStateException} if you try to use it beforehand. + */ + Provider getProvider(Class type); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * injector has been created. The members injector will throw an {@code IllegalStateException} + * if you try to use it beforehand. + * + * @param typeLiteral type to get members injector for + */ + MembersInjector getMembersInjector(TypeLiteral typeLiteral); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * injector has been created. The members injector will throw an {@code IllegalStateException} + * if you try to use it beforehand. + * + * @param type type to get members injector for + */ + MembersInjector getMembersInjector(Class type); + + /** + * Registers a members injector for type {@code I}. Guice will use the members injector after its + * performed its own injections on an instance of {@code I}. + */ + void register(MembersInjector membersInjector); + + /** + * Registers an injection listener for type {@code I}. Guice will notify the listener after all + * injections have been performed on an instance of {@code I}. + */ + void register(InjectionListener listener); + +} diff --git a/src/main/java/com/google/inject/spi/TypeListener.java b/src/main/java/com/google/inject/spi/TypeListener.java new file mode 100644 index 0000000..726f56d --- /dev/null +++ b/src/main/java/com/google/inject/spi/TypeListener.java @@ -0,0 +1,24 @@ +package com.google.inject.spi; + +import com.google.inject.TypeLiteral; + +/** + * Listens for Guice to encounter injectable types. If a given type has its constructor injected in + * one situation but only its methods and fields injected in another, Guice will notify this + * listener once. + */ +public interface TypeListener { + + /** + * Invoked when Guice encounters a new type eligible for constructor or members injection. + * Called during injector creation (or afterwords if Guice encounters a type at run time and + * creates a JIT binding). + * + * @param type encountered by Guice + * @param encounter context of this encounter, enables reporting errors, registering injection + * listeners and binding method interceptors for {@code type}. + * @param the injectable type + */ + void hear(TypeLiteral type, TypeEncounter encounter); + +} diff --git a/src/main/java/com/google/inject/spi/TypeListenerBinding.java b/src/main/java/com/google/inject/spi/TypeListenerBinding.java new file mode 100644 index 0000000..22a2ce6 --- /dev/null +++ b/src/main/java/com/google/inject/spi/TypeListenerBinding.java @@ -0,0 +1,53 @@ +package com.google.inject.spi; + +import com.google.inject.Binder; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matcher; + +/** + * Binds types (picked using a Matcher) to an type listener. Registrations are created explicitly in + * a module using {@link com.google.inject.Binder#bindListener(Matcher, TypeListener)} statements: + * + *
+ *     register(only(new TypeLiteral<PaymentService<CreditCard>>() {}), listener);
+ * + */ +public final class TypeListenerBinding implements Element { + + private final Object source; + private final Matcher> typeMatcher; + private final TypeListener listener; + + TypeListenerBinding(Object source, TypeListener listener, + Matcher> typeMatcher) { + this.source = source; + this.listener = listener; + this.typeMatcher = typeMatcher; + } + + /** + * Returns the registered listener. + */ + public TypeListener getListener() { + return listener; + } + + /** + * Returns the type matcher which chooses which types the listener should be notified of. + */ + public Matcher> getTypeMatcher() { + return typeMatcher; + } + + public Object getSource() { + return source; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).bindListener(typeMatcher, listener); + } +} diff --git a/src/main/java/com/google/inject/spi/UntargettedBinding.java b/src/main/java/com/google/inject/spi/UntargettedBinding.java new file mode 100644 index 0000000..dc8aefd --- /dev/null +++ b/src/main/java/com/google/inject/spi/UntargettedBinding.java @@ -0,0 +1,11 @@ +package com.google.inject.spi; + +import com.google.inject.Binding; + +/** + * An untargetted binding. This binding indicates that the injector should use its implicit binding + * strategies to resolve injections. + * + */ +public interface UntargettedBinding extends Binding { +} diff --git a/src/main/java/com/google/inject/util/Modules.java b/src/main/java/com/google/inject/util/Modules.java new file mode 100644 index 0000000..463e01a --- /dev/null +++ b/src/main/java/com/google/inject/util/Modules.java @@ -0,0 +1,355 @@ +package com.google.inject.util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.PrivateBinder; +import com.google.inject.PrivateModule; +import com.google.inject.Scope; +import com.google.inject.internal.Errors; +import com.google.inject.spi.DefaultBindingScopingVisitor; +import com.google.inject.spi.DefaultElementVisitor; +import com.google.inject.spi.Element; +import com.google.inject.spi.ElementVisitor; +import com.google.inject.spi.Elements; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; +import com.google.inject.spi.PrivateElements; +import com.google.inject.spi.ScopeBinding; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Static utility methods for creating and working with instances of {@link Module}. + * + */ +public final class Modules { + public static final Module EMPTY_MODULE = new EmptyModule(); + + private Modules() { + } + + /** + * Returns a builder that creates a module that overlays override modules over the given + * modules. If a key is bound in both sets of modules, only the binding from the override modules + * is kept. If a single {@link PrivateModule} is supplied or all elements are from + * a single {@link PrivateBinder}, then this will overwrite the private bindings. + * Otherwise, private bindings will not be overwritten unless they are exposed. + * This can be used to replace the bindings of a production module with test bindings: + *
+     * Module functionalTestModule
+     *     = Modules.override(new ProductionModule()).with(new TestModule());
+     * 
+ * + *

Prefer to write smaller modules that can be reused and tested without overrides. + * + * @param modules the modules whose bindings are open to be overridden + */ + public static OverriddenModuleBuilder override(Module... modules) { + return new RealOverriddenModuleBuilder(Arrays.asList(modules)); + } + + /** + * Returns a builder that creates a module that overlays override modules over the given + * modules. If a key is bound in both sets of modules, only the binding from the override modules + * is kept. If a single {@link PrivateModule} is supplied or all elements are from + * a single {@link PrivateBinder}, then this will overwrite the private bindings. + * Otherwise, private bindings will not be overwritten unless they are exposed. + * This can be used to replace the bindings of a production module with test bindings: + *

+     * Module functionalTestModule
+     *     = Modules.override(getProductionModules()).with(getTestModules());
+     * 
+ * + *

Prefer to write smaller modules that can be reused and tested without overrides. + * + * @param modules the modules whose bindings are open to be overridden + */ + public static OverriddenModuleBuilder override(Iterable modules) { + return new RealOverriddenModuleBuilder(modules); + } + + /** + * Returns a new module that installs all of {@code modules}. + */ + public static Module combine(Module... modules) { + return combine(ImmutableSet.copyOf(modules)); + } + + /** + * Returns a new module that installs all of {@code modules}. + */ + public static Module combine(Iterable modules) { + return new CombinedModule(modules); + } + + private static Module extractScanners(Iterable elements) { + final List scanners = Lists.newArrayList(); + ElementVisitor visitor = new DefaultElementVisitor() { + @Override + public Void visit(ModuleAnnotatedMethodScannerBinding binding) { + scanners.add(binding); + return null; + } + }; + for (Element element : elements) { + element.acceptVisitor(visitor); + } + return new AbstractModule() { + @Override + protected void configure() { + for (ModuleAnnotatedMethodScannerBinding scanner : scanners) { + scanner.applyTo(binder()); + } + } + }; + } + + /** + * See the EDSL example at {@link Modules#override(Module[]) override()}. + */ + public interface OverriddenModuleBuilder { + + /** + * See the EDSL example at {@link Modules#override(Module[]) override()}. + */ + Module with(Module... overrides); + + /** + * See the EDSL example at {@link Modules#override(Module[]) override()}. + */ + Module with(Iterable overrides); + } + + private static class EmptyModule implements Module { + public void configure(Binder binder) { + } + } + + private static class CombinedModule implements Module { + final Set modulesSet; + + CombinedModule(Iterable modules) { + this.modulesSet = ImmutableSet.copyOf(modules); + } + + public void configure(Binder binder) { + binder = binder.skipSources(getClass()); + for (Module module : modulesSet) { + binder.install(module); + } + } + } + + private static final class RealOverriddenModuleBuilder implements OverriddenModuleBuilder { + private final ImmutableSet baseModules; + + private RealOverriddenModuleBuilder(Iterable baseModules) { + this.baseModules = ImmutableSet.copyOf(baseModules); + } + + public Module with(Module... overrides) { + return with(Arrays.asList(overrides)); + } + + public Module with(Iterable overrides) { + return new OverrideModule(overrides, baseModules); + } + } + + static class OverrideModule extends AbstractModule { + private final ImmutableSet overrides; + private final ImmutableSet baseModules; + + OverrideModule(Iterable overrides, ImmutableSet baseModules) { + this.overrides = ImmutableSet.copyOf(overrides); + this.baseModules = baseModules; + } + + @Override + public void configure() { + Binder baseBinder = binder(); + List baseElements = Elements.getElements(currentStage(), baseModules); + + // If the sole element was a PrivateElements, we want to override + // the private elements within that -- so refocus our elements + // and binder. + if (baseElements.size() == 1) { + Element element = Iterables.getOnlyElement(baseElements); + if (element instanceof PrivateElements) { + PrivateElements privateElements = (PrivateElements) element; + PrivateBinder privateBinder = baseBinder.newPrivateBinder().withSource(privateElements.getSource()); + for (Key exposed : privateElements.getExposedKeys()) { + privateBinder.withSource(privateElements.getExposedSource(exposed)).expose(exposed); + } + baseBinder = privateBinder; + baseElements = privateElements.getElements(); + } + } + + final Binder binder = baseBinder.skipSources(this.getClass()); + final LinkedHashSet elements = new LinkedHashSet(baseElements); + final Module scannersModule = extractScanners(elements); + final List overrideElements = Elements.getElements(currentStage(), + ImmutableList.builder().addAll(overrides).add(scannersModule).build()); + + final Set> overriddenKeys = Sets.newHashSet(); + final Map, ScopeBinding> overridesScopeAnnotations = + Maps.newHashMap(); + + // execute the overrides module, keeping track of which keys and scopes are bound + new ModuleWriter(binder) { + @Override + public Void visit(Binding binding) { + overriddenKeys.add(binding.getKey()); + return super.visit(binding); + } + + @Override + public Void visit(ScopeBinding scopeBinding) { + overridesScopeAnnotations.put(scopeBinding.getAnnotationType(), scopeBinding); + return super.visit(scopeBinding); + } + + @Override + public Void visit(PrivateElements privateElements) { + overriddenKeys.addAll(privateElements.getExposedKeys()); + return super.visit(privateElements); + } + }.writeAll(overrideElements); + + // execute the original module, skipping all scopes and overridden keys. We only skip each + // overridden binding once so things still blow up if the module binds the same thing + // multiple times. + final Map> scopeInstancesInUse = Maps.newHashMap(); + final List scopeBindings = Lists.newArrayList(); + new ModuleWriter(binder) { + @Override + public Void visit(Binding binding) { + if (!overriddenKeys.remove(binding.getKey())) { + super.visit(binding); + + // Record when a scope instance is used in a binding + Scope scope = getScopeInstanceOrNull(binding); + if (scope != null) { + List existing = scopeInstancesInUse.get(scope); + if (existing == null) { + existing = Lists.newArrayList(); + scopeInstancesInUse.put(scope, existing); + } + existing.add(binding.getSource()); + } + } + + return null; + } + + void rewrite(Binder binder, PrivateElements privateElements, Set> keysToSkip) { + PrivateBinder privateBinder = binder.withSource(privateElements.getSource()) + .newPrivateBinder(); + + Set> skippedExposes = Sets.newHashSet(); + + for (Key key : privateElements.getExposedKeys()) { + if (keysToSkip.remove(key)) { + skippedExposes.add(key); + } else { + privateBinder.withSource(privateElements.getExposedSource(key)).expose(key); + } + } + + for (Element element : privateElements.getElements()) { + if (element instanceof Binding + && skippedExposes.remove(((Binding) element).getKey())) { + continue; + } + if (element instanceof PrivateElements) { + rewrite(privateBinder, (PrivateElements) element, skippedExposes); + continue; + } + element.applyTo(privateBinder); + } + } + + @Override + public Void visit(PrivateElements privateElements) { + rewrite(binder, privateElements, overriddenKeys); + return null; + } + + @Override + public Void visit(ScopeBinding scopeBinding) { + scopeBindings.add(scopeBinding); + return null; + } + }.writeAll(elements); + + // execute the scope bindings, skipping scopes that have been overridden. Any scope that + // is overridden and in active use will prompt an error + new ModuleWriter(binder) { + @Override + public Void visit(ScopeBinding scopeBinding) { + ScopeBinding overideBinding = + overridesScopeAnnotations.remove(scopeBinding.getAnnotationType()); + if (overideBinding == null) { + super.visit(scopeBinding); + } else { + List usedSources = scopeInstancesInUse.get(scopeBinding.getScope()); + if (usedSources != null) { + StringBuilder sb = new StringBuilder( + "The scope for @%s is bound directly and cannot be overridden."); + sb.append("%n original binding at ").append(Errors.convert(scopeBinding.getSource())); + for (Object usedSource : usedSources) { + sb.append("%n bound directly at ").append(Errors.convert(usedSource)).append(""); + } + binder.withSource(overideBinding.getSource()) + .addError(sb.toString(), scopeBinding.getAnnotationType().getSimpleName()); + } + } + return null; + } + }.writeAll(scopeBindings); + } + + private Scope getScopeInstanceOrNull(Binding binding) { + return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor() { + @Override + public Scope visitScope(Scope scope) { + return scope; + } + }); + } + } + + private static class ModuleWriter extends DefaultElementVisitor { + protected final Binder binder; + + ModuleWriter(Binder binder) { + this.binder = binder.skipSources(this.getClass()); + } + + @Override + protected Void visitOther(Element element) { + element.applyTo(binder); + return null; + } + + void writeAll(Iterable elements) { + for (Element element : elements) { + element.acceptVisitor(this); + } + } + } +} diff --git a/src/main/java/com/google/inject/util/Providers.java b/src/main/java/com/google/inject/util/Providers.java new file mode 100644 index 0000000..99e2792 --- /dev/null +++ b/src/main/java/com/google/inject/util/Providers.java @@ -0,0 +1,56 @@ +package com.google.inject.util; + +import com.google.common.base.Objects; +import com.google.inject.Provider; + +/** + * Static utility methods for creating and working with instances of + * {@link Provider}. + */ +public final class Providers { + + private Providers() { + } + + /** + * Returns a provider which always provides {@code instance}. This should not + * be necessary to use in your application, but is helpful for several types + * of unit tests. + * + * @param instance the instance that should always be provided. This is also + * permitted to be null, to enable aggressive testing, although in real + * life a Guice-supplied Provider will never return null. + */ + public static Provider of(final T instance) { + return new ConstantProvider(instance); + } + + private static final class ConstantProvider implements Provider { + private final T instance; + + private ConstantProvider(T instance) { + this.instance = instance; + } + + public T get() { + return instance; + } + + @Override + public String toString() { + return "of(" + instance + ")"; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof ConstantProvider) + && Objects.equal(instance, ((ConstantProvider) obj).instance); + } + + @Override + public int hashCode() { + return Objects.hashCode(instance); + } + } + +} diff --git a/src/main/java/com/google/inject/util/Types.java b/src/main/java/com/google/inject/util/Types.java new file mode 100644 index 0000000..4cc390b --- /dev/null +++ b/src/main/java/com/google/inject/util/Types.java @@ -0,0 +1,116 @@ +package com.google.inject.util; + +import com.google.inject.Provider; +import com.google.inject.internal.MoreTypes; +import com.google.inject.internal.MoreTypes.GenericArrayTypeImpl; +import com.google.inject.internal.MoreTypes.ParameterizedTypeImpl; +import com.google.inject.internal.MoreTypes.WildcardTypeImpl; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Static methods for working with types. + * + */ +public final class Types { + private Types() { + } + + /** + * Returns a new parameterized type, applying {@code typeArguments} to + * {@code rawType}. The returned type does not have an owner type. + * + * @return a parameterized type. + */ + public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) { + return newParameterizedTypeWithOwner(null, rawType, typeArguments); + } + + /** + * Returns a new parameterized type, applying {@code typeArguments} to + * {@code rawType} and enclosed by {@code ownerType}. + * + * @return a parameterized type. + */ + public static ParameterizedType newParameterizedTypeWithOwner( + Type ownerType, Type rawType, Type... typeArguments) { + return new ParameterizedTypeImpl(ownerType, rawType, typeArguments); + } + + /** + * Returns an array type whose elements are all instances of + * {@code componentType}. + * + * @return a generic array type. + */ + public static GenericArrayType arrayOf(Type componentType) { + return new GenericArrayTypeImpl(componentType); + } + + /** + * Returns a type that represents an unknown type that extends {@code bound}. + * For example, if {@code bound} is {@code CharSequence.class}, this returns + * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class}, + * this returns {@code ?}, which is shorthand for {@code ? extends Object}. + */ + public static WildcardType subtypeOf(Type bound) { + return new WildcardTypeImpl(new Type[]{bound}, MoreTypes.EMPTY_TYPE_ARRAY); + } + + /** + * Returns a type that represents an unknown supertype of {@code bound}. For + * example, if {@code bound} is {@code String.class}, this returns {@code ? + * super String}. + */ + public static WildcardType supertypeOf(Type bound) { + return new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{bound}); + } + + /** + * Returns a type modelling a {@link List} whose elements are of type + * {@code elementType}. + * + * @return a parameterized type. + */ + public static ParameterizedType listOf(Type elementType) { + return newParameterizedType(List.class, elementType); + } + + /** + * Returns a type modelling a {@link Set} whose elements are of type + * {@code elementType}. + * + * @return a parameterized type. + */ + public static ParameterizedType setOf(Type elementType) { + return newParameterizedType(Set.class, elementType); + } + + /** + * Returns a type modelling a {@link Map} whose keys are of type + * {@code keyType} and whose values are of type {@code valueType}. + * + * @return a parameterized type. + */ + public static ParameterizedType mapOf(Type keyType, Type valueType) { + return newParameterizedType(Map.class, keyType, valueType); + } + + // for other custom collections types, use newParameterizedType() + + /** + * Returns a type modelling a {@link Provider} that provides elements of type + * {@code elementType}. + * + * @return a parameterized type. + */ + public static ParameterizedType providerOf(Type providedType) { + return newParameterizedType(Provider.class, providedType); + } +} \ No newline at end of file diff --git a/src/test/java/com/google/inject/Asserts.java b/src/test/java/com/google/inject/Asserts.java new file mode 100644 index 0000000..165172c --- /dev/null +++ b/src/test/java/com/google/inject/Asserts.java @@ -0,0 +1,191 @@ +/** + * Copyright (C) 2008 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; + +import static com.google.inject.internal.InternalFlags.IncludeStackTraceOption; +import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.testing.GcFinalization; + +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class Asserts { + private Asserts() {} + + /** + * Returns the String that would appear in an error message for this chain of classes + * as modules. + */ + public static String asModuleChain(Class... classes) { + return Joiner.on(" -> ").appendTo(new StringBuilder(" (via modules: "), + Iterables.transform(ImmutableList.copyOf(classes), new Function() { + @Override + public String apply(Class input) { + return input.getName(); + } + })).append(")").toString(); + } + + /** + * Returns the source file appears in error messages + */ + public static String getDeclaringSourcePart(Class clazz) { + if (getIncludeStackTraceOption() == IncludeStackTraceOption.OFF) { + return ".configure(Unknown Source"; + } + return ".configure(" + clazz.getSimpleName() + ".java:"; + } + + /** + * Returns true if {@link com.google.inject.internal.InternalFlags#getIncludeStackTraceOption()} returns {@link + * IncludeStackTraceOption#OFF}. + */ + public static boolean isIncludeStackTraceOff() { + return getIncludeStackTraceOption() == IncludeStackTraceOption.OFF; + } + + /** + * Returns true if {@link com.google.inject.internal.InternalFlags#getIncludeStackTraceOption()} returns {@link + * IncludeStackTraceOption#COMPLETE}. + */ + public static boolean isIncludeStackTraceComplete() { + return getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE; + } + + /** + * Fails unless {@code expected.equals(actual)}, {@code + * actual.equals(expected)} and their hash codes are equal. This is useful + * for testing the equals method itself. + */ + public static void assertEqualsBothWays(Object expected, Object actual) { + assertNotNull(expected); + assertNotNull(actual); + assertEquals("expected.equals(actual)", actual, expected); + assertEquals("actual.equals(expected)", expected, actual); + assertEquals("hashCode", expected.hashCode(), actual.hashCode()); + } + + /** + * Fails unless {@code text} includes all {@code substrings}, in order. + */ + public static void assertContains(String text, String... substrings) { + + int startingFrom = 0; + for (String substring : substrings) { + int index = text.indexOf(substring, startingFrom); + assertTrue(String.format("Expected \"%s\" to contain substring \"%s\"", text, substring), + index >= startingFrom); + startingFrom = index + substring.length(); + } + + String lastSubstring = substrings[substrings.length - 1]; + assertTrue(String.format("Expected \"%s\" to contain substring \"%s\" only once),", + text, lastSubstring), text.indexOf(lastSubstring, startingFrom) == -1); + } + + /** + * Fails unless {@code object} doesn't equal itself when reserialized. + */ + public static void assertEqualWhenReserialized(Object object) + throws IOException { + //Object reserialized = reserialize(object); + //assertEquals(object, reserialized); + //assertEquals(object.hashCode(), reserialized.hashCode()); + } + + /** + * Fails unless {@code object} has the same toString value when reserialized. + */ + public static void assertSimilarWhenReserialized(Object object) throws IOException { + //Object reserialized = reserialize(object); + //assertEquals(object.toString(), reserialized.toString()); + } + + /*public static E reserialize(E original) throws IOException { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new ObjectOutputStream(out).writeObject(original); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + @SuppressWarnings("unchecked") // the reserialized type is assignable + E reserialized = (E) new ObjectInputStream(in).readObject(); + return reserialized; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + }*/ + + public static void assertNotSerializable(Object object) throws IOException { + //try { + // reserialize(object); + // Assert.fail(); + //} catch (NotSerializableException expected) { + //} + } + + public static void awaitFullGc() { + // GcFinalization *should* do it, but doesn't work well in practice... + // so we put a second latch and wait for a ReferenceQueue to tell us. + ReferenceQueue queue = new ReferenceQueue(); + WeakReference ref = new WeakReference(new Object(), queue); + GcFinalization.awaitFullGc(); + try { + assertSame("queue didn't return ref in time", ref, queue.remove(5000)); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void awaitClear(WeakReference ref) { + // GcFinalization *should* do it, but doesn't work well in practice... + // so we put a second latch and wait for a ReferenceQueue to tell us. + Object data = ref.get(); + ReferenceQueue queue = null; + WeakReference extraRef = null; + if (data != null) { + queue = new ReferenceQueue(); + extraRef = new WeakReference(data, queue); + data = null; + } + GcFinalization.awaitClear(ref); + if (queue != null) { + try { + assertSame("queue didn't return ref in time", extraRef, queue.remove(5000)); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/test/java/com/google/inject/BinderTest.java b/src/test/java/com/google/inject/BinderTest.java new file mode 100644 index 0000000..c62c155 --- /dev/null +++ b/src/test/java/com/google/inject/BinderTest.java @@ -0,0 +1,633 @@ +/** + * Copyright (C) 2007 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; + +import static com.google.inject.Asserts.asModuleChain; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.assertNotSerializable; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static com.google.inject.Asserts.isIncludeStackTraceOff; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.spi.Message; +import com.google.inject.util.Providers; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Handler; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class BinderTest extends TestCase { + + //private final Logger loggerToWatch = Logger.getLogger(Guice.class.getName()); + + /*private final List logRecords = Lists.newArrayList(); + private final Handler fakeHandler = new Handler() { + @Override + public void publish(LogRecord logRecord) { + logRecords.add(logRecord); + } + @Override + public void flush() {} + @Override + public void close() throws SecurityException {} + };*/ + + Provider fooProvider; + + @Override protected void setUp() throws Exception { + super.setUp(); + //loggerToWatch.addHandler(fakeHandler); + } + + @Override protected void tearDown() throws Exception { + //loggerToWatch.removeHandler(fakeHandler); + super.tearDown(); + } + + public void testProviderFromBinder() { + Guice.createInjector(new Module() { + public void configure(Binder binder) { + fooProvider = binder.getProvider(Foo.class); + + try { + fooProvider.get(); + } catch (IllegalStateException e) { /* expected */ } + } + }); + + assertNotNull(fooProvider.get()); + } + + static class Foo {} + + public void testMissingBindings() { + try { + Guice.createInjector(new AbstractModule() { + @Override + public void configure() { + getProvider(Runnable.class); + bind(Comparator.class); + requireBinding(Key.get(new TypeLiteral>() {})); + bind(Date.class).annotatedWith(Names.named("date")); + } + }); + } catch (CreationException e) { + assertEquals(4, e.getErrorMessages().size()); + String segment1 = "No implementation for " + Comparator.class.getName() + " was bound."; + String segment2 = "No implementation for java.util.Date annotated with @" + + Named.class.getName() + "(value=date) was bound."; + String segment3 = "No implementation for java.lang.Runnable was bound."; + String segment4 = " No implementation for java.util.concurrent.Callable was" + + " bound."; + String atSegment = "at " + getClass().getName(); + String sourceFileName = getDeclaringSourcePart(getClass()); + if (isIncludeStackTraceOff()) { + assertContains(e.getMessage(), + segment1, atSegment, sourceFileName, + segment2, atSegment, sourceFileName, + segment3, atSegment, sourceFileName, + segment4, atSegment, sourceFileName); + } else { + assertContains(e.getMessage(), + segment3, atSegment, sourceFileName, + segment1, atSegment, sourceFileName, + segment4, atSegment, sourceFileName, + segment2, atSegment, sourceFileName); + } + } + } + + public void testMissingDependency() { + try { + Guice.createInjector(new AbstractModule() { + @Override + public void configure() { + bind(NeedsRunnable.class); + } + }); + } catch (CreationException e) { + assertEquals(1, e.getErrorMessages().size()); + assertContains(e.getMessage(), + "No implementation for java.lang.Runnable was bound.", + "for field at " + NeedsRunnable.class.getName(), ".runnable(", + "at " + getClass().getName(), getDeclaringSourcePart(getClass())); + } + } + + public static class NeedsRunnable { + @Inject Runnable runnable; + } + + public void testDanglingConstantBinding() { + try { + Guice.createInjector(new AbstractModule() { + @Override public void configure() { + bindConstant(); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Missing constant value. Please call to(...).", + "at " + getClass().getName()); + } + } + + public void testRecursiveBinding() { + try { + Guice.createInjector(new AbstractModule() { + @Override public void configure() { + bind(Runnable.class).to(Runnable.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Binding points to itself.", + "at " + getClass().getName(), getDeclaringSourcePart(getClass())); + } + } + + public void testBindingNullConstant() { + try { + Guice.createInjector(new AbstractModule() { + @Override public void configure() { + String none = null; + bindConstant().annotatedWith(Names.named("nullOne")).to(none); + bind(String.class).annotatedWith(Names.named("nullTwo")).toInstance(none); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Binding to null instances is not allowed. Use toProvider(Providers.of(null))", + "2) Binding to null instances is not allowed. Use toProvider(Providers.of(null))"); + } + } + + public void testToStringOnBinderApi() { + try { + Guice.createInjector(new AbstractModule() { + @Override public void configure() { + assertEquals("Binder", binder().toString()); + assertEquals("Provider", getProvider(Integer.class).toString()); + assertEquals("Provider>", + getProvider(Key.get(new TypeLiteral>() {})).toString()); + + assertEquals("BindingBuilder", + bind(Integer.class).toString()); + assertEquals("BindingBuilder", + bind(Integer.class).annotatedWith(Names.named("a")).toString()); + assertEquals("ConstantBindingBuilder", bindConstant().toString()); + assertEquals("ConstantBindingBuilder", + bindConstant().annotatedWith(Names.named("b")).toString()); + assertEquals("AnnotatedElementBuilder", + binder().newPrivateBinder().expose(Integer.class).toString()); + } + }); + fail(); + } catch (CreationException ignored) { + } + } + + public void testNothingIsSerializableInBinderApi() { + try { + Guice.createInjector(new AbstractModule() { + @Override public void configure() { + try { + assertNotSerializable(binder()); + assertNotSerializable(getProvider(Integer.class)); + assertNotSerializable(getProvider(Key.get(new TypeLiteral>() {}))); + assertNotSerializable(bind(Integer.class)); + assertNotSerializable(bind(Integer.class).annotatedWith(Names.named("a"))); + assertNotSerializable(bindConstant()); + assertNotSerializable(bindConstant().annotatedWith(Names.named("b"))); + } catch (IOException e) { + fail(e.getMessage()); + } + } + }); + fail(); + } catch (CreationException ignored) { + } + } + + /** + * Although {@code String[].class} isn't equal to {@code new + * GenericArrayTypeImpl(String.class)}, Guice should treat these two types + * interchangeably. + */ + public void testArrayTypeCanonicalization() { + final String[] strings = new String[] { "A" }; + final Integer[] integers = new Integer[] { 1 }; + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String[].class).toInstance(strings); + bind(new TypeLiteral() {}).toInstance(integers); + } + }); + + assertSame(integers, injector.getInstance(Key.get(new TypeLiteral() {}))); + assertSame(integers, injector.getInstance(new Key() {})); + assertSame(integers, injector.getInstance(Integer[].class)); + assertSame(strings, injector.getInstance(Key.get(new TypeLiteral() {}))); + assertSame(strings, injector.getInstance(new Key() {})); + assertSame(strings, injector.getInstance(String[].class)); + + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String[].class).toInstance(new String[] { "A" }); + bind(new TypeLiteral() {}).toInstance(new String[] { "B" }); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) A binding to java.lang.String[] was already configured at " + getClass().getName(), + "at " + getClass().getName(), getDeclaringSourcePart(getClass())); + assertContains(expected.getMessage(), "1 error"); + } + + // passes because duplicates are ignored + injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String[].class).toInstance(strings); + bind(new TypeLiteral() {}).toInstance(strings); + } + }); + assertSame(strings, injector.getInstance(Key.get(new TypeLiteral() {}))); + assertSame(strings, injector.getInstance(new Key() {})); + assertSame(strings, injector.getInstance(String[].class)); + } + + static class ParentModule extends AbstractModule { + @Override protected void configure() { + install(new FooModule()); + install(new BarModule()); + } + } + static class FooModule extends AbstractModule { + @Override protected void configure() { + install(new ConstantModule("foo")); + } + } + static class BarModule extends AbstractModule { + @Override protected void configure() { + install(new ConstantModule("bar")); + } + } + static class ConstantModule extends AbstractModule { + private final String constant; + ConstantModule(String constant) { + this.constant = constant; + } + @Override protected void configure() { + bind(String.class).toInstance(constant); + } + } + + /** + * Binding something to two different things should give an error. + */ + public void testSettingBindingTwice() { + try { + Guice.createInjector(new ParentModule()); + fail(); + } catch(CreationException expected) { + assertContains(expected.getMessage(), + "1) A binding to java.lang.String was already configured at " + ConstantModule.class.getName(), + asModuleChain(ParentModule.class, FooModule.class, ConstantModule.class), + "at " + ConstantModule.class.getName(), getDeclaringSourcePart(getClass()), + asModuleChain(ParentModule.class, BarModule.class, ConstantModule.class)); + assertContains(expected.getMessage(), "1 error"); + } + } + + /** + * Binding an @ImplementedBy thing to something else should also fail. + */ + public void testSettingAtImplementedByTwice() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(HasImplementedBy1.class); + bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {}); + } + }); + fail(); + } catch(CreationException expected) { + expected.printStackTrace(); + assertContains(expected.getMessage(), + "1) A binding to " + HasImplementedBy1.class.getName() + + " was already configured at " + getClass().getName(), + "at " + getClass().getName(), getDeclaringSourcePart(getClass())); + assertContains(expected.getMessage(), "1 error"); + } + } + + /** + * See issue 614, Problem One + * https://github.com/google/guice/issues/614 + */ + public void testJitDependencyDoesntBlockOtherExplicitBindings() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(HasImplementedByThatNeedsAnotherImplementedBy.class); + bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {}); + } + }); + injector.getAllBindings(); // just validate it doesn't throw. + // Also validate that we're using the explicit (and not @ImplementedBy) implementation + assertFalse(injector.getInstance(HasImplementedBy1.class) instanceof ImplementsHasImplementedBy1); + } + + /** + * See issue 614, Problem Two + * https://github.com/google/guice/issues/id=614 + */ + public void testJitDependencyCanUseExplicitDependencies() { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(HasImplementedByThatWantsExplicit.class); + bind(JustAnInterface.class).toInstance(new JustAnInterface() {}); + } + }); + } + + /** + * Untargetted bindings should follow @ImplementedBy and @ProvidedBy + * annotations if they exist. Otherwise the class should be constructed + * directly. + */ + public void testUntargettedBinding() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(HasProvidedBy1.class); + bind(HasImplementedBy1.class); + bind(HasProvidedBy2.class); + bind(HasImplementedBy2.class); + bind(JustAClass.class); + } + }); + + assertNotNull(injector.getInstance(HasProvidedBy1.class)); + assertNotNull(injector.getInstance(HasImplementedBy1.class)); + assertNotSame(HasProvidedBy2.class, + injector.getInstance(HasProvidedBy2.class).getClass()); + assertSame(ExtendsHasImplementedBy2.class, + injector.getInstance(HasImplementedBy2.class).getClass()); + assertSame(JustAClass.class, injector.getInstance(JustAClass.class).getClass()); + } + + public void testPartialInjectorGetInstance() { + Injector injector = Guice.createInjector(); + try { + injector.getInstance(MissingParameter.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "1) Could not find a suitable constructor in " + NoInjectConstructor.class.getName(), + "at " + MissingParameter.class.getName() + ".("); + } + } + + public void testUserReportedError() { + final Message message = new Message(getClass(), "Whoops!"); + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + addError(message); + } + }); + fail(); + } catch (CreationException expected) { + assertSame(message, Iterables.getOnlyElement(expected.getErrorMessages())); + } + } + + public void testUserReportedErrorsAreAlsoLogged() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + addError(new Message("Whoops!", new IllegalArgumentException())); + } + }); + fail(); + } catch (CreationException expected) { + } + + /*LogRecord logRecord = Iterables.getOnlyElement(this.logRecords); + assertContains(logRecord.getMessage(), + "An exception was caught and reported. Message: java.lang.IllegalArgumentException"); + */ + } + + public void testBindingToProvider() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).toInstance(Providers.of("A")); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Binding to Provider is not allowed.", + "at " + BinderTest.class.getName(), getDeclaringSourcePart(getClass())); + } + } + + static class OuterCoreModule extends AbstractModule { + @Override protected void configure() { + install(new InnerCoreModule()); + } + } + static class InnerCoreModule extends AbstractModule { + final Named red = Names.named("red"); + + @Override protected void configure() { + bind(AbstractModule.class).annotatedWith(red) + .toProvider(Providers.of(null)); + bind(Binder.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Binding.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Injector.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Key.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Module.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Provider.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Scope.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(Stage.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(TypeLiteral.class).annotatedWith(red).toProvider(Providers.of(null)); + bind(new TypeLiteral>() {}).toProvider(Providers.>of(null)); + } + } + public void testCannotBindToGuiceTypes() { + try { + Guice.createInjector(new OuterCoreModule()); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "Binding to core guice framework type is not allowed: AbstractModule.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Binder.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Binding.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Injector.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Key.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Module.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to Provider is not allowed.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Scope.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Stage.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: TypeLiteral.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class), + + "Binding to core guice framework type is not allowed: Key.", + "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterCoreModule.class, InnerCoreModule.class)); + } + } + + static class MissingParameter { + @Inject MissingParameter(NoInjectConstructor noInjectConstructor) {} + } + + static class NoInjectConstructor { + private NoInjectConstructor() {} + } + + @ProvidedBy(HasProvidedBy1Provider.class) + interface HasProvidedBy1 {} + + static class HasProvidedBy1Provider implements Provider { + public HasProvidedBy1 get() { + return new HasProvidedBy1() {}; + } + } + + @ImplementedBy(ImplementsHasImplementedBy1.class) + interface HasImplementedBy1 {} + + static class ImplementsHasImplementedBy1 implements HasImplementedBy1 {} + + @ProvidedBy(HasProvidedBy2Provider.class) + static class HasProvidedBy2 {} + + static class HasProvidedBy2Provider implements Provider { + public HasProvidedBy2 get() { + return new HasProvidedBy2() {}; + } + } + + @ImplementedBy(ExtendsHasImplementedBy2.class) + static class HasImplementedBy2 {} + + static class ExtendsHasImplementedBy2 extends HasImplementedBy2 {} + + static class JustAClass {} + + @ImplementedBy(ImplementsHasImplementedByThatNeedsAnotherImplementedBy.class) + static interface HasImplementedByThatNeedsAnotherImplementedBy { + } + + static class ImplementsHasImplementedByThatNeedsAnotherImplementedBy + implements HasImplementedByThatNeedsAnotherImplementedBy { + @Inject + ImplementsHasImplementedByThatNeedsAnotherImplementedBy( + HasImplementedBy1 h1n1) {} + } + + @ImplementedBy(ImplementsHasImplementedByThatWantsExplicit.class) + static interface HasImplementedByThatWantsExplicit { + } + + static class ImplementsHasImplementedByThatWantsExplicit + implements HasImplementedByThatWantsExplicit { + @Inject ImplementsHasImplementedByThatWantsExplicit(JustAnInterface jai) {} + } + + static interface JustAnInterface {} + + +// public void testBindInterfaceWithoutImplementation() { +// Guice.createInjector(new AbstractModule() { +// protected void configure() { +// bind(Runnable.class); +// } +// }).getInstance(Runnable.class); +// } + + enum Roshambo { ROCK, SCISSORS, PAPER } + + public void testInjectRawProvider() { + try { + Guice.createInjector().getInstance(Provider.class); + fail(); + } catch (ConfigurationException expected) { + Asserts.assertContains(expected.getMessage(), + "1) Cannot inject a Provider that has no type parameter", + "while locating " + Provider.class.getName()); + } + } +} diff --git a/src/test/java/com/google/inject/BinderTestSuite.java b/src/test/java/com/google/inject/BinderTestSuite.java new file mode 100644 index 0000000..75f11a0 --- /dev/null +++ b/src/test/java/com/google/inject/BinderTestSuite.java @@ -0,0 +1,798 @@ +/** + * Copyright (C) 2008 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.name.Names.named; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.ScopedBindingBuilder; +import com.google.inject.name.Named; +import com.google.inject.util.Providers; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class BinderTestSuite extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(); + + new Builder() + .name("bind A") + .module(new AbstractModule() { + protected void configure() { + bind(A.class); + } + }) + .creationException("No implementation for %s was bound", A.class.getName()) + .addToSuite(suite); + + new Builder() + .name("bind PlainA named apple") + .module(new AbstractModule() { + protected void configure() { + bind(PlainA.class).annotatedWith(named("apple")); + } + }) + .creationException("No implementation for %s annotated with %s was bound", + PlainA.class.getName(), named("apple")) + .addToSuite(suite); + + new Builder() + .name("bind A to new PlainA(1)") + .module(new AbstractModule() { + protected void configure() { + bind(A.class).toInstance(new PlainA(1)); + } + }) + .creationTime(CreationTime.NONE) + .expectedValues(new PlainA(1), new PlainA(1), new PlainA(1)) + .addToSuite(suite); + + new Builder() + .name("no binding, AWithProvidedBy") + .key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class) + .addToSuite(suite); + + new Builder() + .name("no binding, AWithImplementedBy") + .key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class) + .addToSuite(suite); + + new Builder() + .name("no binding, ScopedA") + .key(Key.get(ScopedA.class), InjectsScopedA.class) + .expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)) + .addToSuite(suite); + + new Builder() + .name("no binding, AWithProvidedBy named apple") + .key(Key.get(AWithProvidedBy.class, named("apple")), + InjectsAWithProvidedByNamedApple.class) + .configurationException("No implementation for %s annotated with %s was bound", + AWithProvidedBy.class.getName(), named("apple")) + .addToSuite(suite); + + new Builder() + .name("no binding, AWithImplementedBy named apple") + .key(Key.get(AWithImplementedBy.class, named("apple")), + InjectsAWithImplementedByNamedApple.class) + .configurationException("No implementation for %s annotated with %s was bound", + AWithImplementedBy.class.getName(), named("apple")) + .addToSuite(suite); + + new Builder() + .name("no binding, ScopedA named apple") + .key(Key.get(ScopedA.class, named("apple")), InjectsScopedANamedApple.class) + .configurationException("No implementation for %s annotated with %s was bound", + ScopedA.class.getName(), named("apple")) + .addToSuite(suite); + + for (final Scoper scoper : Scoper.values()) { + new Builder() + .name("bind PlainA") + .key(Key.get(PlainA.class), InjectsPlainA.class) + .module(new AbstractModule() { + protected void configure() { + AnnotatedBindingBuilder abb = bind(PlainA.class); + scoper.configure(abb); + } + }) + .scoper(scoper) + .addToSuite(suite); + + new Builder() + .name("bind A to PlainA") + .module(new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(A.class).to(PlainA.class); + scoper.configure(sbb); + } + }) + .scoper(scoper) + .addToSuite(suite); + + new Builder() + .name("bind A to PlainAProvider.class") + .module(new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(A.class).toProvider(PlainAProvider.class); + scoper.configure(sbb); + } + }) + .scoper(scoper) + .addToSuite(suite); + + new Builder() + .name("bind A to new PlainAProvider()") + .module(new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(A.class).toProvider(new PlainAProvider()); + scoper.configure(sbb); + } + }) + .scoper(scoper) + .addToSuite(suite); + + new Builder() + .name("bind AWithProvidedBy") + .key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class) + .module(new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(AWithProvidedBy.class); + scoper.configure(sbb); + } + }) + .scoper(scoper) + .addToSuite(suite); + + new Builder() + .name("bind AWithImplementedBy") + .key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class) + .module(new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(AWithImplementedBy.class); + scoper.configure(sbb); + } + }) + .scoper(scoper) + .addToSuite(suite); + + new Builder() + .name("bind ScopedA") + .key(Key.get(ScopedA.class), InjectsScopedA.class) + .module(new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(ScopedA.class); + scoper.configure(sbb); + } + }) + .expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)) + .scoper(scoper) + .addToSuite(suite); + + + new Builder() + .name("bind AWithProvidedBy named apple") + .module(new AbstractModule() { + protected void configure() { + scoper.configure(bind(AWithProvidedBy.class).annotatedWith(named("apple"))); + } + }) + .creationException("No implementation for %s annotated with %s was bound", + AWithProvidedBy.class.getName(), named("apple")) + .addToSuite(suite); + + new Builder() + .name("bind AWithImplementedBy named apple") + .module(new AbstractModule() { + protected void configure() { + scoper.configure(bind(AWithImplementedBy.class).annotatedWith(named("apple"))); + } + }) + .creationException("No implementation for %s annotated with %s was bound", + AWithImplementedBy.class.getName(), named("apple")) + .addToSuite(suite); + + new Builder() + .name("bind ScopedA named apple") + .module(new AbstractModule() { + protected void configure() { + scoper.configure(bind(ScopedA.class).annotatedWith(named("apple"))); + } + }) + .creationException("No implementation for %s annotated with %s was bound", + ScopedA.class.getName(), named("apple")) + .addToSuite(suite); + + } + + return suite; + } + + enum Scoper { + UNSCOPED { + void configure(ScopedBindingBuilder sbb) {} + void apply(Builder builder) {} + }, + + EAGER_SINGLETON { + void configure(ScopedBindingBuilder sbb) { + sbb.asEagerSingleton(); + } + void apply(Builder builder) { + builder.expectedValues(new PlainA(101), new PlainA(101), new PlainA(101)); + builder.creationTime(CreationTime.EAGER); + } + }, + + SCOPES_SINGLETON { + void configure(ScopedBindingBuilder sbb) { + sbb.in(Scopes.SINGLETON); + } + void apply(Builder builder) { + builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201)); + } + }, + + SINGLETON_DOT_CLASS { + void configure(ScopedBindingBuilder sbb) { + sbb.in(Singleton.class); + } + void apply(Builder builder) { + builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201)); + } + }, + + TWO_AT_A_TIME_SCOPED_DOT_CLASS { + void configure(ScopedBindingBuilder sbb) { + sbb.in(TwoAtATimeScoped.class); + } + void apply(Builder builder) { + builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)); + } + }, + + TWO_AT_A_TIME_SCOPE { + void configure(ScopedBindingBuilder sbb) { + sbb.in(new TwoAtATimeScope()); + } + void apply(Builder builder) { + builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)); + } + }; + + abstract void configure(ScopedBindingBuilder sbb); + abstract void apply(Builder builder); + } + + /** When Guice creates a value, directly or via a provider */ + enum CreationTime { + NONE, EAGER, LAZY + } + + public static class Builder { + private String name = "test"; + private Key key = Key.get(A.class); + private Class injectsKey = InjectsA.class; + private List modules = Lists.newArrayList(new AbstractModule() { + protected void configure() { + bindScope(TwoAtATimeScoped.class, new TwoAtATimeScope()); + } + }); + private List expectedValues = Lists.newArrayList( + new PlainA(201), new PlainA(202), new PlainA(203)); + private CreationTime creationTime = CreationTime.LAZY; + private String creationException; + private String configurationException; + + public Builder module(Module module) { + this.modules.add(module); + return this; + } + + public Builder creationTime(CreationTime creationTime) { + this.creationTime = creationTime; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder key(Key key, Class injectsKey) { + this.key = key; + this.injectsKey = injectsKey; + return this; + } + + private Builder creationException(String message, Object... args) { + this.creationException = String.format(message, args); + return this; + } + + private Builder configurationException(String message, Object... args) { + configurationException = String.format(message, args); + return this; + } + + private Builder scoper(Scoper scoper) { + name(name + " in " + scoper); + scoper.apply(this); + return this; + } + + private Builder expectedValues(T... values) { + this.expectedValues.clear(); + Collections.addAll(this.expectedValues, values); + return this; + } + + public void addToSuite(TestSuite suite) { + if (creationException != null) { + suite.addTest(new CreationExceptionTest().set(this)); + + } else if (configurationException != null) { + suite.addTest(new ConfigurationExceptionTest().set(this)); + + } else { + suite.addTest(new SuccessTest().set(this)); + //if (creationTime != CreationTime.NONE) { + // suite.addTest(new UserExceptionsTest().set(this)); + //} + } + } + } + + public static class SuccessTest extends TestCase { + String name; + Key key; + Class injectsKey; + ImmutableList modules; + ImmutableList expectedValues; + + public SuccessTest() { + super("test"); + } + + public SuccessTest set(Builder builder) { + name = builder.name; + key = builder.key; + injectsKey = builder.injectsKey; + modules = ImmutableList.copyOf(builder.modules); + expectedValues = ImmutableList.copyOf(builder.expectedValues); + return this; + } + + public String getName() { + return name; + } + + Injector newInjector() { + nextId.set(101); + return Guice.createInjector(modules); + } + + public void test() throws IllegalAccessException, InstantiationException { + if (expectedValues == null) { + return; + } + Injector injector = newInjector(); + nextId.set(201); + for (Object value : expectedValues) { + assertEquals(value, injector.getInstance(key)); + } + + Provider provider = newInjector().getProvider(key); + nextId.set(201); + for (Object value : expectedValues) { + assertEquals(value, provider.get()); + } + + Provider bindingProvider = newInjector().getBinding(key).getProvider(); + nextId.set(201); + for (Object value : expectedValues) { + assertEquals(value, bindingProvider.get()); + } + + injector = newInjector(); + nextId.set(201); + for (Object value : expectedValues) { + Injectable instance = injector.getInstance(injectsKey); + assertEquals(value, instance.value); + } + + injector = newInjector(); + nextId.set(201); + for (Object value : expectedValues) { + Injectable injectable = injectsKey.newInstance(); + injector.injectMembers(injectable); + assertEquals(value, injectable.value); + } + + Injector injector1 = newInjector(); + nextId.set(201); + Injectable hasProvider = injector1.getInstance(injectsKey); + hasProvider.provider.get(); + nextId.set(201); + for (Object value : expectedValues) { + assertEquals(value, hasProvider.provider.get()); + } + } + } + + public static class CreationExceptionTest extends TestCase { + String name; + Key key; + ImmutableList modules; + String creationException; + + public CreationExceptionTest() { + super("test"); + } + + public CreationExceptionTest set(Builder builder) { + name = builder.name; + key = builder.key; + modules = ImmutableList.copyOf(builder.modules); + creationException = builder.creationException; + return this; + } + + public String getName() { + return "creation errors:" + name; + } + + public void test() { + if (modules == null) { + return; + } + try { + Guice.createInjector(modules); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), creationException); + } + } + } + + public static class ConfigurationExceptionTest extends TestCase { + String name; + Key key; + Class injectsKey; + ImmutableList modules; + String configurationException; + + public ConfigurationExceptionTest() { + super("test"); + } + + public ConfigurationExceptionTest set(Builder builder) { + name = builder.name; + key = builder.key; + injectsKey = builder.injectsKey; + modules = ImmutableList.copyOf(builder.modules); + configurationException = builder.configurationException; + return this; + } + + public String getName() { + return "provision errors:" + name; + } + + Injector newInjector() { + return Guice.createInjector(modules); + } + + public void test() throws IllegalAccessException, InstantiationException { + if (key == null) { + return; + } + + try { + newInjector().getProvider(key); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), configurationException); + } + + try { + newInjector().getBinding(key).getProvider(); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), configurationException); + } + + try { + newInjector().getInstance(key); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), configurationException); + } + + try { + newInjector().getInstance(injectsKey); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + configurationException, injectsKey.getName() + ".inject", + configurationException, injectsKey.getName() + ".inject", + "2 errors"); + } + + try { + Injectable injectable = injectsKey.newInstance(); + newInjector().injectMembers(injectable); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + configurationException, injectsKey.getName() + ".inject", + configurationException, injectsKey.getName() + ".inject", + "2 errors"); + } + } + } + + public static class UserExceptionsTest extends TestCase { + String name; + Key key; + Class injectsKey; + ImmutableList modules; + ImmutableList expectedValues; + CreationTime creationTime; + + public UserExceptionsTest() { + super("test"); + } + + public UserExceptionsTest set(Builder builder) { + name = builder.name; + key = builder.key; + injectsKey = builder.injectsKey; + modules = ImmutableList.copyOf(builder.modules); + expectedValues = ImmutableList.copyOf(builder.expectedValues); + creationTime = builder.creationTime; + return this; + } + + public String getName() { + return "provision errors:" + name; + } + + Injector newInjector() { + return Guice.createInjector(modules); + } + + public void test() throws IllegalAccessException, InstantiationException { + if (creationTime == null) { + return; + } + nextId.set(-1); + try { + newInjector(); + assertEquals(CreationTime.LAZY, creationTime); + } catch (CreationException expected) { + assertEquals(CreationTime.EAGER, creationTime); + assertContains(expected.getMessage(), "Illegal value: -1"); + return; + } + + Provider provider = newInjector().getProvider(key); + Provider bindingProvider = newInjector().getBinding(key).getProvider(); + + nextId.set(-1); + try { + newInjector().getInstance(key); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), "Illegal value: -1"); + } + + nextId.set(-1); + try { + provider.get(); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), "Illegal value: -1"); + } + + nextId.set(-1); + try { + bindingProvider.get(); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), "Illegal value: -1"); + } + + try { + nextId.set(-1); + newInjector().getInstance(injectsKey); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), "Illegal value: -1", + "for parameter 0 at " + injectsKey.getName() + ".inject"); + } + + nextId.set(201); + Injectable injectable = injectsKey.newInstance(); + try { + nextId.set(-1); + newInjector().injectMembers(injectable); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), "Illegal value: -1", + "for parameter 0 at " + injectsKey.getName() + ".inject"); + } + + nextId.set(201); + Injectable hasProvider = newInjector().getInstance(injectsKey); + hasProvider.provider.get(); + try { + nextId.set(-1); + hasProvider.provider.get(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), "Illegal value: -1"); + } + } + } + + /** negative to throw, 101... for eager singletons, 201... for everything else */ + static final AtomicInteger nextId = new AtomicInteger(); + + @ProvidedBy(PlainAProvider.class) + interface AWithProvidedBy {} + + static class InjectsAWithProvidedBy extends Injectable { + @Inject public void inject(AWithProvidedBy aWithProvidedBy, + Provider aWithProvidedByProvider) { + this.value = aWithProvidedBy; + this.provider = aWithProvidedByProvider; + } + } + + static class InjectsAWithProvidedByNamedApple extends Injectable { + @Inject public void inject(@Named("apple") AWithProvidedBy aWithProvidedBy, + @Named("apple") Provider aWithProvidedByProvider) { + this.value = aWithProvidedBy; + this.provider = aWithProvidedByProvider; + } + } + + @ImplementedBy(PlainA.class) + interface AWithImplementedBy {} + + static class InjectsAWithImplementedBy extends Injectable { + @Inject public void inject(AWithImplementedBy aWithImplementedBy, + Provider aWithImplementedByProvider) { + this.value = aWithImplementedBy; + this.provider = aWithImplementedByProvider; + } + } + + static class InjectsAWithImplementedByNamedApple extends Injectable { + @Inject public void inject(@Named("apple") AWithImplementedBy aWithImplementedBy, + @Named("apple") Provider aWithImplementedByProvider) { + this.value = aWithImplementedBy; + this.provider = aWithImplementedByProvider; + } + } + + interface A extends AWithProvidedBy, AWithImplementedBy {} + + static class InjectsA extends Injectable { + @Inject public void inject(A a, Provider aProvider) { + this.value = a; + this.provider = aProvider; + } + } + + static class PlainA implements A { + final int value; + PlainA() { + value = nextId.getAndIncrement(); + if (value < 0) { + throw new RuntimeException("Illegal value: " + value); + } + } + PlainA(int value) { + this.value = value; + } + public boolean equals(Object obj) { + return obj instanceof PlainA + && value == ((PlainA) obj).value; + } + public int hashCode() { + return value; + } + public String toString() { + return "PlainA#" + value; + } + } + + static class PlainAProvider implements Provider { + public A get() { + return new PlainA(); + } + } + + static class InjectsPlainA extends Injectable { + @Inject public void inject(PlainA plainA, Provider plainAProvider) { + this.value = plainA; + this.provider = plainAProvider; + } + } + + /** This scope hands out each value exactly twice */ + static class TwoAtATimeScope implements Scope { + public Provider scope(Key key, final Provider unscoped) { + return new Provider() { + T instance; + public T get() { + if (instance == null) { + instance = unscoped.get(); + return instance; + } else { + T result = instance; + instance = null; + return result; + } + } + }; + } + } + + @Target({ TYPE, METHOD }) @Retention(RUNTIME) @ScopeAnnotation + public @interface TwoAtATimeScoped {} + + @TwoAtATimeScoped + static class ScopedA extends PlainA {} + + static class InjectsScopedA extends Injectable { + @Inject public void inject(ScopedA scopedA, Provider scopedAProvider) { + this.value = scopedA; + this.provider = scopedAProvider; + } + } + + static class InjectsScopedANamedApple extends Injectable { + @Inject public void inject(@Named("apple") ScopedA scopedA, + @Named("apple") Provider scopedAProvider) { + this.value = scopedA; + this.provider = scopedAProvider; + } + } + + static class Injectable { + Object value = new Object(); + Provider provider = Providers.of(new Object()); + } +} diff --git a/src/test/java/com/google/inject/BindingAnnotationTest.java b/src/test/java/com/google/inject/BindingAnnotationTest.java new file mode 100644 index 0000000..b119953 --- /dev/null +++ b/src/test/java/com/google/inject/BindingAnnotationTest.java @@ -0,0 +1,169 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class BindingAnnotationTest extends TestCase { + + public void testAnnotationWithValueMatchesKeyWithTypeOnly() throws CreationException { + Injector c = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(Blue.class).to("foo"); + bind(BlueFoo.class); + } + }); + + BlueFoo foo = c.getInstance(BlueFoo.class); + + assertEquals("foo", foo.s); + } + + public void testRequireExactAnnotationsDisablesFallback() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExactBindingAnnotations(); + bindConstant().annotatedWith(Blue.class).to("foo"); + bind(BlueFoo.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "No implementation for java.lang.String annotated with", + "BindingAnnotationTest$Blue(value=5) was bound", + "at " + BindingAnnotationTest.class.getName(), + getDeclaringSourcePart(getClass())); + } + } + + public void testRequireExactAnnotationsDoesntBreakIfDefaultsExist() { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExactBindingAnnotations(); + bindConstant().annotatedWith(Red.class).to("foo"); + bind(RedFoo.class); + } + }).getInstance(RedFoo.class); + } + + public void testRequireExactAnnotationsRequireAllOptionals() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExactBindingAnnotations(); + bindConstant().annotatedWith(Color.class).to("foo"); + bind(ColorFoo.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "No implementation for java.lang.String annotated with", + "BindingAnnotationTest$Color", + "at " + BindingAnnotationTest.class.getName(), + getDeclaringSourcePart(getClass())); + } + } + + public void testAnnotationWithValueThatDoesntMatch() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(createBlue(6)).to("six"); + bind(String.class).toInstance("bar"); + bind(BlueFoo.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "No implementation for java.lang.String annotated with", + "BindingAnnotationTest$Blue(value=5) was bound", + "at " + BindingAnnotationTest.class.getName(), + getDeclaringSourcePart(getClass())); + } + } + + static class BlueFoo { + @Inject @Blue(5) String s; + } + + static class RedFoo { + @Inject @Red String s; + } + + static class ColorFoo { + @Inject @Color(b=2) String s; + } + + @Retention(RUNTIME) + @BindingAnnotation + @interface Blue { + int value(); + } + + @Retention(RUNTIME) + @BindingAnnotation + @interface Red { + int r() default 42; + int g() default 42; + int b() default 42; + } + + @Retention(RUNTIME) + @BindingAnnotation + @interface Color { + int r() default 0; + int g() default 0; + int b(); + } + + public Blue createBlue(final int value) { + return new Blue() { + public int value() { + return value; + } + + public Class annotationType() { + return Blue.class; + } + + @Override public boolean equals(Object o) { + return o instanceof Blue + && ((Blue) o).value() == value; + } + + @Override public int hashCode() { + return 127 * "value".hashCode() ^ value; + } + }; + } +} diff --git a/src/test/java/com/google/inject/BindingOrderTest.java b/src/test/java/com/google/inject/BindingOrderTest.java new file mode 100644 index 0000000..6754cad --- /dev/null +++ b/src/test/java/com/google/inject/BindingOrderTest.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2006 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; + + +import junit.framework.TestCase; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ + +public class BindingOrderTest extends TestCase { + + public void testBindingOutOfOrder() { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(BoundFirst.class); + bind(BoundSecond.class).to(BoundSecondImpl.class); + } + }); + } + + public static class BoundFirst { + @Inject public BoundFirst(BoundSecond boundSecond) { } + } + + interface BoundSecond { } + static class BoundSecondImpl implements BoundSecond { } + + public void testBindingOrderAndScopes() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class); + bind(B.class).asEagerSingleton(); + } + }); + + assertSame(injector.getInstance(A.class).b, injector.getInstance(A.class).b); + } + + public void testBindingWithExtraThreads() throws InterruptedException { + final CountDownLatch ready = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + final AtomicReference ref = new AtomicReference(); + + final Object createsAThread = new Object() { + @Inject void createAnotherThread(final Injector injector) { + new Thread() { + public void run() { + ready.countDown(); + A a = injector.getInstance(A.class); + ref.set(a.b); + done.countDown(); + } + }.start(); + + // to encourage collisions, we make sure the other thread is running before returning + try { + ready.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }; + + Guice.createInjector(new AbstractModule() { + protected void configure() { + requestInjection(createsAThread); + bind(A.class).toInstance(new A()); + } + }); + + done.await(); + assertNotNull(ref.get()); + } + + static class A { + @Inject B b; + } + + static class B { } +} diff --git a/src/test/java/com/google/inject/BindingTest.java b/src/test/java/com/google/inject/BindingTest.java new file mode 100644 index 0000000..e574b90 --- /dev/null +++ b/src/test/java/com/google/inject/BindingTest.java @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2007 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.name.Names.named; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Runnables; +import com.google.inject.matcher.Matchers; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; + +import junit.framework.TestCase; + +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class BindingTest extends TestCase { + + static class Dependent { + @Inject A a; + @Inject Dependent(A a, B b) {} + @Inject void injectBob(Bob bob) {} + } + + public void testExplicitCyclicDependency() { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class); + bind(B.class); + } + }).getInstance(A.class); + } + + static class A { @Inject B b; } + static class B { @Inject A a; } + + static class Bob {} + + static class MyModule extends AbstractModule { + + protected void configure() { + // Linked. + bind(Object.class).to(Runnable.class).in(Scopes.SINGLETON); + + // Instance. + bind(Runnable.class).toInstance(Runnables.doNothing()); + + // Provider instance. + bind(Foo.class).toProvider(new Provider() { + public Foo get() { + return new Foo(); + } + }).in(Scopes.SINGLETON); + + // Provider. + bind(Foo.class) + .annotatedWith(named("provider")) + .toProvider(FooProvider.class); + + // Class. + bind(Bar.class).in(Scopes.SINGLETON); + + // Constant. + bindConstant().annotatedWith(named("name")).to("Bob"); + } + } + + static class Foo {} + + public static class FooProvider implements Provider { + public Foo get() { + throw new UnsupportedOperationException(); + } + } + + public static class Bar {} + + public void testBindToUnboundLinkedBinding() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Collection.class).to(List.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "No implementation for java.util.List was bound."); + } + } + + /** + * This test ensures that the asEagerSingleton() scoping applies to the key, + * not to what the key is linked to. + */ + public void testScopeIsAppliedToKeyNotTarget() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Integer.class).toProvider(Counter.class).asEagerSingleton(); + bind(Number.class).toProvider(Counter.class).asEagerSingleton(); + } + }); + + assertNotSame(injector.getInstance(Integer.class), injector.getInstance(Number.class)); + } + + static class Counter implements Provider { + static AtomicInteger next = new AtomicInteger(1); + public Integer get() { + return next.getAndIncrement(); + } + } + + public void testAnnotatedNoArgConstructor() { + assertBindingSucceeds(PublicNoArgAnnotated.class); + assertBindingSucceeds(ProtectedNoArgAnnotated.class); + assertBindingSucceeds(PackagePrivateNoArgAnnotated.class); + assertBindingSucceeds(PrivateNoArgAnnotated.class); + } + + static class PublicNoArgAnnotated { + @Inject public PublicNoArgAnnotated() { } + } + + static class ProtectedNoArgAnnotated { + @Inject protected ProtectedNoArgAnnotated() { } + } + + static class PackagePrivateNoArgAnnotated { + @Inject PackagePrivateNoArgAnnotated() { } + } + + static class PrivateNoArgAnnotated { + @Inject private PrivateNoArgAnnotated() { } + } + + public void testUnannotatedNoArgConstructor() throws Exception{ + assertBindingSucceeds(PublicNoArg.class); + assertBindingSucceeds(ProtectedNoArg.class); + assertBindingSucceeds(PackagePrivateNoArg.class); + assertBindingSucceeds(PrivateNoArgInPrivateClass.class); + assertBindingFails(PrivateNoArg.class); + } + + static class PublicNoArg { + public PublicNoArg() { } + } + + static class ProtectedNoArg { + protected ProtectedNoArg() { } + } + + static class PackagePrivateNoArg { + PackagePrivateNoArg() { } + } + + private static class PrivateNoArgInPrivateClass { + PrivateNoArgInPrivateClass() { } + } + + static class PrivateNoArg { + private PrivateNoArg() { } + } + + private void assertBindingSucceeds(final Class clazz) { + assertNotNull(Guice.createInjector().getInstance(clazz)); + } + + private void assertBindingFails(final Class clazz) throws NoSuchMethodException { + try { + Guice.createInjector().getInstance(clazz); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "Could not find a suitable constructor in " + PrivateNoArg.class.getName(), + "at " + PrivateNoArg.class.getName() + ".class("); + } + } + + public void testTooManyConstructors() { + try { + Guice.createInjector().getInstance(TooManyConstructors.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + TooManyConstructors.class.getName() + " has more than one constructor annotated with " + + "@Inject. Classes must have either one (and only one) constructor", + "at " + TooManyConstructors.class.getName() + ".class("); + } + } + + static class TooManyConstructors { + @Inject TooManyConstructors(Injector i) {} + @Inject TooManyConstructors() {} + } + + public void testToConstructorBinding() throws NoSuchMethodException { + final Constructor constructor = D.class.getConstructor(Stage.class); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Object.class).toConstructor(constructor); + } + }); + + D d = (D) injector.getInstance(Object.class); + assertEquals(Stage.DEVELOPMENT, d.stage); + } + + public void testToConstructorBindingsOnParameterizedTypes() throws NoSuchMethodException { + final Constructor constructor = C.class.getConstructor(Stage.class, Object.class); + final Key s = new Key(named("s")) {}; + final Key i = new Key(named("i")) {}; + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(s).toConstructor(constructor, new TypeLiteral>() {}); + bind(i).toConstructor(constructor, new TypeLiteral>() {}); + } + }); + + C one = (C) injector.getInstance(s); + assertEquals(Stage.DEVELOPMENT, one.stage); + assertEquals(Stage.DEVELOPMENT, one.t); + assertEquals(Stage.DEVELOPMENT, one.anotherT); + + C two = (C) injector.getInstance(i); + assertEquals(Stage.DEVELOPMENT, two.stage); + assertEquals(injector, two.t); + assertEquals(injector, two.anotherT); + } + + public void testToConstructorBindingsFailsOnRawTypes() throws NoSuchMethodException { + final Constructor constructor = C.class.getConstructor(Stage.class, Object.class); + + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Object.class).toConstructor(constructor); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) T cannot be used as a key; It is not fully specified.", + "at " + C.class.getName() + ".(", + "2) T cannot be used as a key; It is not fully specified.", + "at " + C.class.getName() + ".anotherT("); + } + } + + public void testInaccessibleConstructor() throws NoSuchMethodException { + final Constructor constructor = E.class.getDeclaredConstructor(Stage.class); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(E.class).toConstructor(constructor); + } + }); + + E e = injector.getInstance(E.class); + assertEquals(Stage.DEVELOPMENT, e.stage); + } + + public void testToConstructorAndScopes() throws NoSuchMethodException { + final Constructor constructor = F.class.getConstructor(Stage.class); + + final Key d = Key.get(Object.class, named("D")); // default scoping + final Key s = Key.get(Object.class, named("S")); // singleton + final Key n = Key.get(Object.class, named("N")); // "N" instances + final Key r = Key.get(Object.class, named("R")); // a regular binding + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(d).toConstructor(constructor); + bind(s).toConstructor(constructor).in(Singleton.class); + bind(n).toConstructor(constructor).in(Scopes.NO_SCOPE); + bind(r).to(F.class); + } + }); + + assertDistinct(injector, 1, d, d, d, d); + assertDistinct(injector, 1, s, s, s, s); + assertDistinct(injector, 4, n, n, n, n); + assertDistinct(injector, 1, r, r, r, r); + assertDistinct(injector, 4, d, d, r, r, s, s, n); + } + + public void assertDistinct(Injector injector, int expectedCount, Key... keys) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (Key k : keys) { + builder.add(injector.getInstance(k)); + } + assertEquals(expectedCount, builder.build().size()); + } + + public void testToConstructorSpiData() throws NoSuchMethodException { + final Set> heardTypes = Sets.newHashSet(); + + final Constructor constructor = D.class.getConstructor(Stage.class); + final TypeListener listener = new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + if (!heardTypes.add(type)) { + fail("Heard " + type + " multiple times!"); + } + } + }; + + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Object.class).toConstructor(constructor); + bind(D.class).toConstructor(constructor); + bindListener(Matchers.any(), listener); + } + }); + + assertEquals(ImmutableSet.of(TypeLiteral.get(D.class)), heardTypes); + } + + public void testInterfaceToImplementationConstructor() throws NoSuchMethodException { + final Constructor constructor = CFoo.class.getDeclaredConstructor(); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(IFoo.class).toConstructor(constructor); + } + }); + + injector.getInstance(IFoo.class); + } + + public static interface IFoo {} + public static class CFoo implements IFoo {} + + public void testGetAllBindings() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(D.class).toInstance(new D(Stage.PRODUCTION)); + bind(Object.class).to(D.class); + getProvider(new Key>() {}); + } + }); + + Map,Binding> bindings = injector.getAllBindings(); + assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class), + Key.get(Object.class), new Key>() {}), + bindings.keySet()); + + // add a JIT binding + injector.getInstance(F.class); + + Map,Binding> bindings2 = injector.getAllBindings(); + assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class), + Key.get(Object.class), new Key>() {}, Key.get(F.class)), + bindings2.keySet()); + + // the original map shouldn't have changed + assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class), + Key.get(Object.class), new Key>() {}), + bindings.keySet()); + + // check the bindings' values + assertEquals(injector, bindings.get(Key.get(Injector.class)).getProvider().get()); + } + + public void testGetAllServletBindings() throws Exception { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(F.class); // an explicit binding that uses a JIT binding for a constructor + } + }); + injector.getAllBindings(); + } + + public static class C { + private Stage stage; + private T t; + @Inject T anotherT; + + public C(Stage stage, T t) { + this.stage = stage; + this.t = t; + } + + @Inject C() {} + } + + public static class D { + Stage stage; + public D(Stage stage) { + this.stage = stage; + } + } + + private static class E { + Stage stage; + private E(Stage stage) { + this.stage = stage; + } + } + + @Singleton + public static class F { + Stage stage; + @Inject public F(Stage stage) { + this.stage = stage; + } + } + + public void testTurkeyBaconProblemUsingToConstuctor() { + Injector injector = Guice.createInjector(new AbstractModule() { + @SuppressWarnings("unchecked") + @Override + public void configure() { + bind(Bacon.class).to(UncookedBacon.class); + bind(Bacon.class).annotatedWith(named("Turkey")).to(TurkeyBacon.class); + bind(Bacon.class).annotatedWith(named("Cooked")).toConstructor( + (Constructor)InjectionPoint.forConstructorOf(Bacon.class).getMember()); + } + }); + Bacon bacon = injector.getInstance(Bacon.class); + assertEquals(Food.PORK, bacon.getMaterial()); + assertFalse(bacon.isCooked()); + + Bacon turkeyBacon = injector.getInstance(Key.get(Bacon.class, named("Turkey"))); + assertEquals(Food.TURKEY, turkeyBacon.getMaterial()); + assertTrue(turkeyBacon.isCooked()); + + Bacon cookedBacon = injector.getInstance(Key.get(Bacon.class, named("Cooked"))); + assertEquals(Food.PORK, cookedBacon.getMaterial()); + assertTrue(cookedBacon.isCooked()); + } + + enum Food { TURKEY, PORK } + + private static class Bacon { + public Food getMaterial() { return Food.PORK; } + public boolean isCooked() { return true; } + } + + private static class TurkeyBacon extends Bacon { + public Food getMaterial() { return Food.TURKEY; } + } + + private static class UncookedBacon extends Bacon { + public boolean isCooked() { return false; } + } +} diff --git a/src/test/java/com/google/inject/BoundInstanceInjectionTest.java b/src/test/java/com/google/inject/BoundInstanceInjectionTest.java new file mode 100644 index 0000000..59fe8b2 --- /dev/null +++ b/src/test/java/com/google/inject/BoundInstanceInjectionTest.java @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2006 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; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.name.Named; + +import junit.framework.TestCase; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class BoundInstanceInjectionTest extends TestCase { + + public void testInstancesAreInjected() throws CreationException { + final O o = new O(); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(O.class).toInstance(o); + bind(int.class).toInstance(5); + } + }); + + assertEquals(5, o.fromMethod); + } + + static class O { + int fromMethod; + @Inject + void setInt(int i) { + this.fromMethod = i; + } + } + + public void testProvidersAreInjected() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(O.class).toProvider(new Provider() { + @Inject int i; + public O get() { + O o = new O(); + o.setInt(i); + return o; + } + }); + bind(int.class).toInstance(5); + } + }); + + assertEquals(5, injector.getInstance(O.class).fromMethod); + } + + public void testMalformedInstance() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Object.class).toInstance(new MalformedInjectable()); + } + }); + fail(); + } catch (CreationException expected) { + Asserts.assertContains(expected.getMessage(), MalformedInjectable.class.getName(), + ".doublyAnnotated() has more than one ", "annotation annotated with @BindingAnnotation: ", + Named.class.getName() + " and " + Another.class.getName()); + } + } + + public void testMalformedProvider() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toProvider(new MalformedProvider()); + } + }); + fail(); + } catch (CreationException expected) { + Asserts.assertContains(expected.getMessage(), MalformedProvider.class.getName(), + ".doublyAnnotated() has more than one ", "annotation annotated with @BindingAnnotation: ", + Named.class.getName() + " and " + Another.class.getName()); + } + } + + static class MalformedInjectable { + @Inject void doublyAnnotated(@Named("a") @Another String unused) {} + } + + static class MalformedProvider implements Provider { + @Inject void doublyAnnotated(@Named("a") @Another String s) {} + + public String get() { + return "a"; + } + } + + @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) + public @interface Another {} +} diff --git a/src/test/java/com/google/inject/BoundProviderTest.java b/src/test/java/com/google/inject/BoundProviderTest.java new file mode 100644 index 0000000..5fc46f4 --- /dev/null +++ b/src/test/java/com/google/inject/BoundProviderTest.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2006 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; + +import junit.framework.TestCase; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class BoundProviderTest extends TestCase { + + public void testFooProvider() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toProvider(FooProvider.class); + } + }); + + Foo a = injector.getInstance(Foo.class); + Foo b = injector.getInstance(Foo.class); + + assertEquals(0, a.i); + assertEquals(0, b.i); + assertNotNull(a.bar); + assertNotNull(b.bar); + assertNotSame(a.bar, b.bar); + } + + public void testSingletonFooProvider() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toProvider(SingletonFooProvider.class); + } + }); + + Foo a = injector.getInstance(Foo.class); + Foo b = injector.getInstance(Foo.class); + + assertEquals(0, a.i); + assertEquals(1, b.i); + assertNotNull(a.bar); + assertNotNull(b.bar); + assertSame(a.bar, b.bar); + } + + static class Bar {} + + static class Foo { + final Bar bar; + final int i; + + Foo(Bar bar, int i) { + this.bar = bar; + this.i = i; + } + } + + static class FooProvider implements Provider { + + final Bar bar; + int count = 0; + + @Inject + public FooProvider(Bar bar) { + this.bar = bar; + } + + public Foo get() { + return new Foo(this.bar, count++); + } + } + + @Singleton + static class SingletonFooProvider implements Provider { + + final Bar bar; + int count = 0; + + @Inject + public SingletonFooProvider(Bar bar) { + this.bar = bar; + } + + public Foo get() { + return new Foo(this.bar, count++); + } + } +} diff --git a/src/test/java/com/google/inject/CircularDependencyTest.java b/src/test/java/com/google/inject/CircularDependencyTest.java new file mode 100644 index 0000000..2b67f7b --- /dev/null +++ b/src/test/java/com/google/inject/CircularDependencyTest.java @@ -0,0 +1,598 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.assertContains; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + +import junit.framework.TestCase; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author crazybob@google.com (Bob Lee) + * @author sameb@google.com (Sam Berlin) + */ +public class CircularDependencyTest extends TestCase { + + @Override + protected void setUp() throws Exception { + AImpl.nextId = 0; + BImpl.nextId = 0; + } + + public void testCircularlyDependentConstructors() + throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class).to(AImpl.class); + bind(B.class).to(BImpl.class); + } + }); + assertCircularDependencies(injector); + } + + public void testCircularlyDependentConstructorsWithProviderMethods() + throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() {} + + @Provides @Singleton A a(B b) { return new AImpl(b); } + @Provides B b(A a) { return new BImpl(a); } + }); + assertCircularDependencies(injector); + } + + public void testCircularlyDependentConstructorsWithProviderInstances() + throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class).toProvider(new Provider() { + @Inject Provider bp; + public A get() { + return new AImpl(bp.get()); + } + }).in(Singleton.class); + bind(B.class).toProvider(new Provider() { + @Inject Provider ap; + public B get() { + return new BImpl(ap.get()); + } + }); + } + }); + assertCircularDependencies(injector); + } + + public void testCircularlyDependentConstructorsWithProviderKeys() + throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class).toProvider(AP.class).in(Singleton.class); + bind(B.class).toProvider(BP.class); + } + }); + assertCircularDependencies(injector); + } + + public void testCircularlyDependentConstructorsWithProvidedBy() + throws CreationException { + Injector injector = Guice.createInjector(); + assertCircularDependencies(injector); + } + + private void assertCircularDependencies(Injector injector) { + A a = injector.getInstance(A.class); + assertNotNull(a.getB().getA()); + assertEquals(0, a.id()); + assertEquals(a.id(), a.getB().getA().id()); + assertEquals(0, a.getB().id()); + assertEquals(1, AImpl.nextId); + assertEquals(1, BImpl.nextId); + assertSame(a, injector.getInstance(A.class)); + } + + @ProvidedBy(AutoAP.class) + public interface A { + B getB(); + int id(); + } + + @Singleton + static class AImpl implements A { + static int nextId; + int id = nextId++; + + final B b; + @Inject public AImpl(B b) { + this.b = b; + } + public int id() { + return id; + } + public B getB() { + return b; + } + } + + static class AP implements Provider { + @Inject Provider bp; + public A get() { + return new AImpl(bp.get()); + } + } + + @Singleton + static class AutoAP implements Provider { + @Inject Provider bp; + A a; + + public A get() { + if (a == null) { + a = new AImpl(bp.get()); + } + return a; + } + } + + @ProvidedBy(BP.class) + public interface B { + A getA(); + int id(); + } + + static class BImpl implements B { + static int nextId; + int id = nextId++; + + final A a; + @Inject public BImpl(A a) { + this.a = a; + } + public int id() { + return id; + } + public A getA() { + return a; + } + } + + static class BP implements Provider { + Provider ap; + @Inject BP(Provider ap) { + this.ap = ap; + } + public B get() { + return new BImpl(ap.get()); + } + } + + public void testUnresolvableCircularDependency() { + try { + Guice.createInjector().getInstance(C.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C.class.getName() + " to support a circular dependency, ", + "but it is not an interface."); + } + } + + public void testUnresolvableCircularDependenciesWithProviderInstances() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() {} + @Provides C c(D d) { return null; } + @Provides D d(C c) { return null; } + }).getInstance(C.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C.class.getName() + " to support a circular dependency, ", + "but it is not an interface."); + } + } + + public void testUnresolvableCircularDependenciesWithProviderKeys() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(C2.class).toProvider(C2P.class); + bind(D2.class).toProvider(D2P.class); + } + }).getInstance(C2.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", + "but it is not an interface."); + } + } + + public void testUnresolvableCircularDependenciesWithProvidedBy() { + try { + Guice.createInjector().getInstance(C2.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", + "but it is not an interface."); + } + } + + static class C { + @Inject C(D d) {} + } + static class D { + @Inject D(C c) {} + } + + static class C2P implements Provider { + @Inject Provider dp; + public C2 get() { + dp.get(); + return null; + } + } + static class D2P implements Provider { + @Inject Provider cp; + public D2 get() { + cp.get(); + return null; + } + } + @ProvidedBy(C2P.class) + static class C2 { + @Inject C2(D2 d) {} + } + @ProvidedBy(D2P.class) + static class D2 { + @Inject D2(C2 c) {} + } + + public void testDisabledCircularDependency() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().disableCircularProxies(); + } + }).getInstance(C.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C.class.getName() + " to support a circular dependency, ", + "but circular proxies are disabled."); + } + } + + public void testDisabledCircularDependenciesWithProviderInstances() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().disableCircularProxies(); + } + @Provides C c(D d) { return null; } + @Provides D d(C c) { return null; } + }).getInstance(C.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C.class.getName() + " to support a circular dependency, ", + "but circular proxies are disabled."); + } + } + + public void testDisabledCircularDependenciesWithProviderKeys() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().disableCircularProxies(); + bind(C2.class).toProvider(C2P.class); + bind(D2.class).toProvider(D2P.class); + } + }).getInstance(C2.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", + "but circular proxies are disabled."); + } + } + + public void testDisabledCircularDependenciesWithProvidedBy() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().disableCircularProxies(); + } + }).getInstance(C2.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", + "but circular proxies are disabled."); + } + } + + /** + * As reported by issue 349, we give a lousy trace when a class is circularly + * dependent on itself in multiple ways. + */ + public void testCircularlyDependentMultipleWays() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + binder.bind(A.class).to(E.class); + binder.bind(B.class).to(E.class); + } + }); + injector.getInstance(A.class); + } + + public void testDisablingCircularProxies() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + binder().disableCircularProxies(); + binder.bind(A.class).to(E.class); + binder.bind(B.class).to(E.class); + } + }); + + try { + injector.getInstance(A.class); + fail("expected exception"); + } catch(ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + A.class.getName() + " to support a circular dependency, but circular proxies are disabled", + "Tried proxying " + B.class.getName() + " to support a circular dependency, but circular proxies are disabled"); + } + } + + @Singleton + static class E implements A, B { + @Inject + public E(A a, B b) {} + + public B getB() { + return this; + } + + public A getA() { + return this; + } + + public int id() { + return 0; + } + } + + + public void testCircularDependencyProxyDelegateNeverInitialized() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(F.class).to(RealF.class); + bind(G.class).to(RealG.class); + } + }); + F f = injector.getInstance(F.class); + assertEquals("F", f.g().f().toString()); + assertEquals("G", f.g().f().g().toString()); + + } + + public interface F { + G g(); + } + + @Singleton + public static class RealF implements F { + private final G g; + @Inject RealF(G g) { + this.g = g; + } + + public G g() { + return g; + } + + @Override public String toString() { + return "F"; + } + } + + public interface G { + F f(); + } + + @Singleton + public static class RealG implements G { + private final F f; + @Inject RealG(F f) { + this.f = f; + } + + public F f() { + return f; + } + + @Override public String toString() { + return "G"; + } + } + + /** + * Tests that ProviderInternalFactory can detect circular dependencies + * before it gets to Scopes.SINGLETON. This is especially important + * because the failure in Scopes.SINGLETON doesn't have enough context to + * provide a decent error message. + */ + public void testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Number.class).to(Integer.class); + } + + @Provides @Singleton Integer provideInteger(List list) { + return new Integer(2); + } + + @Provides List provideList(Integer integer) { + return new ArrayList(); + } + }); + try { + injector.getInstance(Number.class); + fail(); + } catch(ProvisionException expected) { + assertContains(expected.getMessage(), + "Tried proxying " + Integer.class.getName() + " to support a circular dependency, ", + "but it is not an interface."); + } + } + + public void testPrivateModulesDontTriggerCircularErrorsInProviders() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + install(new PrivateModule() { + @Override + protected void configure() { + bind(Foo.class); + expose(Foo.class); + } + @Provides String provideString(Bar bar) { + return new String("private 1, " + bar.string); + } + }); + install(new PrivateModule() { + @Override + protected void configure() { + bind(Bar.class); + expose(Bar.class); + } + @Provides String provideString() { + return new String("private 2"); + } + }); + } + }); + Foo foo = injector.getInstance(Foo.class); + assertEquals("private 1, private 2", foo.string); + } + static class Foo { + @Inject String string; + } + static class Bar { + @Inject String string; + } + + /** + * When Scope Providers call their unscoped Provider's get() methods are + * called, it's possible that the result is a circular proxy designed for one + * specific parameter (not for all possible parameters). But custom scopes + * typically cache the results without checking to see if the result is a + * proxy. This leads to caching a result that is unsuitable for reuse for + * other parameters. + * + * This means that custom proxies have to do an + * {@code if(Scopes.isCircularProxy(..))} + * in order to avoid exceptions. + */ + public void testCustomScopeCircularProxies() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindScope(SimpleSingleton.class, new BasicSingleton()); + bind(H.class).to(HImpl.class); + bind(I.class).to(IImpl.class); + bind(J.class).to(JImpl.class); + } + }); + + // The reason this happens is because the Scope gets these requests, in order: + // entry: Key (1 - from getInstance call) + // entry: Key + // entry: Key (2 - circular dependency from HImpl) + // result of 2nd Key - a com.google.inject.$Proxy, because it's a circular proxy + // result of Key - an HImpl + // entry: Key + // entry: Key (3 - another circular dependency, this time from JImpl) + // At this point, if the first Key result was cached, our cache would have + // Key caching to an instanceof of I, but not an an instanceof of IImpl. + // If returned this, it would result in cglib giving a ClassCastException or + // java reflection giving an IllegalArgumentException when filling in parameters + // for the constructor, because JImpl wants an IImpl, not an I. + + try { + injector.getInstance(IImpl.class); + fail(); + } catch(ProvisionException pe) { + assertContains(Iterables.getOnlyElement(pe.getErrorMessages()).getMessage(), + "Tried proxying " + IImpl.class.getName() + + " to support a circular dependency, but it is not an interface."); + } + } + + interface H {} + interface I {} + interface J {} + @SimpleSingleton + static class HImpl implements H { + @Inject HImpl(I i) {} + } + @SimpleSingleton + static class IImpl implements I { + @Inject IImpl(HImpl i, J j) {} + } + @SimpleSingleton + static class JImpl implements J { + @Inject JImpl(IImpl i) {} + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RUNTIME) + @ScopeAnnotation + public @interface SimpleSingleton {} + public static class BasicSingleton implements Scope { + private static Map cache = Maps.newHashMap(); + public Provider scope(final Key key, final Provider unscoped) { + return new Provider() { + @SuppressWarnings("unchecked") + public T get() { + if (!cache.containsKey(key)) { + T t = unscoped.get(); + if (Scopes.isCircularProxy(t)) { + return t; + } + cache.put(key, t); + } + return (T)cache.get(key); + } + }; + } + } +} diff --git a/src/test/java/com/google/inject/DuplicateBindingsTest.java b/src/test/java/com/google/inject/DuplicateBindingsTest.java new file mode 100644 index 0000000..27a2fd1 --- /dev/null +++ b/src/test/java/com/google/inject/DuplicateBindingsTest.java @@ -0,0 +1,576 @@ +/** + * Copyright (C) 2010 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; + +import static com.google.inject.Asserts.*; +import static com.google.inject.name.Names.named; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.inject.name.Named; +import com.google.inject.spi.Element; +import com.google.inject.spi.Elements; +import com.google.inject.util.Providers; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.logging.Logger; + +/** + * A suite of tests for duplicate bindings. + * + * @author sameb@google.com (Sam Berlin) + */ +public class DuplicateBindingsTest extends TestCase { + + private FooImpl foo = new FooImpl(); + private Provider pFoo = Providers.of(new FooImpl()); + private Class> pclFoo = FooProvider.class; + private Class clFoo = FooImpl.class; + private Constructor cFoo = FooImpl.cxtor(); + + public void testDuplicateBindingsAreIgnored() { + Injector injector = Guice.createInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) + ); + List> bindings = Lists.newArrayList(injector.getAllBindings().keySet()); + removeBasicBindings(bindings); + + // Ensure only one binding existed for each type. + assertTrue(bindings.remove(Key.get(Foo.class, named("instance")))); + assertTrue(bindings.remove(Key.get(Foo.class, named("pInstance")))); + assertTrue(bindings.remove(Key.get(Foo.class, named("pKey")))); + assertTrue(bindings.remove(Key.get(Foo.class, named("linkedKey")))); + assertTrue(bindings.remove(Key.get(FooImpl.class))); + assertTrue(bindings.remove(Key.get(Foo.class, named("constructor")))); + assertTrue(bindings.remove(Key.get(FooProvider.class))); // JIT binding + assertTrue(bindings.remove(Key.get(Foo.class, named("providerMethod")))); + + assertEquals(bindings.toString(), 0, bindings.size()); + } + + public void testElementsDeduplicate() { + List elements = Elements.getElements( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) + ); + assertEquals(14, elements.size()); + assertEquals(7, new LinkedHashSet(elements).size()); + } + + public void testProviderMethodsFailIfInstancesDiffer() { + try { + Guice.createInjector(new FailingProviderModule(), new FailingProviderModule()); + fail("should have failed"); + } catch(CreationException ce) { + assertContains(ce.getMessage(), + "A binding to " + Foo.class.getName() + " was already configured " + + "at " + FailingProviderModule.class.getName(), + "at " + FailingProviderModule.class.getName() + ); + } + } + + public void testSameScopeInstanceIgnored() { + Guice.createInjector( + new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), + new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo) + ); + + Guice.createInjector( + new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo), + new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo) + ); + } + + public void testSameScopeAnnotationIgnored() { + Guice.createInjector( + new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo), + new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo) + ); + } + + public void testMixedAnnotationAndScopeForSingletonIgnored() { + Guice.createInjector( + new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), + new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo) + ); + } + + public void testMixedScopeAndUnscopedIgnored() { + Guice.createInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), + new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo) + ); + } + + public void testMixedScopeFails() { + try { + Guice.createInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), + new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo) + ); + fail("expected exception"); + } catch(CreationException ce) { + String segment1 = "A binding to " + Foo.class.getName() + " annotated with " + + named("pInstance") + " was already configured at " + SimpleModule.class.getName(); + String segment2 = "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + + " was already configured at " + SimpleModule.class.getName(); + String segment3 = "A binding to " + Foo.class.getName() + " annotated with " + + named("constructor") + " was already configured at " + SimpleModule.class.getName(); + String segment4 = "A binding to " + FooImpl.class.getName() + " was already configured at " + + SimpleModule.class.getName(); + String atSegment = "at " + ScopedModule.class.getName(); + if (isIncludeStackTraceOff()) { + assertContains(ce.getMessage(), segment1 , atSegment, segment2, atSegment, segment3, + atSegment, segment4, atSegment); + } else { + assertContains(ce.getMessage(), segment1 , atSegment, segment2, atSegment, segment4, + atSegment, segment3, atSegment); + } + } + } + + @SuppressWarnings("unchecked") + public void testMixedTargetsFails() { + try { + Guice.createInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), + new SimpleModule(new FooImpl(), Providers.of(new FooImpl()), + (Class)BarProvider.class, (Class)Bar.class, (Constructor)Bar.cxtor()) + ); + fail("expected exception"); + } catch(CreationException ce) { + assertContains(ce.getMessage(), + "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName()); + } + } + + public void testExceptionInEqualsThrowsCreationException() { + try { + Guice.createInjector(new ThrowingModule(), new ThrowingModule()); + fail("expected exception"); + } catch(CreationException ce) { + assertContains(ce.getMessage(), + "A binding to " + Foo.class.getName() + " was already configured at " + ThrowingModule.class.getName(), + "and an error was thrown while checking duplicate bindings. Error: java.lang.RuntimeException: Boo!", + "at " + ThrowingModule.class.getName()); + } + } + + public void testChildInjectorDuplicateParentFail() { + Injector injector = Guice.createInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) + ); + + try { + injector.createChildInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) + ); + fail("expected exception"); + } catch(CreationException ce) { + assertContains(ce.getMessage(), + "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(), + "at " + SimpleModule.class.getName(), + "A binding to " + Foo.class.getName() + " annotated with " + named("providerMethod") + " was already configured at " + SimpleProviderModule.class.getName(), + "at " + SimpleProviderModule.class.getName() + ); + } + + + } + + public void testDuplicatesSolelyInChildIgnored() { + Injector injector = Guice.createInjector(); + injector.createChildInjector( + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), + new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) + ); + } + + public void testDifferentBindingTypesFail() { + List elements = Elements.getElements( + new FailedModule(foo, pFoo, pclFoo, clFoo, cFoo) + ); + + // Make sure every combination of the elements with another element fails. + // This ensures that duplication checks the kind of binding also. + for(Element e1 : elements) { + for(Element e2: elements) { + // if they're the same, this shouldn't fail. + try { + Guice.createInjector(Elements.getModule(Arrays.asList(e1, e2))); + if(e1 != e2) { + fail("must fail!"); + } + } catch(CreationException expected) { + if(e1 != e2) { + assertContains(expected.getMessage(), + "A binding to " + Foo.class.getName() + " was already configured at " + FailedModule.class.getName(), + "at " + FailedModule.class.getName()); + } else { + throw expected; + } + } + } + } + } + + public void testJitBindingsAreCheckedAfterConversions() { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(A.class); + bind(A.class).to(RealA.class); + } + }); + } + + public void testEqualsNotCalledByDefaultOnInstance() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnEquals = true; + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(HashEqualsTester.class).toInstance(a); + } + }); + } + + public void testEqualsNotCalledByDefaultOnProvider() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnEquals = true; + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(Object.class).toProvider(a); + } + }); + } + + public void testHashcodeNeverCalledOnInstance() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnHashcode = true; + a.equality = "test"; + + final HashEqualsTester b = new HashEqualsTester(); + b.throwOnHashcode = true; + b.equality = "test"; + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(HashEqualsTester.class).toInstance(a); + bind(HashEqualsTester.class).toInstance(b); + } + }); + } + + public void testHashcodeNeverCalledOnProviderInstance() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnHashcode = true; + a.equality = "test"; + + final HashEqualsTester b = new HashEqualsTester(); + b.throwOnHashcode = true; + b.equality = "test"; + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(Object.class).toProvider(a); + bind(Object.class).toProvider(b); + } + }); + } + + private static class RealA extends A {} + @ImplementedBy(RealA.class) private static class A {} + + private void removeBasicBindings(Collection> bindings) { + bindings.remove(Key.get(Injector.class)); + bindings.remove(Key.get(Logger.class)); + bindings.remove(Key.get(Stage.class)); + } + + private static class ThrowingModule extends AbstractModule { + @Override + protected void configure() { + bind(Foo.class).toInstance(new Foo() { + @Override + public boolean equals(Object obj) { + throw new RuntimeException("Boo!"); + } + }); + } + } + + private static abstract class FooModule extends AbstractModule { + protected final FooImpl foo; + protected final Provider pFoo; + protected final Class> pclFoo; + protected final Class clFoo; + protected final Constructor cFoo; + + FooModule(FooImpl foo, Provider pFoo, Class> pclFoo, + Class clFoo, Constructor cFoo) { + this.foo = foo; + this.pFoo = pFoo; + this.pclFoo = pclFoo; + this.clFoo = clFoo; + this.cFoo = cFoo; + } + } + + private static class FailedModule extends FooModule { + FailedModule(FooImpl foo, Provider pFoo, Class> pclFoo, + Class clFoo, Constructor cFoo) { + super(foo, pFoo, pclFoo, clFoo, cFoo); + } + + protected void configure() { + // InstanceBinding + bind(Foo.class).toInstance(foo); + + // ProviderInstanceBinding + bind(Foo.class).toProvider(pFoo); + + // ProviderKeyBinding + bind(Foo.class).toProvider(pclFoo); + + // LinkedKeyBinding + bind(Foo.class).to(clFoo); + + // ConstructorBinding + bind(Foo.class).toConstructor(cFoo); + } + + @Provides Foo foo() { + return null; + } + } + + private static class FailingProviderModule extends AbstractModule { + @Override protected void configure() {} + + @Provides Foo foo() { + return null; + } + } + + private static class SimpleProviderModule extends AbstractModule { + @Override protected void configure() {} + + @Provides @Named("providerMethod") Foo foo() { + return null; + } + + @Override + public boolean equals(Object obj) { + return obj.getClass() == getClass(); + } + } + + private static class SimpleModule extends FooModule { + SimpleModule(FooImpl foo, Provider pFoo, Class> pclFoo, + Class clFoo, Constructor cFoo) { + super(foo, pFoo, pclFoo, clFoo, cFoo); + } + + protected void configure() { + // InstanceBinding + bind(Foo.class).annotatedWith(named("instance")).toInstance(foo); + + // ProviderInstanceBinding + bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo); + + // ProviderKeyBinding + bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo); + + // LinkedKeyBinding + bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo); + + // UntargettedBinding / ConstructorBinding + bind(FooImpl.class); + + // ConstructorBinding + bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo); + + // ProviderMethod + // (reconstructed from an Element to ensure it doesn't get filtered out + // by deduplicating Modules) + install(Elements.getModule(Elements.getElements(new SimpleProviderModule()))); + } + } + + private static class ScopedModule extends FooModule { + private final Scope scope; + + ScopedModule(Scope scope, FooImpl foo, Provider pFoo, + Class> pclFoo, Class clFoo, + Constructor cFoo) { + super(foo, pFoo, pclFoo, clFoo, cFoo); + this.scope = scope; + } + + protected void configure() { + // ProviderInstanceBinding + bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); + + // ProviderKeyBinding + bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); + + // LinkedKeyBinding + bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); + + // UntargettedBinding / ConstructorBinding + bind(FooImpl.class).in(scope); + + // ConstructorBinding + bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); + } + } + + private static class AnnotatedScopeModule extends FooModule { + private final Class scope; + + AnnotatedScopeModule(Class scope, FooImpl foo, Provider pFoo, + Class> pclFoo, Class clFoo, + Constructor cFoo) { + super(foo, pFoo, pclFoo, clFoo, cFoo); + this.scope = scope; + } + + + protected void configure() { + // ProviderInstanceBinding + bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); + + // ProviderKeyBinding + bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); + + // LinkedKeyBinding + bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); + + // UntargettedBinding / ConstructorBinding + bind(FooImpl.class).in(scope); + + // ConstructorBinding + bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); + } + } + + private static interface Foo {} + private static class FooImpl implements Foo { + @Inject public FooImpl() {} + + private static Constructor cxtor() { + try { + return FooImpl.class.getConstructor(); + } catch (SecurityException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + private static class FooProvider implements Provider { + public Foo get() { + return new FooImpl(); + } + } + + private static class Bar implements Foo { + @Inject public Bar() {} + + private static Constructor cxtor() { + try { + return Bar.class.getConstructor(); + } catch (SecurityException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + private static class BarProvider implements Provider { + public Foo get() { + return new Bar(); + } + } + + private static class HashEqualsTester implements Provider { + private String equality; + private boolean throwOnEquals; + private boolean throwOnHashcode; + + @Override + public boolean equals(Object obj) { + if (throwOnEquals) { + throw new RuntimeException(); + } else if (obj instanceof HashEqualsTester) { + HashEqualsTester o = (HashEqualsTester)obj; + if(o.throwOnEquals) { + throw new RuntimeException(); + } + if(equality == null && o.equality == null) { + return this == o; + } else { + return Objects.equal(equality, o.equality); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + if(throwOnHashcode) { + throw new RuntimeException(); + } else { + return super.hashCode(); + } + } + + public Object get() { + return new Object(); + } + } + +} diff --git a/src/test/java/com/google/inject/EagerSingletonTest.java b/src/test/java/com/google/inject/EagerSingletonTest.java new file mode 100644 index 0000000..d18f194 --- /dev/null +++ b/src/test/java/com/google/inject/EagerSingletonTest.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2008 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; + +import junit.framework.TestCase; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class EagerSingletonTest extends TestCase { + + @Override public void setUp() { + A.instanceCount = 0; + B.instanceCount = 0; + C.instanceCount = 0; + } + + public void testJustInTimeEagerSingletons() { + Guice.createInjector(Stage.PRODUCTION, new AbstractModule() { + protected void configure() { + // create a just-in-time binding for A + getProvider(A.class); + + // create a just-in-time binding for C + requestInjection(new Object() { + @Inject void inject(Injector injector) { + injector.getInstance(C.class); + } + }); + } + }); + + assertEquals(1, A.instanceCount); + assertEquals("Singletons discovered when creating singletons should not be built eagerly", + 0, B.instanceCount); + assertEquals(1, C.instanceCount); + } + + public void testJustInTimeSingletonsAreNotEager() { + Injector injector = Guice.createInjector(Stage.PRODUCTION); + injector.getProvider(B.class); + assertEquals(0, B.instanceCount); + } + + public void testChildEagerSingletons() { + Injector parent = Guice.createInjector(Stage.PRODUCTION); + parent.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(D.class).to(C.class); + } + }); + + assertEquals(1, C.instanceCount); + } + + @Singleton + static class A { + static int instanceCount = 0; + int instanceId = instanceCount++; + + @Inject A(Injector injector) { + injector.getProvider(B.class); + } + } + + @Singleton + static class B { + static int instanceCount = 0; + int instanceId = instanceCount++; + } + + @Singleton + static class C implements D { + static int instanceCount = 0; + int instanceId = instanceCount++; + } + + private static interface D {} +} diff --git a/src/test/java/com/google/inject/ErrorHandlingTest.java b/src/test/java/com/google/inject/ErrorHandlingTest.java new file mode 100644 index 0000000..19161ca --- /dev/null +++ b/src/test/java/com/google/inject/ErrorHandlingTest.java @@ -0,0 +1,150 @@ +/** + * Copyright (C) 2006 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; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.List; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ErrorHandlingTest { + + public static void main(String[] args) throws CreationException { + try { + Guice.createInjector(new MyModule()); + } + catch (CreationException e) { + e.printStackTrace(); + System.err.println("--"); + } + + Injector bad = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toProvider(new Provider() { + public String get() { + return null; + } + }); + } + }); + try { + bad.getInstance(String.class); + } + catch (Exception e) { + e.printStackTrace(); + System.err.println("--"); + } + try { + bad.getInstance(NeedsString.class); + } + catch (Exception e) { + e.printStackTrace(); + System.err.println("--"); + } + } + + static class NeedsString { + @Inject String mofo; + } + + @Inject @Named("missing") + static List missing = null; + + static class Foo { + @Inject + public Foo(Runnable r) {} + + @Inject void setNames(List names) {} + } + + static class Bar { + // Invalid constructor. + Bar(String s) {} + + @Inject void setNumbers(@Named("numbers") List numbers) {} + + @Inject void bar(@Named("foo") String s) {} + } + + static class Tee { + @Inject String s; + + @Inject void tee(String s, int i) {} + + @Inject Invalid invalid; + } + + static class Invalid { + Invalid(String s) {} + } + + @SuppressWarnings("MoreThanOneScopeAnnotationOnClass") // suppress compiler error to test + @Singleton + @GoodScope + static class TooManyScopes { + } + + @Target(ElementType.TYPE) + @Retention(RUNTIME) + @ScopeAnnotation + @interface GoodScope {} + + @interface BadScope {} + + @ImplementedBy(String.class) + interface I {} + + static class MyModule extends AbstractModule { + protected void configure() { + bind(Runnable.class); + bind(Foo.class); + bind(Bar.class); + bind(Tee.class); + bind(new TypeLiteral>() {}); + bind(String.class).annotatedWith(Names.named("foo")).in(Named.class); + bind(Key.get(Runnable.class)).to(Key.get(Runnable.class)); + bind(TooManyScopes.class); + bindScope(BadScope.class, Scopes.SINGLETON); + bind(Object.class).toInstance(new Object() { + @Inject void foo() { + throw new RuntimeException(); + } + }); + requestStaticInjection(ErrorHandlingTest.class); + + addError("I don't like %s", "you"); + + Object o = "2"; + try { + Integer i = (Integer) o; + } catch (Exception e) { + addError(e); + } + + bind(Module.class).toInstance(this); + bind(I.class); + } + } +} diff --git a/src/test/java/com/google/inject/GenericInjectionTest.java b/src/test/java/com/google/inject/GenericInjectionTest.java new file mode 100644 index 0000000..51a1cb1 --- /dev/null +++ b/src/test/java/com/google/inject/GenericInjectionTest.java @@ -0,0 +1,187 @@ +/** + * Copyright (C) 2006 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; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.inject.util.Modules; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class GenericInjectionTest extends TestCase { + + public void testGenericInjection() throws CreationException { + final List names = Arrays.asList("foo", "bar", "bob"); + + Injector injector = Guice.createInjector((Module) new AbstractModule() { + protected void configure() { + bind(new TypeLiteral>() {}).toInstance(names); + } + }); + + Foo foo = injector.getInstance(Foo.class); + assertEquals(names, foo.names); + } + + static class Foo { + @Inject List names; + } + + /** + * Although we may not have intended to support this behaviour, this test + * passes under Guice 1.0. The workaround is to add an explicit binding for + * the parameterized type. See {@link #testExplicitBindingOfGenericType()}. + */ + public void testImplicitBindingOfGenericType() { + Parameterized parameterized + = Guice.createInjector().getInstance(Key.get(new TypeLiteral>() {})); + assertNotNull(parameterized); + } + + public void testExplicitBindingOfGenericType() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Key.get(new TypeLiteral>() {})) + .to((Class) Parameterized.class); + } + }); + + Parameterized parameterized + = injector.getInstance(Key.get(new TypeLiteral>() { })); + assertNotNull(parameterized); + } + + static class Parameterized { + @Inject + Parameterized() { } + } + + public void testInjectingParameterizedDependenciesForImplicitBinding() { + assertParameterizedDepsInjected(new Key>() {}, + Modules.EMPTY_MODULE); + } + + public void testInjectingParameterizedDependenciesForBindingTarget() { + final TypeLiteral> type + = new TypeLiteral>() {}; + + assertParameterizedDepsInjected(Key.get(Object.class), new AbstractModule() { + protected void configure() { + bind(Object.class).to(type); + } + }); + } + + public void testInjectingParameterizedDependenciesForBindingSource() { + final TypeLiteral> type + = new TypeLiteral>() {}; + + assertParameterizedDepsInjected(Key.get(type), new AbstractModule() { + protected void configure() { + bind(type); + } + }); + } + + public void testBindingToSubtype() { + final TypeLiteral> type + = new TypeLiteral>() {}; + + assertParameterizedDepsInjected(Key.get(type), new AbstractModule() { + protected void configure() { + bind(type).to(new TypeLiteral>() {}); + } + }); + } + + public void testBindingSubtype() { + final TypeLiteral> type + = new TypeLiteral>() {}; + + assertParameterizedDepsInjected(Key.get(type), new AbstractModule() { + protected void configure() { + bind(type); + } + }); + } + + @SuppressWarnings("unchecked") + public void assertParameterizedDepsInjected(Key key, Module bindingModule) { + Module bindDataModule = new AbstractModule() { + protected void configure() {} + @Provides Map provideMap() { + return ImmutableMap.of("one", 1, "two", 2); + } + @Provides Set provideSet(Map map) { + return map.keySet(); + } + @Provides Collection provideCollection(Map map) { + return map.values(); + } + }; + + Injector injector = Guice.createInjector(bindDataModule, bindingModule); + ParameterizedDeps parameterizedDeps + = (ParameterizedDeps) injector.getInstance(key); + assertEquals(ImmutableMap.of("one", 1, "two", 2), parameterizedDeps.map); + assertEquals(ImmutableSet.of("one", "two"), parameterizedDeps.keys); + assertEquals(ImmutableSet.of(1, 2), ImmutableSet.copyOf(parameterizedDeps.values)); + } + + static class SubParameterizedDeps extends ParameterizedDeps { + @Inject SubParameterizedDeps(Set keys) { + super(keys); + } + } + + static class ParameterizedDeps { + @Inject private Map map; + private Set keys; + private Collection values; + + @Inject ParameterizedDeps(Set keys) { + this.keys = keys; + } + + @Inject void method(Collection values) { + this.values = values; + } + } + + public void testImmediateTypeVariablesAreInjected() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("tee"); + } + }); + InjectsT injectsT = injector.getInstance(new Key>() {}); + assertEquals("tee", injectsT.t); + } + + static class InjectsT { + @Inject T t; + } +} diff --git a/src/test/java/com/google/inject/ImplicitBindingTest.java b/src/test/java/com/google/inject/ImplicitBindingTest.java new file mode 100644 index 0000000..eb47e49 --- /dev/null +++ b/src/test/java/com/google/inject/ImplicitBindingTest.java @@ -0,0 +1,395 @@ +/** + * Copyright (C) 2006 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; + +import com.google.common.collect.Iterables; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.spi.Message; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ImplicitBindingTest extends TestCase { + + public void testCircularDependency() throws CreationException { + Injector injector = Guice.createInjector(); + Foo foo = injector.getInstance(Foo.class); + assertSame(foo, foo.bar.foo); + } + + static class Foo { + @Inject Bar bar; + } + + static class Bar { + final Foo foo; + @Inject + public Bar(Foo foo) { + this.foo = foo; + } + } + + public void testDefaultImplementation() { + Injector injector = Guice.createInjector(); + I i = injector.getInstance(I.class); + i.go(); + } + + @ImplementedBy(IImpl.class) + interface I { + void go(); + } + + static class IImpl implements I { + public void go() {} + } + + static class AlternateImpl implements I { + public void go() {} + } + + public void testDefaultProvider() { + Injector injector = Guice.createInjector(); + Provided provided = injector.getInstance(Provided.class); + provided.go(); + } + + public void testBindingOverridesImplementedBy() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(I.class).to(AlternateImpl.class); + } + }); + assertEquals(AlternateImpl.class, injector.getInstance(I.class).getClass()); + } + + @ProvidedBy(ProvidedProvider.class) + interface Provided { + void go(); + } + + public void testNoImplicitBindingIsCreatedForAnnotatedKeys() { + try { + Guice.createInjector().getInstance(Key.get(I.class, Names.named("i"))); + fail(); + } catch (ConfigurationException expected) { + Asserts.assertContains(expected.getMessage(), + "1) No implementation for " + I.class.getName(), + "annotated with @" + Named.class.getName() + "(value=i) was bound.", + "while locating " + I.class.getName(), + " annotated with @" + Named.class.getName() + "(value=i)"); + } + } + + static class ProvidedProvider implements Provider { + public Provided get() { + return new Provided() { + public void go() {} + }; + } + } + + /** + * When we're building the binding for A, we temporarily insert that binding to support circular + * dependencies. And so we can successfully create a binding for B. But later, when the binding + * for A ultimately fails, we need to clean up the dependent binding for B. + * + * The test loops through linked bindings & bindings with constructor & member injections, + * to make sure that all are cleaned up and traversed. It also makes sure we don't touch + * explicit bindings. + */ + public void testCircularJitBindingsLeaveNoResidue() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Valid.class); + bind(Valid2.class); + } + }); + + // Capture good bindings. + Binding v1 = injector.getBinding(Valid.class); + Binding v2 = injector.getBinding(Valid2.class); + Binding jv1 = injector.getBinding(JitValid.class); + Binding jv2 = injector.getBinding(JitValid2.class); + + // Then validate that a whole series of invalid bindings are erased. + assertFailure(injector, Invalid.class); + assertFailure(injector, InvalidLinked.class); + assertFailure(injector, InvalidLinkedImpl.class); + assertFailure(injector, InvalidLinked2.class); + assertFailure(injector, InvalidLinked2Impl.class); + assertFailure(injector, InvalidProvidedBy.class); + assertFailure(injector, InvalidProvidedByProvider.class); + assertFailure(injector, InvalidProvidedBy2.class); + assertFailure(injector, InvalidProvidedBy2Provider.class); + assertFailure(injector, Invalid2.class); + + // Validate we didn't do anything to the valid explicit bindings. + assertSame(v1, injector.getBinding(Valid.class)); + assertSame(v2, injector.getBinding(Valid2.class)); + + // Validate that we didn't erase the valid JIT bindings + assertSame(jv1, injector.getBinding(JitValid.class)); + assertSame(jv2, injector.getBinding(JitValid2.class)); + } + + @SuppressWarnings("unchecked") + private void assertFailure(Injector injector, Class clazz) { + try { + injector.getBinding(clazz); + fail("Shouldn't have been able to get binding of: " + clazz); + } catch(ConfigurationException expected) { + Message msg = Iterables.getOnlyElement(expected.getErrorMessages()); + assertEquals("No implementation for " + InvalidInterface.class.getName() + " was bound.", + msg.getMessage()); + List sources = msg.getSources(); + // Assert that the first item in the sources if the key for the class we're looking up, + // ensuring that each lookup is "new". + assertEquals(Key.get(clazz).toString(), sources.get(0).toString()); + // Assert that the last item in each lookup contains the InvalidInterface class + Asserts.assertContains(sources.get(sources.size()-1).toString(), + Key.get(InvalidInterface.class).toString()); + } + } + + static class Invalid { + @Inject Valid a; + @Inject JitValid b; + @Inject InvalidProvidedBy c; + @Inject Invalid(InvalidLinked a) {} + @Inject void foo(InvalidInterface a) {} + + } + + @ImplementedBy(InvalidLinkedImpl.class) + static interface InvalidLinked {} + static class InvalidLinkedImpl implements InvalidLinked { + @Inject InvalidLinked2 a; + } + + @ImplementedBy(InvalidLinked2Impl.class) + static interface InvalidLinked2 {} + static class InvalidLinked2Impl implements InvalidLinked2 { + @Inject InvalidLinked2Impl(Invalid2 a) {} + } + + @ProvidedBy(InvalidProvidedByProvider.class) + static interface InvalidProvidedBy {} + static class InvalidProvidedByProvider implements Provider { + @Inject InvalidProvidedBy2 a; + public InvalidProvidedBy get() { + return null; + } + } + + @ProvidedBy(InvalidProvidedBy2Provider.class) + static interface InvalidProvidedBy2 {} + static class InvalidProvidedBy2Provider implements Provider { + @Inject Invalid2 a; + public InvalidProvidedBy2 get() { + return null; + } + } + + static class Invalid2 { + @Inject Invalid a; + } + + interface InvalidInterface {} + + static class Valid { @Inject Valid2 a; } + static class Valid2 {} + + static class JitValid { @Inject JitValid2 a; } + static class JitValid2 {} + + /** + * Regression test for https://github.com/google/guice/issues/319 + * + * The bug is that a class that asks for a provider for itself during injection time, + * where any one of the other types required to fulfill the object creation was bound + * in a child constructor, explodes when the injected Provider is called. + * + * It works just fine when the other types are bound in a main injector. + */ + public void testInstancesRequestingProvidersForThemselvesWithChildInjectors() { + final Module testModule = new AbstractModule() { + @Override + protected void configure() { + bind(String.class) + .toProvider(TestStringProvider.class); + } + }; + + // Verify it works when the type is setup in the parent. + Injector parentSetupRootInjector = Guice.createInjector(testModule); + Injector parentSetupChildInjector = parentSetupRootInjector.createChildInjector(); + assertEquals(TestStringProvider.TEST_VALUE, + parentSetupChildInjector.getInstance( + RequiresProviderForSelfWithOtherType.class).getValue()); + + // Verify it works when the type is setup in the child, not the parent. + // If it still occurs, the bug will explode here. + Injector childSetupRootInjector = Guice.createInjector(); + Injector childSetupChildInjector = childSetupRootInjector.createChildInjector(testModule); + assertEquals(TestStringProvider.TEST_VALUE, + childSetupChildInjector.getInstance( + RequiresProviderForSelfWithOtherType.class).getValue()); + } + + static class TestStringProvider implements Provider { + static final String TEST_VALUE = "This is to verify it all works"; + + public String get() { + return TEST_VALUE; + } + } + + static class RequiresProviderForSelfWithOtherType { + private final Provider selfProvider; + private final String providedStringValue; + + @Inject + RequiresProviderForSelfWithOtherType( + String providedStringValue, + Provider selfProvider + ) { + this.providedStringValue = providedStringValue; + this.selfProvider = selfProvider; + } + + public String getValue() { + // Attempt to get another instance of ourself. This pattern + // is possible for recursive processing. + selfProvider.get(); + + return providedStringValue; + } + } + + /** + * Ensure that when we cleanup failed JIT bindings, we don't break. + * The test here requires a sequence of JIT bindings: + * A-> B + * B -> C, A + * C -> A, D + * D not JITable + * The problem was that C cleaned up A's binding and then handed control back to B, + * which tried to continue processing A.. but A was removed from the jitBindings Map, + * so it attempts to create a new JIT binding for A, but we haven't yet finished + * constructing the first JIT binding for A, so we get a recursive + * computation exception from ComputingConcurrentHashMap. + * + * We also throw in a valid JIT binding, E, to guarantee that if + * something fails in this flow, it can be recreated later if it's + * not from a failed sequence. + */ + public void testRecursiveJitBindingsCleanupCorrectly() throws Exception { + Injector injector = Guice.createInjector(); + try { + injector.getInstance(A.class); + fail("Expected failure"); + } catch(ConfigurationException expected) { + Message msg = Iterables.getOnlyElement(expected.getErrorMessages()); + Asserts.assertContains(msg.getMessage(), + "Could not find a suitable constructor in " + D.class.getName()); + } + // Assert that we've removed all the bindings. + assertNull(injector.getExistingBinding(Key.get(A.class))); + assertNull(injector.getExistingBinding(Key.get(B.class))); + assertNull(injector.getExistingBinding(Key.get(C.class))); + assertNull(injector.getExistingBinding(Key.get(D.class))); + + // Confirm that we didn't prevent 'E' from working. + assertNotNull(injector.getBinding(Key.get(E.class))); + } + + static class A { + @Inject public A(B b) {} + } + + static class B { + @Inject public B(C c, A a) {} + } + + static class C { + @Inject public C(A a, D d, E e) {} + } + + static class D { + public D(int i) {} + } + + // Valid JITable binding + static class E { } + + public void testProvidedByNonEmptyEnum() { + NonEmptyEnum cardSuit = Guice.createInjector().getInstance(NonEmptyEnum.class); + + assertEquals(NonEmptyEnum.HEARTS, cardSuit); + } + + public void testProvidedByEmptyEnum() { + EmptyEnum emptyEnumValue = Guice.createInjector().getInstance(EmptyEnum.class); + assertNull(emptyEnumValue); + } + + @ProvidedBy(NonEmptyEnumProvider.class) + enum NonEmptyEnum { HEARTS, DIAMONDS, CLUBS, SPADES } + + static final class NonEmptyEnumProvider implements Provider { + @Override + public NonEmptyEnum get() { + return NonEmptyEnum.HEARTS; + } + } + + @ProvidedBy(EmptyEnumProvider.class) + enum EmptyEnum {} + + static final class EmptyEnumProvider implements Provider { + @Override + public EmptyEnum get() { + return null; + } + } + + // An enum cannot be implemented by anything, so it should not be possible to have a successful + // binding when the enum is annotated with @ImplementedBy. + public void testImplementedByEnum() { + Injector injector = Guice.createInjector(); + try { + injector.getInstance(EnumWithImplementedBy.class); + fail("Expected failure"); + } catch(ConfigurationException expected) { + Message msg = Iterables.getOnlyElement(expected.getErrorMessages()); + Asserts.assertContains(msg.getMessage(), + "No implementation for " + EnumWithImplementedBy.class.getName() + " was bound."); + } + } + + @ImplementedBy(EnumWithImplementedByEnum.class) + enum EnumWithImplementedBy {} + private static class EnumWithImplementedByEnum {} +} diff --git a/src/test/java/com/google/inject/InjectorTest.java b/src/test/java/com/google/inject/InjectorTest.java new file mode 100644 index 0000000..a71eb18 --- /dev/null +++ b/src/test/java/com/google/inject/InjectorTest.java @@ -0,0 +1,413 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.assertNotSerializable; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author crazybob@google.com (Bob Lee) + */ + +public class InjectorTest extends TestCase { + + @Retention(RUNTIME) + @BindingAnnotation @interface Other {} + + @Retention(RUNTIME) + @BindingAnnotation @interface S {} + + @Retention(RUNTIME) + @BindingAnnotation @interface I {} + + public void testToStringDoesNotInfinitelyRecurse() { + Injector injector = Guice.createInjector(Stage.TOOL); + injector.toString(); + injector.getBinding(Injector.class).toString(); + } + + public void testProviderMethods() throws CreationException { + final SampleSingleton singleton = new SampleSingleton(); + final SampleSingleton other = new SampleSingleton(); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(SampleSingleton.class).toInstance(singleton); + bind(SampleSingleton.class) + .annotatedWith(Other.class) + .toInstance(other); + } + }); + + assertSame(singleton, + injector.getInstance(Key.get(SampleSingleton.class))); + assertSame(singleton, injector.getInstance(SampleSingleton.class)); + + assertSame(other, + injector.getInstance(Key.get(SampleSingleton.class, Other.class))); + } + + static class SampleSingleton {} + + public void testInjection() throws CreationException { + Injector injector = createFooInjector(); + Foo foo = injector.getInstance(Foo.class); + + assertEquals("test", foo.s); + assertEquals("test", foo.bar.getTee().getS()); + assertSame(foo.bar, foo.copy); + assertEquals(5, foo.i); + assertEquals(5, foo.bar.getI()); + + // Test circular dependency. + assertSame(foo.bar, foo.bar.getTee().getBar()); + } + + private Injector createFooInjector() throws CreationException { + return Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Bar.class).to(BarImpl.class); + bind(Tee.class).to(TeeImpl.class); + bindConstant().annotatedWith(S.class).to("test"); + bindConstant().annotatedWith(I.class).to(5); + } + }); + } + + public void testGetInstance() throws CreationException { + Injector injector = createFooInjector(); + + Bar bar = injector.getInstance(Key.get(Bar.class)); + assertEquals("test", bar.getTee().getS()); + assertEquals(5, bar.getI()); + } + + public void testIntAndIntegerAreInterchangeable() + throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(I.class).to(5); + } + }); + + IntegerWrapper iw = injector.getInstance(IntegerWrapper.class); + assertEquals(5, (int) iw.i); + } + + public void testInjectorApiIsNotSerializable() throws IOException { + Injector injector = Guice.createInjector(); + assertNotSerializable(injector); + assertNotSerializable(injector.getProvider(String.class)); + assertNotSerializable(injector.getBinding(String.class)); + for (Binding binding : injector.getBindings().values()) { + assertNotSerializable(binding); + } + } + + static class IntegerWrapper { + @Inject @I Integer i; + } + + static class Foo { + + @Inject Bar bar; + @Inject Bar copy; + + @Inject @S String s; + + int i; + + @Inject + void setI(@I int i) { + this.i = i; + } + } + + interface Bar { + + Tee getTee(); + int getI(); + } + + @Singleton + static class BarImpl implements Bar { + + @Inject @I int i; + + Tee tee; + + @Inject + void initialize(Tee tee) { + this.tee = tee; + } + + public Tee getTee() { + return tee; + } + + public int getI() { + return i; + } + } + + interface Tee { + + String getS(); + Bar getBar(); + } + + static class TeeImpl implements Tee { + + final String s; + @Inject Bar bar; + + @Inject + TeeImpl(@S String s) { + this.s = s; + } + + public String getS() { + return s; + } + + public Bar getBar() { + return bar; + } + } + + public void testInjectStatics() throws CreationException { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(S.class).to("test"); + bindConstant().annotatedWith(I.class).to(5); + requestStaticInjection(Static.class); + } + }); + + assertEquals("test", Static.s); + assertEquals(5, Static.i); + } + + public void testInjectStaticInterface() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + requestStaticInjection(Interface.class); + } + }); + fail(); + } catch(CreationException ce) { + assertEquals(1, ce.getErrorMessages().size()); + Asserts.assertContains( + ce.getMessage(), + "1) " + Interface.class.getName() + + " is an interface, but interfaces have no static injection points.", + "at " + InjectorTest.class.getName(), + "configure"); + } + } + + private static interface Interface {} + + static class Static { + + @Inject @I static int i; + + static String s; + + @Inject static void setS(@S String s) { + Static.s = s; + } + } + + public void testPrivateInjection() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("foo"); + bind(int.class).toInstance(5); + } + }); + + Private p = injector.getInstance(Private.class); + assertEquals("foo", p.fromConstructor); + assertEquals(5, p.fromMethod); + } + + static class Private { + String fromConstructor; + int fromMethod; + + @Inject + private Private(String fromConstructor) { + this.fromConstructor = fromConstructor; + } + + @Inject + private void setInt(int i) { + this.fromMethod = i; + } + } + + public void testProtectedInjection() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("foo"); + bind(int.class).toInstance(5); + } + }); + + Protected p = injector.getInstance(Protected.class); + assertEquals("foo", p.fromConstructor); + assertEquals(5, p.fromMethod); + } + + static class Protected { + String fromConstructor; + int fromMethod; + + @Inject + protected Protected(String fromConstructor) { + this.fromConstructor = fromConstructor; + } + + @Inject + protected void setInt(int i) { + this.fromMethod = i; + } + } + + public void testInstanceInjectionHappensAfterFactoriesAreSetUp() { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Object.class).toInstance(new Object() { + @Inject Runnable r; + }); + + bind(Runnable.class).to(MyRunnable.class); + } + }); + } + + public void testSubtypeNotProvided() { + try { + Guice.createInjector().getInstance(Money.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + Tree.class.getName() + " doesn't provide instances of " + Money.class.getName(), + "while locating ", Tree.class.getName(), + "while locating ", Money.class.getName()); + } + } + + public void testNotASubtype() { + try { + Guice.createInjector().getInstance(PineTree.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + Tree.class.getName() + " doesn't extend " + PineTree.class.getName(), + "while locating ", PineTree.class.getName()); + } + } + + public void testRecursiveImplementationType() { + try { + Guice.createInjector().getInstance(SeaHorse.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "@ImplementedBy points to the same class it annotates.", + "while locating ", SeaHorse.class.getName()); + } + } + + public void testRecursiveProviderType() { + try { + Guice.createInjector().getInstance(Chicken.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "@ProvidedBy points to the same class it annotates", + "while locating ", Chicken.class.getName()); + } + } + + static class MyRunnable implements Runnable { + public void run() {} + } + + @ProvidedBy(Tree.class) + static class Money {} + + static class Tree implements Provider { + public Object get() { + return "Money doesn't grow on trees"; + } + } + + @ImplementedBy(Tree.class) + static class PineTree extends Tree {} + + @ImplementedBy(SeaHorse.class) + static class SeaHorse {} + + @ProvidedBy(Chicken.class) + static class Chicken implements Provider { + public Chicken get() { + return this; + } + } + + public void testJitBindingFromAnotherThreadDuringInjection() { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final AtomicReference got = new AtomicReference(); + + Guice.createInjector(new AbstractModule() { + protected void configure() { + requestInjection(new Object() { + @Inject void initialize(final Injector injector) + throws ExecutionException, InterruptedException { + Future future = executorService.submit(new Callable() { + public JustInTime call() throws Exception { + return injector.getInstance(JustInTime.class); + } + }); + got.set(future.get()); + } + }); + } + }); + + assertNotNull(got.get()); + } + + static class JustInTime {} +} diff --git a/src/test/java/com/google/inject/Java8LanguageFeatureBindingTest.java b/src/test/java/com/google/inject/Java8LanguageFeatureBindingTest.java new file mode 100644 index 0000000..394a60b --- /dev/null +++ b/src/test/java/com/google/inject/Java8LanguageFeatureBindingTest.java @@ -0,0 +1,157 @@ +/** + * 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; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +/** + * Test bindings to lambdas, method references, etc. + * + * @author cgdecker@google.com (Colin Decker) + */ +public class Java8LanguageFeatureBindingTest extends TestCase { + + // Some of these tests are kind of weird. + // See https://github.com/google/guice/issues/757 for more on why they exist. + + public void testBinding_lambdaToInterface() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).toInstance(o -> o != null); + } + }); + + Predicate predicate = injector.getInstance(new Key>() {}); + assertTrue(predicate.test(new Object())); + assertFalse(predicate.test(null)); + } + + public void testProviderMethod_returningLambda() throws Exception { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() {} + + @Provides + public Callable provideCallable() { + return () -> "foo"; + } + }); + + Callable callable = injector.getInstance(new Key>() {}); + assertEquals("foo", callable.call()); + } + + public void testProviderMethod_containingLambda_throwingException() throws Exception { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() {} + + @Provides + public Callable provideCallable() { + if (Boolean.parseBoolean("false")) { // avoid dead code warnings + return () -> "foo"; + } else { + throw new RuntimeException("foo"); + } + } + }); + + try { + injector.getInstance(new Key>() {}); + } catch (ProvisionException expected) { + assertTrue(expected.getCause() instanceof RuntimeException); + assertEquals("foo", expected.getCause().getMessage()); + } + } + + public void testProvider_usingJdk8Features() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class).toProvider(StringProvider.class); + } + }); + + fail(); + } catch (CreationException expected) { + } + + UUID uuid = UUID.randomUUID(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(UUID.class).toInstance(uuid); + bind(String.class).toProvider(StringProvider.class); + } + }); + + assertEquals(uuid.toString(), injector.getInstance(String.class)); + } + + private static final class StringProvider implements Provider { + private final UUID uuid; + + @Inject + StringProvider(UUID uuid) { + this.uuid = uuid; + } + + @Override + public String get() { + return Collections.singleton(uuid).stream() + .map(UUID::toString) + .findFirst().get(); + } + } + + public void testBinding_toProvider_lambda() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + AtomicInteger i = new AtomicInteger(); + bind(String.class).toProvider(() -> "Hello" + i.incrementAndGet()); + } + }); + + assertEquals("Hello1", injector.getInstance(String.class)); + assertEquals("Hello2", injector.getInstance(String.class)); + } + + public void testBinding_toProvider_methodReference() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class).toProvider(Java8LanguageFeatureBindingTest.this::provideString); + } + }); + + Provider provider = injector.getProvider(String.class); + assertEquals("Hello", provider.get()); + } + + private String provideString() { + return "Hello"; + } +} diff --git a/src/test/java/com/google/inject/JitBindingsTest.java b/src/test/java/com/google/inject/JitBindingsTest.java new file mode 100644 index 0000000..243c53e --- /dev/null +++ b/src/test/java/com/google/inject/JitBindingsTest.java @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2010 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; + +import static com.google.common.collect.ImmutableSet.of; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.JitBindingsTest.GetBindingCheck.ALLOW_BINDING; +import static com.google.inject.JitBindingsTest.GetBindingCheck.FAIL_ALL; + +import junit.framework.TestCase; + +import java.util.Set; + +/** + * Some tests for {@link Binder#requireExplicitBindings()} + * + * @author sberlin@gmail.com (Sam Berlin) + */ +public class JitBindingsTest extends TestCase { + + private String jitFailed(Class clazz) { + return jitFailed(TypeLiteral.get(clazz)); + } + + private String jitFailed(TypeLiteral clazz) { + return "Explicit bindings are required and " + clazz + " is not explicitly bound."; + } + + private String jitInParentFailed(Class clazz) { + return jitInParentFailed(TypeLiteral.get(clazz)); + } + + private String jitInParentFailed(TypeLiteral clazz) { + return "Explicit bindings are required and " + clazz + " would be bound in a parent injector."; + } + + private String inChildMessage(Class clazz) { + return "Unable to create binding for " + + clazz.getName() + + ". It was already configured on one or more child injectors or private modules"; + } + + public void testLinkedBindingWorks() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + } + }); + // Foo was explicitly bound + ensureWorks(injector, Foo.class); + // FooImpl was implicitly bound, it is an error to call getInstance or getProvider, + // It is OK to call getBinding for introspection, but an error to get the provider + // of the binding + ensureFails(injector, ALLOW_BINDING, FooImpl.class); + } + + public void testMoreBasicsWork() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + bind(Bar.class); + bind(FooBar.class); + } + }); + // Foo, Bar & FooBar was explicitly bound + ensureWorks(injector, FooBar.class, Bar.class, Foo.class); + // FooImpl was implicitly bound, it is an error to call getInstance or getProvider, + // It is OK to call getBinding for introspection, but an error to get the provider + // of the binding + ensureFails(injector, ALLOW_BINDING, FooImpl.class); + } + + public void testLinkedEagerSingleton() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class).asEagerSingleton(); + } + }); + // Foo was explicitly bound + ensureWorks(injector, Foo.class); + // FooImpl was implicitly bound, it is an error to call getInstance or getProvider, + // It is OK to call getBinding for introspection, but an error to get the provider + // of the binding + ensureFails(injector, ALLOW_BINDING, FooImpl.class); + } + + public void testBasicsWithEagerSingleton() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class).asEagerSingleton(); + bind(Bar.class); + bind(FooBar.class); + } + }); + // Foo, Bar & FooBar was explicitly bound + ensureWorks(injector, FooBar.class, Bar.class, Foo.class); + // FooImpl was implicitly bound, it is an error to call getInstance or getProvider, + // It is OK to call getBinding for introspection, but an error to get the provider + // of the binding + ensureFails(injector, ALLOW_BINDING, FooImpl.class); + } + + public void testLinkedToScoped() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder.requireExplicitBindings(); + bind(Foo.class).to(ScopedFooImpl.class); + } + }); + // Foo was explicitly bound + ensureWorks(injector, Foo.class); + // FooSingletonImpl was implicitly bound, it is an error to call getInstance or getProvider, + // It is OK to call getBinding for introspection, but an error to get the provider + // of the binding + ensureFails(injector, ALLOW_BINDING, ScopedFooImpl.class); + } + + public void testBasicsWithScoped() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(ScopedFooImpl.class); + bind(Bar.class); + bind(FooBar.class); + } + }); + // Foo, Bar & FooBar was explicitly bound + ensureWorks(injector, FooBar.class, Bar.class, Foo.class); + // FooSingletonImpl was implicitly bound, it is an error to call getInstance or getProvider, + // It is OK to call getBinding for introspection, but an error to get the provider + // of the binding + ensureFails(injector, ALLOW_BINDING, ScopedFooImpl.class); + } + + public void testFailsIfInjectingScopedDirectlyWhenItIsntBound() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(ScopedFooImpl.class); + bind(WantsScopedFooImpl.class); + } + }); + fail(); + } catch(CreationException expected) { + assertContains(expected.getMessage(), jitFailed(ScopedFooImpl.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testLinkedProviderBindingWorks() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).toProvider(FooProvider.class); + } + }); + // Foo was explicitly bound + ensureWorks(injector, Foo.class); + // FooImpl was not bound at all (even implicitly), it is an error + // to call getInstance, getProvider, or getBinding. + ensureFails(injector, FAIL_ALL, FooImpl.class); + } + + public void testJitGetFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + } + }).getInstance(Bar.class); + fail("should have failed"); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), jitFailed(Bar.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testJitInjectionFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + bind(FooBar.class); + } + }); + fail("should have failed"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), jitFailed(Bar.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testJitProviderGetFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + } + }).getProvider(Bar.class); + fail("should have failed"); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), jitFailed(Bar.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testJitProviderInjectionFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + bind(ProviderFooBar.class); + } + }); + fail("should have failed"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), jitFailed(Bar.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testImplementedBy() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(ImplBy.class); + } + }); + ensureWorks(injector, ImplBy.class); + ensureFails(injector, ALLOW_BINDING, ImplByImpl.class); + } + + public void testImplementedBySomethingThatIsAnnotated() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(ImplByScoped.class); + } + }); + ensureWorks(injector, ImplByScoped.class); + ensureFails(injector, ALLOW_BINDING, ImplByScopedImpl.class); + } + + public void testProvidedBy() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(ProvBy.class); + } + }); + ensureWorks(injector, ProvBy.class); + ensureFails(injector, ALLOW_BINDING, ProvByProvider.class); + } + + public void testProviderMethods() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().requireExplicitBindings(); + } + @SuppressWarnings("unused") @Provides Foo foo() { return new FooImpl(); } + }); + ensureWorks(injector, Foo.class); + } + + public void testChildInjectorInheritsOption() { + Injector parent = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Bar.class); + } + }); + ensureWorks(parent, Bar.class); + ensureFails(parent, FAIL_ALL, FooImpl.class, FooBar.class, Foo.class); + + try { + parent.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(FooBar.class); + } + }); + fail("should have failed"); + } catch(CreationException expected) { + assertContains(expected.getMessage(), jitFailed(Foo.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + + Injector child = parent.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Foo.class).to(FooImpl.class); + } + }); + ensureWorks(child, Foo.class, Bar.class); + ensureFails(child, ALLOW_BINDING, FooImpl.class); + ensureInChild(parent, FooImpl.class, Foo.class); + // TODO(sameb): FooBar may or may not be in a child injector, depending on if GC has run. + // We should fix failed child injectors to remove their contents from the parent blacklist + // immediately, rather than waiting on GC to do it. + // FooBar was succesfully inserted into the child injector (and parent blacklist), but then + // JIT bindings it depended on failed, making the child injector invalid. + + Injector grandchild = child.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(FooBar.class); + } + }); + ensureWorks(grandchild, FooBar.class, Foo.class, Bar.class); + ensureFails(grandchild, ALLOW_BINDING, FooImpl.class); + ensureFails(child, ALLOW_BINDING, FooImpl.class); + ensureInChild(parent, FooImpl.class, FooBar.class, Foo.class); + } + + public void testChildInjectorAddsOption() { + Injector parent = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Bar.class); + } + }); + int totalParentBindings = parent.getAllBindings().size(); + + try { + parent.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(FooBar.class); + } + }); + fail("should have failed"); + } catch(CreationException expected) { + assertContains(expected.getMessage(), jitFailed(Foo.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + assertEquals(totalParentBindings, parent.getAllBindings().size()); + + Injector child = parent.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + bind(FooImpl.class); + } + }); + assertEquals(totalParentBindings, parent.getAllBindings().size()); + ensureWorks(child, Foo.class, Bar.class); + + Injector grandchild = child.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(FooBar.class); + } + }); + assertEquals(totalParentBindings, parent.getAllBindings().size()); + ensureWorks(grandchild, FooBar.class, Foo.class, Bar.class); + + // Make sure siblings of children don't inherit each others settings... + // a new child should be able to get FooImpl. + child = parent.createChildInjector(); + ensureWorks(child, FooImpl.class); + } + + public void testPrivateModulesInheritOptions() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + + install(new PrivateModule() { + public void configure() { + bind(FooBar.class); + expose(FooBar.class); + } + }); + } + }); + fail("should have failed"); + } catch(CreationException expected) { + assertContains(expected.getMessage(), jitFailed(Bar.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + binder().requireExplicitBindings(); + + install(new PrivateModule() { + public void configure() { + bind(Foo.class).to(FooImpl.class); + expose(Foo.class); + } + }); + } + }); + ensureInChild(injector, FooImpl.class); + } + + public void testPrivateModuleAddsOption() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).to(FooImpl.class); + + // Fails because FooBar is in the private module, + // and it wants Bar, but Bar would be JIT. + install(new PrivateModule() { + public void configure() { + binder().requireExplicitBindings(); + bind(FooBar.class); + expose(FooBar.class); + } + }); + } + }); + fail("should have failed"); + } catch(CreationException expected) { + assertContains(expected.getMessage(), jitFailed(Bar.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testPrivateModuleSiblingsDontShareOption() { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).to(FooImpl.class); + + install(new PrivateModule() { + public void configure() { + binder().requireExplicitBindings(); + } + }); + + // This works, even though Bar is JIT, + // because the requireExplicitBindings isn't shared + // between sibling private modules. + install(new PrivateModule() { + public void configure() { + bind(FooBar.class); + expose(FooBar.class); + } + }); + } + }); + } + + public void testTypeLiteralsCanBeInjected() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().requireExplicitBindings(); + bind(new TypeLiteral>() {}); + bind(new TypeLiteral>() {}).toInstance(of("bar")); + } + }); + + WantsTypeLiterals foo = injector.getInstance(new Key>() {}); + assertEquals(foo.literal.getRawType(), String.class); + assertEquals(of("bar"), foo.set); + } + + public void testMembersInjectorsCanBeInjected() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().requireExplicitBindings(); + } + + @Provides String data(MembersInjector mi) { + String data = "foo"; + mi.injectMembers(data); + return data; + } + }); + + String data = injector.getInstance(String.class); + assertEquals("foo", data); + } + + public void testJitLinkedBindingInParentFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + install(new PrivateModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).to(FooImpl.class); + } + }); + } + }); + fail("should have failed"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), jitInParentFailed(FooImpl.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testJitProviderBindingInParentFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + install(new PrivateModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class).toProvider(FooProvider.class); + } + }); + } + }); + fail("should have failed"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), jitInParentFailed(FooProvider.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testJitImplementedByBindingInParentFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + install(new PrivateModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(ImplBy.class); + } + }); + } + }); + fail("should have failed"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), jitInParentFailed(ImplByImpl.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + public void testJitProvidedByBindingInParentFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + install(new PrivateModule() { + @Override + protected void configure() { + binder().requireExplicitBindings(); + bind(ProvBy.class); + } + }); + } + }); + fail("should have failed"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), jitInParentFailed(ProvByProvider.class)); + assertEquals(1, expected.getErrorMessages().size()); + } + } + + private void ensureWorks(Injector injector, Class... classes) { + for(int i = 0; i < classes.length; i++) { + injector.getInstance(classes[i]); + injector.getProvider(classes[i]).get(); + injector.getBinding(classes[i]).getProvider().get(); + } + } + + enum GetBindingCheck { FAIL_ALL, ALLOW_BINDING, ALLOW_BINDING_PROVIDER } + private void ensureFails(Injector injector, GetBindingCheck getBinding, Class... classes) { + for(int i = 0; i < classes.length; i++) { + try { + injector.getInstance(classes[i]); + fail("should have failed tring to retrieve class: " + classes[i]); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), jitFailed(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + + try { + injector.getProvider(classes[i]); + fail("should have failed tring to retrieve class: " + classes[i]); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), jitFailed(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + + if (getBinding == GetBindingCheck.ALLOW_BINDING + || getBinding == GetBindingCheck.ALLOW_BINDING_PROVIDER) { + Binding binding = injector.getBinding(classes[i]); + try { + binding.getProvider(); + if (getBinding != GetBindingCheck.ALLOW_BINDING_PROVIDER) { + fail("should have failed trying to retrieve class: " + classes[i]); + } + } catch(ConfigurationException expected) { + if (getBinding == GetBindingCheck.ALLOW_BINDING_PROVIDER) { + throw expected; + } + assertContains(expected.getMessage(), jitFailed(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + } else { + try { + injector.getBinding(classes[i]); + fail("should have failed tring to retrieve class: " + classes[i]); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), jitFailed(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + } + } + } + + private void ensureInChild(Injector injector, Class... classes) { + for(int i = 0; i < classes.length; i++) { + try { + injector.getInstance(classes[i]); + fail("should have failed tring to retrieve class: " + classes[i]); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), inChildMessage(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + + try { + injector.getProvider(classes[i]); + fail("should have failed tring to retrieve class: " + classes[i]); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), inChildMessage(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + + try { + injector.getBinding(classes[i]); + fail("should have failed tring to retrieve class: " + classes[i]); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), inChildMessage(classes[i])); + assertEquals(1, expected.getErrorMessages().size()); + } + } + } + + private static interface Foo {} + private static class FooImpl implements Foo {} + @Singleton private static class ScopedFooImpl implements Foo {} + private static class WantsScopedFooImpl { + @SuppressWarnings("unused") @Inject ScopedFooImpl scopedFoo; + } + private static class Bar {} + private static class FooBar { + @SuppressWarnings("unused") @Inject Foo foo; + @SuppressWarnings("unused") @Inject Bar bar; + } + private static class ProviderFooBar { + @SuppressWarnings("unused") @Inject Provider foo; + @SuppressWarnings("unused") @Inject Provider bar; + } + private static class FooProvider implements Provider { + public Foo get() { + return new FooImpl(); + } + } + + @ImplementedBy(ImplByImpl.class) + private static interface ImplBy {} + private static class ImplByImpl implements ImplBy {} + + @ImplementedBy(ImplByScopedImpl.class) + private static interface ImplByScoped {} + @Singleton + private static class ImplByScopedImpl implements ImplByScoped {} + + @ProvidedBy(ProvByProvider.class) + private static interface ProvBy {} + private static class ProvByProvider implements Provider { + public ProvBy get() { + return new ProvBy() {}; + } + } + + private static class WantsTypeLiterals { + TypeLiteral literal; + Set set; + + @Inject WantsTypeLiterals(TypeLiteral literal, Set set) { + this.literal = literal; + this.set = set; + + } + } +} diff --git a/src/test/java/com/google/inject/KeyTest.java b/src/test/java/com/google/inject/KeyTest.java new file mode 100644 index 0000000..d9dd943 --- /dev/null +++ b/src/test/java/com/google/inject/KeyTest.java @@ -0,0 +1,321 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.assertEqualsBothWays; +import static com.google.inject.Asserts.assertNotSerializable; +import static com.google.inject.Asserts.awaitClear; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.spi.Dependency; +import com.google.inject.util.Types; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class KeyTest extends TestCase { + + public void foo(List a, List b) {} + public void bar(Provider> a) {} + @Foo String baz; + List wildcardExtends; + + public void testOfType() { + Key k = Key.get(Object.class, Foo.class); + Key ki = k.ofType(Integer.class); + assertEquals(Integer.class, ki.getRawType()); + assertEquals(Foo.class, ki.getAnnotationType()); + } + + public void testKeyEquality() { + Key> a = new Key>(Foo.class) {}; + Key> b = Key.get(new TypeLiteral>() {}, Foo.class); + assertEqualsBothWays(a, b); + } + + public void testProviderKey() throws NoSuchMethodException { + Key actual = Key.get(getClass().getMethod("foo", List.class, List.class) + .getGenericParameterTypes()[0]).providerKey(); + Key expected = Key.get(getClass().getMethod("bar", Provider.class) + .getGenericParameterTypes()[0]); + assertEqualsBothWays(expected, actual); + assertEquals(expected.toString(), actual.toString()); + } + + public void testTypeEquality() throws Exception { + Method m = getClass().getMethod("foo", List.class, List.class); + Type[] types = m.getGenericParameterTypes(); + assertEquals(types[0], types[1]); + Key> k = new Key>() {}; + assertEquals(types[0], k.getTypeLiteral().getType()); + assertFalse(types[0].equals( + new Key>() {}.getTypeLiteral().getType())); + } + + /** + * Key canonicalizes {@link int.class} to {@code Integer.class}, and + * won't expose wrapper types. + */ + public void testPrimitivesAndWrappersAreEqual() { + Class[] primitives = new Class[] { + boolean.class, byte.class, short.class, int.class, long.class, + float.class, double.class, char.class, void.class + }; + Class[] wrappers = new Class[] { + Boolean.class, Byte.class, Short.class, Integer.class, Long.class, + Float.class, Double.class, Character.class, Void.class + }; + + for (int t = 0; t < primitives.length; t++) { + @SuppressWarnings("unchecked") + Key primitiveKey = Key.get(primitives[t]); + @SuppressWarnings("unchecked") + Key wrapperKey = Key.get(wrappers[t]); + + assertEquals(primitiveKey, wrapperKey); + assertEquals(wrappers[t], primitiveKey.getRawType()); + assertEquals(wrappers[t], wrapperKey.getRawType()); + assertEquals(wrappers[t], primitiveKey.getTypeLiteral().getType()); + assertEquals(wrappers[t], wrapperKey.getTypeLiteral().getType()); + } + + Key integerKey = Key.get(Integer.class); + Key integerKey2 = Key.get(Integer.class, Named.class); + Key integerKey3 = Key.get(Integer.class, Names.named("int")); + + Class intClassLiteral = int.class; + assertEquals(integerKey, Key.get(intClassLiteral)); + assertEquals(integerKey2, Key.get(intClassLiteral, Named.class)); + assertEquals(integerKey3, Key.get(intClassLiteral, Names.named("int"))); + + Type intType = int.class; + assertEquals(integerKey, Key.get(intType)); + assertEquals(integerKey2, Key.get(intType, Named.class)); + assertEquals(integerKey3, Key.get(intType, Names.named("int"))); + + TypeLiteral intTypeLiteral = TypeLiteral.get(int.class); + assertEquals(integerKey, Key.get(intTypeLiteral)); + assertEquals(integerKey2, Key.get(intTypeLiteral, Named.class)); + assertEquals(integerKey3, Key.get(intTypeLiteral, Names.named("int"))); + } + + public void testSerialization() throws IOException, NoSuchFieldException { + assertNotSerializable(Key.get(B.class)); + assertNotSerializable(Key.get(B.class, Names.named("bee"))); + assertNotSerializable(Key.get(B.class, Named.class)); + assertNotSerializable(Key.get(B[].class)); + assertNotSerializable(Key.get(new TypeLiteral, B>>() {})); + assertNotSerializable(Key.get(new TypeLiteral>() {})); + assertNotSerializable(Key.get(Types.listOf(Types.subtypeOf(CharSequence.class)))); + } + + public void testEqualityOfAnnotationTypesAndInstances() throws NoSuchFieldException { + Foo instance = getClass().getDeclaredField("baz").getAnnotation(Foo.class); + Key keyWithInstance = Key.get(String.class, instance); + Key keyWithLiteral = Key.get(String.class, Foo.class); + assertEqualsBothWays(keyWithInstance, keyWithLiteral); + } + + public void testNonBindingAnnotationOnKey() { + try { + Key.get(String.class, Deprecated.class); + fail(); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), "java.lang.Deprecated is not a binding annotation. ", + "Please annotate it with @BindingAnnotation."); + } + } + + public void testBindingAnnotationWithoutRuntimeRetention() { + try { + Key.get(String.class, Bar.class); + fail(); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), Bar.class.getName() + " is not retained at runtime.", + "Please annotate it with @Retention(RUNTIME)."); + } + } + + void parameterizedWithVariable(List typeWithVariables) {} + + /** Test for issue 186 */ + public void testCannotCreateKeysWithTypeVariables() throws NoSuchMethodException { + ParameterizedType listOfTType = (ParameterizedType) getClass().getDeclaredMethod( + "parameterizedWithVariable", List.class).getGenericParameterTypes()[0]; + + TypeLiteral listOfT = TypeLiteral.get(listOfTType); + try { + Key.get(listOfT); + fail("Guice should not allow keys for java.util.List"); + } catch (ConfigurationException e) { + assertContains(e.getMessage(), + "java.util.List cannot be used as a key; It is not fully specified."); + } + + TypeVariable tType = (TypeVariable) listOfTType.getActualTypeArguments()[0]; + TypeLiteral t = TypeLiteral.get(tType); + try { + Key.get(t); + fail("Guice should not allow keys for T"); + } catch (ConfigurationException e) { + assertContains(e.getMessage(), + "T cannot be used as a key; It is not fully specified."); + } + } + + public void testCannotGetKeyWithUnspecifiedTypeVariables() { + TypeLiteral typeLiteral = KeyTest.createTypeLiteral(); + try { + Key.get(typeLiteral); + fail("Guice should not allow keys for T"); + } catch (ConfigurationException e) { + assertContains(e.getMessage(), + "T cannot be used as a key; It is not fully specified."); + } + } + + private static TypeLiteral createTypeLiteral() { + return new TypeLiteral() {}; + } + + public void testCannotCreateKeySubclassesWithUnspecifiedTypeVariables() { + try { + KeyTest.createKey(); + fail("Guice should not allow keys for T"); + } catch (ConfigurationException e) { + assertContains(e.getMessage(), + "T cannot be used as a key; It is not fully specified."); + } + } + + private static Key createKey() { + return new Key() {}; + } + + interface B {} + + @Retention(RUNTIME) + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @BindingAnnotation @interface Foo {} + + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @BindingAnnotation @interface Bar {} + + class HasTypeParameters & Runnable, C extends Runnable> { + A a; B b; C c; + } + + public void testKeysWithDefaultAnnotations() { + AllDefaults allDefaults = HasAnnotations.class.getAnnotation(AllDefaults.class); + assertEquals(Key.get(Foo.class, allDefaults), Key.get(Foo.class, AllDefaults.class)); + + Marker marker = HasAnnotations.class.getAnnotation(Marker.class); + assertEquals(Key.get(Foo.class, marker), Key.get(Foo.class, Marker.class)); + + Key noDefaults = Key.get(Foo.class, NoDefaults.class); + assertNull(noDefaults.getAnnotation()); + assertEquals(NoDefaults.class, noDefaults.getAnnotationType()); + + Key someDefaults = Key.get(Foo.class, SomeDefaults.class); + assertNull(someDefaults.getAnnotation()); + assertEquals(SomeDefaults.class, someDefaults.getAnnotationType()); + } + + @Retention(RUNTIME) + @BindingAnnotation @interface AllDefaults { + int v1() default 1; + String v2() default "foo"; + } + + @Retention(RUNTIME) + @BindingAnnotation @interface SomeDefaults { + int v1() default 1; + String v2() default "foo"; + Class clazz(); + } + + @Retention(RUNTIME) + @BindingAnnotation @interface NoDefaults { + int value(); + } + + @Retention(RUNTIME) + @BindingAnnotation @interface Marker { + } + + @AllDefaults + @Marker + class HasAnnotations {} + + public void testAnonymousClassesDontHoldRefs() { + final AtomicReference>> stringProvider = + new AtomicReference>>(); + final AtomicReference>> intProvider = + new AtomicReference>>(); + final Object foo = new Object() { + @SuppressWarnings("unused") @Inject List list; + }; + Module module = new AbstractModule() { + @Override protected void configure() { + bind(new Key>() {}).toInstance(new ArrayList()); + bind(new TypeLiteral>() {}).toInstance(new ArrayList()); + + stringProvider.set(getProvider(new Key>() {})); + intProvider.set(binder().getProvider(Dependency.get(new Key>() {}))); + + binder().requestInjection(new TypeLiteral() {}, foo); + } + }; + WeakReference moduleRef = new WeakReference(module); + final Injector injector = Guice.createInjector(module); + module = null; + awaitClear(moduleRef); // Make sure anonymous keys & typeliterals don't hold the module. + + Runnable runner = new Runnable() { + @Override public void run() { + injector.getInstance(new Key>() {}); + injector.getInstance(Key.get(new TypeLiteral>() {})); + } + }; + WeakReference runnerRef = new WeakReference(runner); + runner.run(); + runner = null; + awaitClear(runnerRef); // also make sure anonymous keys & typeliterals don't hold for JITs + } + + static class Typed {} + +} diff --git a/src/test/java/com/google/inject/MembersInjectorTest.java b/src/test/java/com/google/inject/MembersInjectorTest.java new file mode 100644 index 0000000..6bf7589 --- /dev/null +++ b/src/test/java/com/google/inject/MembersInjectorTest.java @@ -0,0 +1,287 @@ +/** + * 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.google.inject; + +import static com.google.inject.Asserts.assertContains; + +import com.google.inject.name.Names; +import com.google.inject.util.Providers; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class MembersInjectorTest extends TestCase { + + private static final A uninjectableA = new A() { + @Override void doNothing() { + throw new AssertionFailedError(); + } + }; + + private static final B uninjectableB = new B() { + @Override void doNothing() { + throw new AssertionFailedError(); + } + }; + + private static final C myFavouriteC = new C(); + + public void testMembersInjectorFromBinder() { + final AtomicReference>> aMembersInjectorReference + = new AtomicReference>>(); + final AtomicReference> bMembersInjectorReference + = new AtomicReference>(); + + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + MembersInjector> aMembersInjector = getMembersInjector(new TypeLiteral>() {}); + try { + aMembersInjector.injectMembers(uninjectableA); + fail(); + } catch (IllegalStateException expected) { + assertContains(expected.getMessage(), + "This MembersInjector cannot be used until the Injector has been created."); + } + + MembersInjector bMembersInjector = getMembersInjector(B.class); + try { + bMembersInjector.injectMembers(uninjectableB); + fail(); + } catch (IllegalStateException expected) { + assertContains(expected.getMessage(), + "This MembersInjector cannot be used until the Injector has been created."); + } + + aMembersInjectorReference.set(aMembersInjector); + bMembersInjectorReference.set(bMembersInjector); + + assertEquals("MembersInjector", + getMembersInjector(String.class).toString()); + + bind(C.class).toInstance(myFavouriteC); + } + }); + + A injectableA = new A(); + aMembersInjectorReference.get().injectMembers(injectableA); + assertSame(myFavouriteC, injectableA.t); + assertSame(myFavouriteC, injectableA.b.c); + + B injectableB = new B(); + bMembersInjectorReference.get().injectMembers(injectableB); + assertSame(myFavouriteC, injectableB.c); + + B anotherInjectableB = new B(); + bMembersInjectorReference.get().injectMembers(anotherInjectableB); + assertSame(myFavouriteC, anotherInjectableB.c); + } + + public void testMembersInjectorFromInjector() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(C.class).toInstance(myFavouriteC); + } + }); + + MembersInjector> aMembersInjector + = injector.getMembersInjector(new TypeLiteral>() {}); + MembersInjector bMembersInjector = injector.getMembersInjector(B.class); + + A injectableA = new A(); + aMembersInjector.injectMembers(injectableA); + assertSame(myFavouriteC, injectableA.t); + assertSame(myFavouriteC, injectableA.b.c); + + B injectableB = new B(); + bMembersInjector.injectMembers(injectableB); + assertSame(myFavouriteC, injectableB.c); + + B anotherInjectableB = new B(); + bMembersInjector.injectMembers(anotherInjectableB); + assertSame(myFavouriteC, anotherInjectableB.c); + + assertEquals("MembersInjector", + injector.getMembersInjector(String.class).toString()); + } + + public void testMembersInjectorWithNonInjectedTypes() { + Injector injector = Guice.createInjector(); + + MembersInjector membersInjector + = injector.getMembersInjector(NoInjectedMembers.class); + + membersInjector.injectMembers(new NoInjectedMembers()); + membersInjector.injectMembers(new NoInjectedMembers()); + } + + public void testInjectionFailure() { + Injector injector = Guice.createInjector(); + + MembersInjector membersInjector + = injector.getMembersInjector(InjectionFailure.class); + + try { + membersInjector.injectMembers(new InjectionFailure()); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "1) Error injecting method, java.lang.ClassCastException: whoops, failure #1"); + } + } + + public void testInjectionAppliesToSpecifiedType() { + Injector injector = Guice.createInjector(); + + MembersInjector membersInjector = injector.getMembersInjector(Object.class); + membersInjector.injectMembers(new InjectionFailure()); + } + + public void testInjectingMembersInjector() { + InjectsMembersInjector injectsMembersInjector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(C.class).toInstance(myFavouriteC); + } + }).getInstance(InjectsMembersInjector.class); + + A a = new A(); + injectsMembersInjector.aMembersInjector.injectMembers(a); + assertSame(myFavouriteC, a.t); + assertSame(myFavouriteC, a.b.c); + } + + public void testCannotBindMembersInjector() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(MembersInjector.class).toProvider(Providers.of(null)); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Binding to core guice framework type is not allowed: MembersInjector."); + } + + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(new TypeLiteral>>() {}) + .toProvider(Providers.>>of(null)); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Binding to core guice framework type is not allowed: MembersInjector."); + } + } + + public void testInjectingMembersInjectorWithErrorsInDependencies() { + try { + Guice.createInjector().getInstance(InjectsBrokenMembersInjector.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "1) No implementation for " + Unimplemented.class.getName() + " was bound.", + "while locating " + Unimplemented.class.getName(), + "for field at " + A.class.getName() + ".t(", + "while locating com.google.inject.MembersInjector<", + "for field at " + InjectsBrokenMembersInjector.class.getName() + ".aMembersInjector(", + "while locating " + InjectsBrokenMembersInjector.class.getName()); + } + } + + public void testLookupMembersInjectorBinding() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(C.class).toInstance(myFavouriteC); + } + }); + MembersInjector> membersInjector = + injector.getInstance(new Key>>() {}); + + A a = new A(); + membersInjector.injectMembers(a); + assertSame(myFavouriteC, a.t); + assertSame(myFavouriteC, a.b.c); + + assertEquals("MembersInjector", + injector.getInstance(new Key>() {}).toString()); + } + + public void testGettingRawMembersInjector() { + Injector injector = Guice.createInjector(); + try { + injector.getInstance(MembersInjector.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "Cannot inject a MembersInjector that has no type parameter"); + } + } + + public void testGettingAnnotatedMembersInjector() { + Injector injector = Guice.createInjector(); + try { + injector.getInstance(new Key>(Names.named("foo")) {}); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "1) No implementation for com.google.inject.MembersInjector " + + "annotated with @com.google.inject.name.Named(value=foo) was bound."); + } + } + + static class A { + @Inject B b; + @Inject T t; + @Inject void doNothing() {} + } + + static class B { + @Inject C c; + @Inject void doNothing() {} + } + + static class C {} + + static class NoInjectedMembers {} + + static class InjectionFailure { + int failures = 0; + + @Inject void fail() { + throw new ClassCastException("whoops, failure #" + (++failures)); + } + } + + static class InjectsMembersInjector { + @Inject MembersInjector> aMembersInjector; + @Inject A ab; + } + + static class InjectsBrokenMembersInjector { + @Inject MembersInjector> aMembersInjector; + } + + static interface Unimplemented {} +} diff --git a/src/test/java/com/google/inject/ModuleTest.java b/src/test/java/com/google/inject/ModuleTest.java new file mode 100644 index 0000000..e938da5 --- /dev/null +++ b/src/test/java/com/google/inject/ModuleTest.java @@ -0,0 +1,56 @@ +// Copyright 2007 Google Inc. All Rights Reserved. + +package com.google.inject; + +import junit.framework.TestCase; + +/** + * Tests relating to modules. + * + * @author kevinb + */ +public class ModuleTest extends TestCase { + + static class A implements Module { + public void configure(Binder binder) { + binder.bind(X.class); + binder.install(new B()); + binder.install(new C()); + } + } + + static class B implements Module { + public void configure(Binder binder) { + binder.bind(Y.class); + binder.install(new D()); + } + } + + static class C implements Module { + public void configure(Binder binder) { + binder.bind(Z.class); + binder.install(new D()); + } + } + + static class D implements Module { + public void configure(Binder binder) { + binder.bind(W.class); + } + @Override public boolean equals(Object obj) { + return obj.getClass() == D.class; // we're all equal in the eyes of guice + } + @Override public int hashCode() { + return D.class.hashCode(); + } + } + + static class X {} + static class Y {} + static class Z {} + static class W {} + + public void testDiamond() throws Exception { + Guice.createInjector(new A()); + } +} diff --git a/src/test/java/com/google/inject/ModulesTest.java b/src/test/java/com/google/inject/ModulesTest.java new file mode 100644 index 0000000..b32e4f6 --- /dev/null +++ b/src/test/java/com/google/inject/ModulesTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2008 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; + +import com.google.common.collect.ImmutableList; +import com.google.inject.spi.ElementSource; +import com.google.inject.util.Modules; + +import junit.framework.TestCase; + +import java.util.Arrays; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ModulesTest extends TestCase { + + public void testCombineVarargs() { + Module combined = Modules.combine(newModule(1), newModule(2L), newModule((short) 3)); + Injector injector = Guice.createInjector(combined); + assertEquals(1, injector.getInstance(Integer.class).intValue()); + assertEquals(2L, injector.getInstance(Long.class).longValue()); + assertEquals(3, injector.getInstance(Short.class).shortValue()); + } + + public void testCombineIterable() { + Iterable modules = Arrays.asList(newModule(1), newModule(2L), newModule((short) 3)); + Injector injector = Guice.createInjector(Modules.combine(modules)); + assertEquals(1, injector.getInstance(Integer.class).intValue()); + assertEquals(2, injector.getInstance(Long.class).longValue()); + assertEquals(3, injector.getInstance(Short.class).shortValue()); + } + + /** + * The module returned by Modules.combine shouldn't show up in binder sources. + */ + public void testCombineSources() { + final Module m1 = newModule(1); + final Module m2 = newModule(2L); + final Module combined1 = Modules.combine(m1, m2); + Module skipSourcesModule = new AbstractModule() { + @Override protected void configure() { + install(combined1); + } + }; + final Module combined2 = Modules.combine(skipSourcesModule); + Injector injector = Guice.createInjector(combined2); + ElementSource source = (ElementSource) injector.getBinding(Integer.class).getSource(); + assertEquals(source.getModuleClassNames().size(), 4); + assertEquals(ImmutableList.of(m1.getClass().getName(), + combined1.getClass().getName(), skipSourcesModule.getClass().getName(), + combined2.getClass().getName()), source.getModuleClassNames()); + StackTraceElement stackTraceElement = (StackTraceElement) source.getDeclaringSource(); + assertEquals(skipSourcesModule.getClass().getName(), stackTraceElement.getClassName()); + } + + private Module newModule(final T toBind) { + return new AbstractModule() { + protected void configure() { + @SuppressWarnings("unchecked") // getClass always needs a cast + Class tClass = (Class) toBind.getClass(); + binder().skipSources(getClass()).bind(tClass).toInstance(toBind); + } + }; + } +} diff --git a/src/test/java/com/google/inject/NullableInjectionPointTest.java b/src/test/java/com/google/inject/NullableInjectionPointTest.java new file mode 100644 index 0000000..9cf22cc --- /dev/null +++ b/src/test/java/com/google/inject/NullableInjectionPointTest.java @@ -0,0 +1,251 @@ +package com.google.inject; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; + +import junit.framework.TestCase; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class NullableInjectionPointTest extends TestCase { + + public void testInjectNullIntoNotNullableConstructor() { + try { + createInjector().getInstance(FooConstructor.class); + fail("Injecting null should fail with an error"); + } + catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "null returned by binding at " + getClass().getName(), + "parameter 0 of " + FooConstructor.class.getName() + ".() is not @Nullable"); + } + } + + public void testInjectNullIntoNotNullableMethod() { + try { + createInjector().getInstance(FooMethod.class); + fail("Injecting null should fail with an error"); + } + catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "null returned by binding at " + getClass().getName(), + "parameter 0 of " + FooMethod.class.getName() + ".setFoo() is not @Nullable"); + } + } + + public void testInjectNullIntoNotNullableField() { + try { + createInjector().getInstance(FooField.class); + fail("Injecting null should fail with an error"); + } + catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "null returned by binding at " + getClass().getName(), + " but " + FooField.class.getName() + ".foo is not @Nullable"); + } + } + + /** + * Provider.getInstance() is allowed to return null via direct calls to + * getInstance(). + */ + public void testGetInstanceOfNull() { + assertNull(createInjector().getInstance(Foo.class)); + } + + public void testInjectNullIntoNullableConstructor() { + NullableFooConstructor nfc + = createInjector().getInstance(NullableFooConstructor.class); + assertNull(nfc.foo); + } + + public void testInjectNullIntoNullableMethod() { + NullableFooMethod nfm + = createInjector().getInstance(NullableFooMethod.class); + assertNull(nfm.foo); + } + + public void testInjectNullIntoNullableField() { + NullableFooField nff + = createInjector().getInstance(NullableFooField.class); + assertNull(nff.foo); + } + + public void testInjectNullIntoCustomNullableConstructor() { + CustomNullableFooConstructor nfc + = createInjector().getInstance(CustomNullableFooConstructor.class); + assertNull(nfc.foo); + } + + public void testInjectNullIntoCustomNullableMethod() { + CustomNullableFooMethod nfm + = createInjector().getInstance(CustomNullableFooMethod.class); + assertNull(nfm.foo); + } + + public void testInjectNullIntoCustomNullableField() { + CustomNullableFooField nff + = createInjector().getInstance(CustomNullableFooField.class); + assertNull(nff.foo); + } + + private Injector createInjector() { + return Guice.createInjector( + new AbstractModule() { + protected void configure() { + bind(Foo.class).toProvider(new Provider() { + public Foo get() { + return null; + } + }); + } + }); + } + + /** + * We haven't decided on what the desired behaviour of this test should be... + */ + public void testBindNullToInstance() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toInstance(null); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "Binding to null instances is not allowed.", + "at " + getClass().getName(), getDeclaringSourcePart(getClass())); + } + } + + public void testBindNullToProvider() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toProvider(new Provider() { + public Foo get() { + return null; + } + }); + } + }); + assertNull(injector.getInstance(NullableFooField.class).foo); + assertNull(injector.getInstance(CustomNullableFooField.class).foo); + + try { + injector.getInstance(FooField.class); + } + catch(ProvisionException expected) { + assertContains(expected.getMessage(), "null returned by binding at"); + } + } + + public void testBindScopedNull() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toProvider(new Provider() { + public Foo get() { + return null; + } + }).in(Scopes.SINGLETON); + } + }); + assertNull(injector.getInstance(NullableFooField.class).foo); + assertNull(injector.getInstance(CustomNullableFooField.class).foo); + + try { + injector.getInstance(FooField.class); + } + catch(ProvisionException expected) { + assertContains(expected.getMessage(), "null returned by binding at"); + } + } + + public void testBindNullAsEagerSingleton() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toProvider(new Provider() { + public Foo get() { + return null; + } + }).asEagerSingleton(); + } + }); + assertNull(injector.getInstance(NullableFooField.class).foo); + assertNull(injector.getInstance(CustomNullableFooField.class).foo); + + try { + injector.getInstance(FooField.class); + fail(); + } catch(ProvisionException expected) { + assertContains(expected.getMessage(), "null returned by binding " + + "at com.google.inject.NullableInjectionPointTest"); + } + } + + static class Foo { } + + static class FooConstructor { + @Inject FooConstructor(Foo foo) { } + } + static class FooField { + @Inject Foo foo; + } + static class FooMethod { + @Inject + void setFoo(Foo foo) { } + } + + static class NullableFooConstructor { + Foo foo; + @Inject NullableFooConstructor(@Nullable Foo foo) { + this.foo = foo; + } + } + static class NullableFooField { + @Inject @Nullable Foo foo; + } + static class NullableFooMethod { + Foo foo; + @Inject void setFoo(@Nullable Foo foo) { + this.foo = foo; + } + } + + static class CustomNullableFooConstructor { + Foo foo; + @Inject CustomNullableFooConstructor(@Namespace.Nullable Foo foo) { + this.foo = foo; + } + } + + static class CustomNullableFooField { + @Inject @Namespace.Nullable Foo foo; + } + static class CustomNullableFooMethod { + Foo foo; + @Inject void setFoo(@Namespace.Nullable Foo foo) { + this.foo = foo; + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER, ElementType.FIELD}) + @interface Nullable { } + + static interface Namespace { + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER, ElementType.FIELD}) + @interface Nullable { } + } +} diff --git a/src/test/java/com/google/inject/OptionalBindingTest.java b/src/test/java/com/google/inject/OptionalBindingTest.java new file mode 100644 index 0000000..a62191c --- /dev/null +++ b/src/test/java/com/google/inject/OptionalBindingTest.java @@ -0,0 +1,298 @@ +/** + * Copyright (C) 2008 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; + +import static com.google.inject.Asserts.assertContains; + +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import junit.framework.TestCase; + +/** + * This test verifies the ways things are injected (ie. getInstance(), + * injectMembers(), bind to instance, and bind to provider instance) for all + * states of optional bindings (fields, methods, multiple-argument methods, + * provider fields, provider methods, constructors). + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class OptionalBindingTest extends TestCase { + + private static final A injectA = new A() {}; + private static final B injectB = new B() {}; + private static final C injectC = new C() {}; + private static final D injectD = new D() {}; + private static final E injectE = new E() {}; + private static final F injectF = new F() {}; + private static final G injectG = new G() {}; + + private Module everythingModule = new AbstractModule() { + protected void configure() { + bind(A.class).toInstance(injectA); + bind(B.class).toInstance(injectB); + bind(C.class).toInstance(injectC); + bind(D.class).toInstance(injectD); + bind(E.class).annotatedWith(Names.named("e")).toInstance(injectE); + bind(F.class).toInstance(injectF); + bind(G.class).toInstance(injectG); + } + }; + + private Module partialModule = new AbstractModule() { + protected void configure() { + bind(C.class).toInstance(new C() {}); + } + }; + + private Module toInstanceModule = new AbstractModule() { + protected void configure() { + bind(HasOptionalInjections.class) + .toInstance(new HasOptionalInjections()); + } + }; + + private Module toProviderInstanceModule = new AbstractModule() { + protected void configure() { + bind(HasOptionalInjections.class) + .toProvider(new HasOptionalInjectionsProvider()); + } + }; + + private Module toProviderModule = new AbstractModule() { + protected void configure() { + bind(HasOptionalInjections.class) + .toProvider(HasOptionalInjectionsProvider.class); + } + }; + + public void testEverythingInjectorGetInstance() { + Guice.createInjector(everythingModule) + .getInstance(HasOptionalInjections.class) + .assertEverythingInjected(); + } + + public void testPartialInjectorGetInstance() { + Guice.createInjector(partialModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testNothingInjectorGetInstance() { + Guice.createInjector() + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testEverythingInjectorInjectMembers() { + HasOptionalInjections instance = new HasOptionalInjections(); + Guice.createInjector(everythingModule).injectMembers(instance); + instance.assertEverythingInjected(); + } + + public void testPartialInjectorInjectMembers() { + HasOptionalInjections instance = new HasOptionalInjections(); + Guice.createInjector(partialModule).injectMembers(instance); + instance.assertNothingInjected(); + } + + public void testNothingInjectorInjectMembers() { + HasOptionalInjections instance = new HasOptionalInjections(); + Guice.createInjector().injectMembers(instance); + instance.assertNothingInjected(); + } + + public void testEverythingInjectorToInstance() { + Guice.createInjector(everythingModule, toInstanceModule) + .getInstance(HasOptionalInjections.class) + .assertEverythingInjected(); + } + + public void testPartialInjectorToInstance() { + Guice.createInjector(partialModule, toInstanceModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testNothingInjectorToInstance() { + Guice.createInjector(toInstanceModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testEverythingInjectorToProviderInstance() { + Guice.createInjector(everythingModule, toProviderInstanceModule) + .getInstance(HasOptionalInjections.class) + .assertEverythingInjected(); + } + + public void testPartialInjectorToProviderInstance() { + Guice.createInjector(partialModule, toProviderInstanceModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testNothingInjectorToProviderInstance() { + Guice.createInjector(toProviderInstanceModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testEverythingInjectorToProvider() { + Guice.createInjector(everythingModule, toProviderModule) + .getInstance(HasOptionalInjections.class) + .assertEverythingInjected(); + } + + public void testPartialInjectorToProvider() { + Guice.createInjector(partialModule, toProviderModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + public void testNothingInjectorToProvider() { + Guice.createInjector(toProviderModule) + .getInstance(HasOptionalInjections.class) + .assertNothingInjected(); + } + + static class HasOptionalInjections { + A originalA = new A() {}; + @Inject(optional=true) A a = originalA; // field injection + B b; // method injection with one argument + C c; // method injection with two arguments + D d; // method injection with two arguments + E e; // annotated injection + @Inject(optional=true) Provider fProvider; // provider + Provider gProvider; // method injection of provider + boolean invoked0, invoked1, invoked2, invokedAnnotated, invokeProvider; + + @Inject(optional=true) void methodInjectZeroArguments() { + invoked0 = true; + } + + @Inject(optional=true) void methodInjectOneArgument(B b) { + this.b = b; + invoked1 = true; + } + + @Inject(optional=true) void methodInjectTwoArguments(C c, D d) { + this.c = c; + this.d = d; + invoked2 = true; + } + + @Inject(optional=true) void methodInjectAnnotated(@Named("e") E e) { + this.e = e; + invokedAnnotated = true; + } + + @Inject(optional=true) void methodInjectProvider(Provider gProvider) { + this.gProvider = gProvider; + invokeProvider = true; + } + + void assertNothingInjected() { + assertSame(originalA, a); + assertNull(b); + assertNull(c); + assertNull(d); + assertNull(e); + assertNull(fProvider); + assertNull(gProvider); + assertTrue(invoked0); + assertFalse(invoked1); + assertFalse(invoked2); + assertFalse(invokedAnnotated); + } + + public void assertEverythingInjected() { + assertNotSame(injectA, originalA); + assertSame(injectA, a); + assertSame(injectB, b); + assertSame(injectC, c); + assertSame(injectD, d); + assertSame(injectE, e); + assertSame(injectF, fProvider.get()); + assertSame(injectG, gProvider.get()); + assertTrue(invoked0); + assertTrue(invoked1); + assertTrue(invoked2); + assertTrue(invokedAnnotated); + } + } + + static class HasOptionalInjectionsProvider + extends HasOptionalInjections implements Provider { + public HasOptionalInjections get() { + return this; + } + } + + public void testOptionalConstructorBlowsUp() { + try { + Guice.createInjector().getInstance(HasOptionalConstructor.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), "OptionalBindingTest$HasOptionalConstructor.() " + + "is annotated @Inject(optional=true), but constructors cannot be optional."); + } + } + + static class HasOptionalConstructor { + // Suppress compiler errors by the error-prone checker InjectedConstructorAnnotations, + // which catches optional injected constructors. + @SuppressWarnings("InjectedConstructorAnnotations") + @Inject(optional=true) + HasOptionalConstructor() {} + } + + @Inject(optional=true) static A staticInjectA; + + public void testStaticInjection() { + staticInjectA = injectA; + Guice.createInjector(new AbstractModule() { + protected void configure() { + requestStaticInjection(OptionalBindingTest.class); + } + }); + assertSame(staticInjectA, injectA); + } + + /** + * Test for bug 107, where we weren't doing optional injection properly for + * indirect injections. + */ + public void testIndirectOptionalInjection() { + Indirect indirect = Guice.createInjector().getInstance(Indirect.class); + assertNotNull(indirect.hasOptionalInjections); + indirect.hasOptionalInjections.assertNothingInjected(); + } + + static class Indirect { + @Inject HasOptionalInjections hasOptionalInjections; + } + + interface A {} + interface B {} + interface C {} + interface D {} + interface E {} + interface F {} + interface G {} +} diff --git a/src/test/java/com/google/inject/ParentInjectorTest.java b/src/test/java/com/google/inject/ParentInjectorTest.java new file mode 100644 index 0000000..7c7182d --- /dev/null +++ b/src/test/java/com/google/inject/ParentInjectorTest.java @@ -0,0 +1,305 @@ +/* +Copyright (C) 2007 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.inject.matcher.Matchers; +import com.google.inject.name.Names; +import com.google.inject.spi.TypeConverter; + +import junit.framework.TestCase; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.List; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ParentInjectorTest extends TestCase { + + public void testParentAndChildCannotShareExplicitBindings() { + Injector parent = Guice.createInjector(bindsA); + try { + parent.createChildInjector(bindsA); + fail("Created the same explicit binding on both parent and child"); + } catch (CreationException e) { + assertContains(e.getMessage(), "A binding to ", A.class.getName(), " was already configured", + " at ", getClass().getName(), getDeclaringSourcePart(getClass()), + " at ", getClass().getName(), getDeclaringSourcePart(getClass())); + } + } + + public void testParentJitBindingWontClobberChildBinding() { + Injector parent = Guice.createInjector(); + parent.createChildInjector(bindsA); + try { + parent.getInstance(A.class); + fail("Created a just-in-time binding on the parent that's the same as a child's binding"); + } catch (ConfigurationException e) { + assertContains(e.getMessage(), + "Unable to create binding for " + A.class.getName(), + "It was already configured on one or more child injectors or private modules", + "bound at " + bindsA.getClass().getName() + ".configure(", + "If it was in a PrivateModule, did you forget to expose the binding?", + "while locating " + A.class.getName()); + } + } + + public void testChildCannotBindToAParentJitBinding() { + Injector parent = Guice.createInjector(); + parent.getInstance(A.class); + try { + parent.createChildInjector(bindsA); + fail(); + } catch(CreationException ce) { + assertContains(Iterables.getOnlyElement(ce.getErrorMessages()).getMessage(), + "A just-in-time binding to " + A.class.getName() + " was already configured on a parent injector."); + } + } + + public void testJustInTimeBindingsAreSharedWithParentIfPossible() { + Injector parent = Guice.createInjector(); + Injector child = parent.createChildInjector(); + assertSame(child.getInstance(A.class), parent.getInstance(A.class)); + + Injector anotherChild = parent.createChildInjector(); + assertSame(anotherChild.getInstance(A.class), parent.getInstance(A.class)); + + Injector grandchild = child.createChildInjector(); + assertSame(grandchild.getInstance(A.class), parent.getInstance(A.class)); + } + + public void testBindingsInherited() { + Injector parent = Guice.createInjector(bindsB); + Injector child = parent.createChildInjector(); + assertSame(RealB.class, child.getInstance(B.class).getClass()); + } + + public void testGetParent() { + Injector top = Guice.createInjector(bindsA); + Injector middle = top.createChildInjector(bindsB); + Injector bottom = middle.createChildInjector(); + assertSame(middle, bottom.getParent()); + assertSame(top, middle.getParent()); + assertNull(top.getParent()); + } + + public void testChildBindingsNotVisibleToParent() { + Injector parent = Guice.createInjector(); + parent.createChildInjector(bindsB); + try { + parent.getBinding(B.class); + fail(); + } catch (ConfigurationException expected) { + } + } + + public void testScopesInherited() { + Injector parent = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindScope(MyScope.class, Scopes.SINGLETON); + } + }); + Injector child = parent.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(A.class).in(MyScope.class); + } + }); + assertSame(child.getInstance(A.class), child.getInstance(A.class)); + } + + public void testTypeConvertersInherited() { + Injector parent = Guice.createInjector(bindListConverterModule); + Injector child = parent.createChildInjector(bindStringNamedB); + + assertEquals(ImmutableList.of(), child.getInstance(Key.get(List.class, Names.named("B")))); + } + + public void testTypeConvertersConflicting() { + Injector parent = Guice.createInjector(bindListConverterModule); + Injector child = parent.createChildInjector(bindListConverterModule, bindStringNamedB); + + try { + child.getInstance(Key.get(List.class, Names.named("B"))); + fail(); + } catch (ConfigurationException expected) { + Asserts.assertContains(expected.getMessage(), "Multiple converters can convert"); + } + } + + public void testInjectorInjectionSpanningInjectors() { + Injector parent = Guice.createInjector(); + Injector child = parent.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(D.class); + } + }); + + D d = child.getInstance(D.class); + assertSame(d.injector, child); + + E e = child.getInstance(E.class); + assertSame(e.injector, parent); + } + + public void testSeveralLayersOfHierarchy() { + Injector top = Guice.createInjector(bindsA); + Injector left = top.createChildInjector(); + Injector leftLeft = left.createChildInjector(bindsD); + Injector right = top.createChildInjector(bindsD); + + assertSame(leftLeft, leftLeft.getInstance(D.class).injector); + assertSame(right, right.getInstance(D.class).injector); + assertSame(top, leftLeft.getInstance(E.class).injector); + assertSame(top.getInstance(A.class), leftLeft.getInstance(A.class)); + + Injector leftRight = left.createChildInjector(bindsD); + assertSame(leftRight, leftRight.getInstance(D.class).injector); + + try { + top.getInstance(D.class); + fail(); + } catch (ConfigurationException expected) { + } + + try { + left.getInstance(D.class); + fail(); + } catch (ConfigurationException expected) { + } + } + + public void testScopeBoundInChildInjectorOnly() { + Injector parent = Guice.createInjector(); + Injector child = parent.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bindScope(MyScope.class, Scopes.SINGLETON); + } + }); + + try { + parent.getProvider(F.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "No scope is bound to com.google.inject.ParentInjectorTest$MyScope.", + "at " + F.class.getName() + ".class(", + " while locating " + F.class.getName()); + } + + assertNotNull(child.getProvider(F.class).get()); + } + + public void testErrorInParentButOkayInChild() { + Injector parent = Guice.createInjector(); + Injector childInjector = parent.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bindScope(MyScope.class, Scopes.SINGLETON); + bind(Object.class).to(F.class); + } + }); + Object one = childInjector.getInstance(Object.class); + Object two = childInjector.getInstance(Object.class); + assertSame(one, two); + } + + public void testErrorInParentAndChild() { + Injector parent = Guice.createInjector(); + Injector childInjector = parent.createChildInjector(); + + try { + childInjector.getInstance(G.class); + fail(); + } catch(ConfigurationException expected) { + assertContains(expected.getMessage(), "No scope is bound to " + MyScope.class.getName(), + "at " + F.class.getName() + ".class(", + " while locating " + G.class.getName()); + } + } + + @Singleton + static class A {} + + private final Module bindsA = new AbstractModule() { + @Override protected void configure() { + bind(A.class).toInstance(new A()); + } + }; + + interface B {} + static class RealB implements B {} + + private final Module bindsB = new AbstractModule() { + @Override protected void configure() { + bind(B.class).to(RealB.class); + } + }; + + @Target(TYPE) @Retention(RUNTIME) @ScopeAnnotation + public @interface MyScope {} + + private final TypeConverter listConverter = new TypeConverter() { + public Object convert(String value, TypeLiteral toType) { + return ImmutableList.of(); + } + }; + + private final Module bindListConverterModule = new AbstractModule() { + @Override protected void configure() { + convertToTypes(Matchers.any(), listConverter); + } + }; + + private final Module bindStringNamedB = new AbstractModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(Names.named("B")).toInstance("buzz"); + } + }; + + public static class C { + public A interceptedMethod() { + return new A(); + } + } + + static class D { + @Inject Injector injector; + } + + static class E { + @Inject Injector injector; + } + + private final Module bindsD = new AbstractModule() { + @Override protected void configure() { + bind(D.class); + } + }; + + @MyScope + static class F implements G {} + + @ImplementedBy(F.class) + interface G {} +} diff --git a/src/test/java/com/google/inject/PrivateModuleTest.java b/src/test/java/com/google/inject/PrivateModuleTest.java new file mode 100644 index 0000000..226d73d --- /dev/null +++ b/src/test/java/com/google/inject/PrivateModuleTest.java @@ -0,0 +1,564 @@ +/** + * Copyright (C) 2008 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; + +import static com.google.inject.Asserts.asModuleChain; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static com.google.inject.name.Names.named; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.ExposedBinding; +import com.google.inject.spi.PrivateElements; +import com.google.inject.util.Types; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class PrivateModuleTest extends TestCase { + + public void testBasicUsage() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(named("a")).toInstance("public"); + + install(new PrivateModule() { + @Override public void configure() { + bind(String.class).annotatedWith(named("b")).toInstance("i"); + + bind(AB.class).annotatedWith(named("one")).to(AB.class); + expose(AB.class).annotatedWith(named("one")); + } + }); + + install(new PrivateModule() { + @Override public void configure() { + bind(String.class).annotatedWith(named("b")).toInstance("ii"); + + bind(AB.class).annotatedWith(named("two")).to(AB.class); + expose(AB.class).annotatedWith(named("two")); + } + }); + } + }); + + AB ab1 = injector.getInstance(Key.get(AB.class, named("one"))); + assertEquals("public", ab1.a); + assertEquals("i", ab1.b); + + AB ab2 = injector.getInstance(Key.get(AB.class, named("two"))); + assertEquals("public", ab2.a); + assertEquals("ii", ab2.b); + } + + public void testWithoutPrivateModules() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + PrivateBinder bindA = binder().newPrivateBinder(); + bindA.bind(String.class).annotatedWith(named("a")).toInstance("i"); + bindA.expose(String.class).annotatedWith(named("a")); + bindA.bind(String.class).annotatedWith(named("c")).toInstance("private to A"); + + PrivateBinder bindB = binder().newPrivateBinder(); + bindB.bind(String.class).annotatedWith(named("b")).toInstance("ii"); + bindB.expose(String.class).annotatedWith(named("b")); + bindB.bind(String.class).annotatedWith(named("c")).toInstance("private to B"); + } + }); + + assertEquals("i", injector.getInstance(Key.get(String.class, named("a")))); + assertEquals("ii", injector.getInstance(Key.get(String.class, named("b")))); + } + + public void testMisplacedExposedAnnotation() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() {} + + @Provides @Exposed + String provideString() { + return "i"; + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "Cannot expose java.lang.String on a standard binder. ", + "Exposed bindings are only applicable to private binders.", + " at " + PrivateModuleTest.class.getName(), "provideString("); + } + } + + public void testMisplacedExposeStatement() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + ((PrivateBinder) binder()).expose(String.class).annotatedWith(named("a")); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), "Cannot expose java.lang.String on a standard binder. ", + "Exposed bindings are only applicable to private binders.", + " at " + PrivateModuleTest.class.getName(), getDeclaringSourcePart(getClass())); + } + } + + public void testPrivateModulesAndProvidesMethods() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + install(new PrivateModule() { + @Override public void configure() { + expose(String.class).annotatedWith(named("a")); + } + + @Provides @Named("a") String providePublicA() { + return "i"; + } + + @Provides @Named("b") String providePrivateB() { + return "private"; + } + }); + + install(new PrivateModule() { + @Override public void configure() {} + + @Provides @Named("c") String providePrivateC() { + return "private"; + } + + @Provides @Exposed @Named("d") String providePublicD() { + return "ii"; + } + }); + } + }); + + assertEquals("i", injector.getInstance(Key.get(String.class, named("a")))); + + try { + injector.getInstance(Key.get(String.class, named("b"))); + fail(); + } catch(ConfigurationException expected) { + } + + try { + injector.getInstance(Key.get(String.class, named("c"))); + fail(); + } catch(ConfigurationException expected) { + } + + assertEquals("ii", injector.getInstance(Key.get(String.class, named("d")))); + } + + public void testCannotBindAKeyExportedByASibling() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + install(new PrivateModule() { + @Override public void configure() { + bind(String.class).toInstance("public"); + expose(String.class); + } + }); + + install(new PrivateModule() { + @Override public void configure() { + bind(String.class).toInstance("private"); + } + }); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "A binding to java.lang.String was already configured at ", + getClass().getName(), getDeclaringSourcePart(getClass()), + " at " + getClass().getName(), getDeclaringSourcePart(getClass())); + } + } + + public void testExposeButNoBind() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(named("a")).toInstance("a"); + bind(String.class).annotatedWith(named("b")).toInstance("b"); + + install(new PrivateModule() { + @Override public void configure() { + expose(AB.class); + } + }); + } + }); + fail("AB was exposed but not bound"); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "Could not expose() " + AB.class.getName() + ", it must be explicitly bound", + getDeclaringSourcePart(getClass())); + } + } + + /** + * Ensure that when we've got errors in different private modules, Guice presents all errors + * in a unified message. + */ + public void testMessagesFromPrivateModulesAreNicelyIntegrated() { + try { + Guice.createInjector( + new PrivateModule() { + @Override public void configure() { + bind(C.class); + } + }, + new PrivateModule() { + @Override public void configure() { + bind(AB.class); + } + } + ); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) No implementation for " + C.class.getName() + " was bound.", + "at " + getClass().getName(), getDeclaringSourcePart(getClass()), + "2) No implementation for " + String.class.getName(), "Named(value=a) was bound.", + "for field at " + AB.class.getName() + ".a(", + "3) No implementation for " + String.class.getName(), "Named(value=b) was bound.", + "for field at " + AB.class.getName() + ".b(", + "3 errors"); + } + } + + public void testNestedPrivateInjectors() { + Injector injector = Guice.createInjector(new PrivateModule() { + @Override public void configure() { + expose(String.class); + + install(new PrivateModule() { + @Override public void configure() { + bind(String.class).toInstance("nested"); + expose(String.class); + } + }); + } + }); + + assertEquals("nested", injector.getInstance(String.class)); + } + + public void testInstallingRegularModulesFromPrivateModules() { + Injector injector = Guice.createInjector(new PrivateModule() { + @Override public void configure() { + expose(String.class); + + install(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("nested"); + } + }); + } + }); + + assertEquals("nested", injector.getInstance(String.class)); + } + + public void testNestedPrivateModulesWithSomeKeysUnexposed() { + Injector injector = Guice.createInjector(new PrivateModule() { + @Override public void configure() { + bind(String.class).annotatedWith(named("bound outer, exposed outer")).toInstance("boeo"); + expose(String.class).annotatedWith(named("bound outer, exposed outer")); + bind(String.class).annotatedWith(named("bound outer, exposed none")).toInstance("boen"); + expose(String.class).annotatedWith(named("bound inner, exposed both")); + + install(new PrivateModule() { + @Override public void configure() { + bind(String.class).annotatedWith(named("bound inner, exposed both")).toInstance("bieb"); + expose(String.class).annotatedWith(named("bound inner, exposed both")); + bind(String.class).annotatedWith(named("bound inner, exposed none")).toInstance("bien"); + } + }); + } + }); + + assertEquals("boeo", + injector.getInstance(Key.get(String.class, named("bound outer, exposed outer")))); + assertEquals("bieb", + injector.getInstance(Key.get(String.class, named("bound inner, exposed both")))); + + try { + injector.getInstance(Key.get(String.class, named("bound outer, exposed none"))); + fail(); + } catch (ConfigurationException expected) { + } + + try { + injector.getInstance(Key.get(String.class, named("bound inner, exposed none"))); + fail(); + } catch (ConfigurationException expected) { + } + } + + public void testDependenciesBetweenPrivateAndPublic() { + Injector injector = Guice.createInjector( + new PrivateModule() { + @Override protected void configure() {} + + @Provides @Exposed @Named("a") String provideA() { + return "A"; + } + + @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) { + return ab + "C"; + } + }, + new AbstractModule() { + @Override protected void configure() {} + + @Provides @Named("ab") String provideAb(@Named("a") String a) { + return a + "B"; + } + + @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) { + return abc + "D"; + } + } + ); + + assertEquals("ABCD", injector.getInstance(Key.get(String.class, named("abcd")))); + } + + public void testDependenciesBetweenPrivateAndPublicWithPublicEagerSingleton() { + Injector injector = Guice.createInjector( + new PrivateModule() { + @Override protected void configure() {} + + @Provides @Exposed @Named("a") String provideA() { + return "A"; + } + + @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) { + return ab + "C"; + } + }, + new AbstractModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(named("abcde")).toProvider(new Provider() { + @Inject @Named("abcd") String abcd; + + public String get() { + return abcd + "E"; + } + }).asEagerSingleton(); + } + + @Provides @Named("ab") String provideAb(@Named("a") String a) { + return a + "B"; + } + + @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) { + return abc + "D"; + } + } + ); + + assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde")))); + } + + public void testDependenciesBetweenPrivateAndPublicWithPrivateEagerSingleton() { + Injector injector = Guice.createInjector( + new AbstractModule() { + @Override protected void configure() {} + + @Provides @Named("ab") String provideAb(@Named("a") String a) { + return a + "B"; + } + + @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) { + return abc + "D"; + } + }, + new PrivateModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(named("abcde")).toProvider(new Provider() { + @Inject @Named("abcd") String abcd; + + public String get() { + return abcd + "E"; + } + }).asEagerSingleton(); + expose(String.class).annotatedWith(named("abcde")); + } + + @Provides @Exposed @Named("a") String provideA() { + return "A"; + } + + @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) { + return ab + "C"; + } + } + ); + + assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde")))); + } + + static class AB { + @Inject @Named("a") String a; + @Inject @Named("b") String b; + } + + interface C {} + + public void testSpiAccess() { + Injector injector = Guice.createInjector(new PrivateModule() { + @Override public void configure() { + bind(String.class).annotatedWith(named("a")).toInstance("private"); + bind(String.class).annotatedWith(named("b")).toInstance("exposed"); + expose(String.class).annotatedWith(named("b")); + } + }); + + ExposedBinding binding + = (ExposedBinding) injector.getBinding(Key.get(String.class, Names.named("b"))); + assertEquals(ImmutableSet.>of(Dependency.get(Key.get(Injector.class))), + binding.getDependencies()); + PrivateElements privateElements = binding.getPrivateElements(); + assertEquals(ImmutableSet.>of(Key.get(String.class, named("b"))), + privateElements.getExposedKeys()); + assertContains(privateElements.getExposedSource(Key.get(String.class, named("b"))).toString(), + PrivateModuleTest.class.getName(), getDeclaringSourcePart(getClass())); + Injector privateInjector = privateElements.getInjector(); + assertEquals("private", privateInjector.getInstance(Key.get(String.class, Names.named("a")))); + } + + public void testParentBindsSomethingInPrivate() { + try { + Guice.createInjector(new FailingModule()); + fail(); + } catch(CreationException expected) { + assertEquals(1, expected.getErrorMessages().size()); + assertContains(expected.toString(), + "Unable to create binding for java.util.List.", + "It was already configured on one or more child injectors or private modules", + "bound at " + FailingPrivateModule.class.getName() + ".configure(", + asModuleChain(FailingModule.class, ManyPrivateModules.class, FailingPrivateModule.class), + "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(", + asModuleChain( + FailingModule.class, ManyPrivateModules.class, SecondFailingPrivateModule.class), + "If it was in a PrivateModule, did you forget to expose the binding?", + "at " + FailingModule.class.getName() + ".configure("); + } + } + + public void testParentBindingToPrivateLinkedJitBinding() { + Injector injector = Guice.createInjector(new ManyPrivateModules()); + try { + injector.getBinding(Key.get(Types.providerOf(List.class))); + fail(); + } catch(ConfigurationException expected) { + assertEquals(1, expected.getErrorMessages().size()); + assertContains(expected.toString(), + "Unable to create binding for com.google.inject.Provider.", + "It was already configured on one or more child injectors or private modules", + "bound at " + FailingPrivateModule.class.getName() + ".configure(", + asModuleChain(ManyPrivateModules.class, FailingPrivateModule.class), + "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(", + asModuleChain(ManyPrivateModules.class, SecondFailingPrivateModule.class), + "If it was in a PrivateModule, did you forget to expose the binding?", + "while locating com.google.inject.Provider"); + } + } + + public void testParentBindingToPrivateJitBinding() { + Injector injector = Guice.createInjector(new ManyPrivateModules()); + try { + injector.getBinding(PrivateFoo.class); + fail(); + } catch(ConfigurationException expected) { + assertEquals(1, expected.getErrorMessages().size()); + assertContains(expected.toString(), + "Unable to create binding for " + PrivateFoo.class.getName(), + "It was already configured on one or more child injectors or private modules", + "(bound by a just-in-time binding)", + "If it was in a PrivateModule, did you forget to expose the binding?", + "while locating " + PrivateFoo.class.getName()); + } + } + + private static class FailingModule extends AbstractModule { + @Override protected void configure() { + bind(Collection.class).to(List.class); + install(new ManyPrivateModules()); + } + } + + private static class ManyPrivateModules extends AbstractModule { + @Override protected void configure() { + // make sure duplicate sources are collapsed + install(new FailingPrivateModule()); + install(new FailingPrivateModule()); + // but additional sources are listed + install(new SecondFailingPrivateModule()); + } + } + + private static class FailingPrivateModule extends PrivateModule { + @Override protected void configure() { + bind(List.class).toInstance(new ArrayList()); + + // Add the Provider binding, created just-in-time, + // to make sure our linked JIT bindings have the correct source. + getProvider(Key.get(Types.providerOf(List.class))); + + // Request a JIT binding for PrivateFoo, which can only + // be created in the private module because it depends + // on List. + getProvider(PrivateFoo.class); + } + } + + /** A second class, so we can see another name in the source list. */ + private static class SecondFailingPrivateModule extends PrivateModule { + @Override protected void configure() { + bind(List.class).toInstance(new ArrayList()); + + // Add the Provider binding, created just-in-time, + // to make sure our linked JIT bindings have the correct source. + getProvider(Key.get(Types.providerOf(List.class))); + + // Request a JIT binding for PrivateFoo, which can only + // be created in the private module because it depends + // on List. + getProvider(PrivateFoo.class); + } + } + + private static class PrivateFoo { + @Inject List list; + } +} diff --git a/src/test/java/com/google/inject/ProviderInjectionTest.java b/src/test/java/com/google/inject/ProviderInjectionTest.java new file mode 100644 index 0000000..755ff97 --- /dev/null +++ b/src/test/java/com/google/inject/ProviderInjectionTest.java @@ -0,0 +1,187 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.name.Names.named; + +import com.google.inject.name.Named; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ProviderInjectionTest extends TestCase { + + public void testProviderInjection() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Bar.class); + bind(SampleSingleton.class).in(Scopes.SINGLETON); + } + }); + + Foo foo = injector.getInstance(Foo.class); + + Bar bar = foo.barProvider.get(); + assertNotNull(bar); + assertNotSame(bar, foo.barProvider.get()); + + SampleSingleton singleton = foo.singletonProvider.get(); + assertNotNull(singleton); + assertSame(singleton, foo.singletonProvider.get()); + } + + /** Test for bug 155. */ + public void testProvidersAreInjectedWhenBound() { + Module m = new AbstractModule() { + @Override + protected void configure() { + bind(Bar.class).toProvider(new Provider() { + @SuppressWarnings("unused") + @Inject void cantBeCalled(Baz baz) { + fail("Can't have called this method since Baz is not bound."); + } + public Bar get() { + return new Bar() {}; + } + }); + } + }; + + try { + Guice.createInjector(m); + fail("Should have thrown a CreationException"); + } + catch (CreationException expected) { + } + } + + /** + * When custom providers are used at injector creation time, they should be + * injected before use. In this testcase, we verify that a provider for + * List.class is injected before it is used. + */ + public void testProvidersAreInjectedBeforeTheyAreUsed() { + Injector injector = Guice.createInjector(new AbstractModule() { + public void configure() { + // should bind String to "[true]" + bind(String.class).toProvider(new Provider() { + private String value; + @Inject void initialize(List list) { + value = list.toString(); + } + public String get() { + return value; + } + }); + + // should bind List to [true] + bind(List.class).toProvider(new Provider() { + @Inject Boolean injectedYet = Boolean.FALSE; + public List get() { + return Arrays.asList(injectedYet); + } + }); + + // should bind Boolean to true + bind(Boolean.class).toInstance(Boolean.TRUE); + } + }); + + assertEquals("Providers not injected before use", + "[true]", + injector.getInstance(String.class)); + } + + /** + * This test ensures that regardless of binding order, instances are injected + * before they are used. It injects mutable Count objects and records their + * value at the time that they're injected. + */ + public void testCreationTimeInjectionOrdering() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + // instance injection + bind(Count.class).annotatedWith(named("a")).toInstance(new Count(0) { + @Inject void initialize(@Named("b") Count bCount) { + value = bCount.value + 1; + } + }); + + // provider injection + bind(Count.class).annotatedWith(named("b")).toProvider(new Provider() { + Count count; + @Inject void initialize(@Named("c") Count cCount) { + count = new Count(cCount.value + 2); + } + public Count get() { + return count; + } + }); + + // field and method injection, fields first + bind(Count.class).annotatedWith(named("c")).toInstance(new Count(0) { + @Inject @Named("d") Count dCount; + @Inject void initialize(@Named("e") Count eCount) { + value = dCount.value + eCount.value + 4; + } + }); + + // static injection + requestStaticInjection(StaticallyInjectable.class); + + bind(Count.class).annotatedWith(named("d")).toInstance(new Count(8)); + bind(Count.class).annotatedWith(named("e")).toInstance(new Count(16)); + } + }); + + assertEquals(28, injector.getInstance(Key.get(Count.class, named("c"))).value); + assertEquals(30, injector.getInstance(Key.get(Count.class, named("b"))).value); + assertEquals(31, injector.getInstance(Key.get(Count.class, named("a"))).value); + assertEquals(28, StaticallyInjectable.cCountAtInjectionTime); + } + + static class Count { + int value; + Count(int value) { + this.value = value; + } + } + + static class StaticallyInjectable { + static int cCountAtInjectionTime; + @Inject static void initialize(@Named("c") Count cCount) { + cCountAtInjectionTime = cCount.value; + } + } + + static class Foo { + @Inject Provider barProvider; + @Inject Provider singletonProvider; + } + + static class Bar {} + + static class SampleSingleton {} + + interface Baz { } + +} diff --git a/src/test/java/com/google/inject/ProvisionExceptionTest.java b/src/test/java/com/google/inject/ProvisionExceptionTest.java new file mode 100644 index 0000000..f95eea6 --- /dev/null +++ b/src/test/java/com/google/inject/ProvisionExceptionTest.java @@ -0,0 +1,351 @@ +/** + * Copyright (C) 2007 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +@SuppressWarnings("UnusedDeclaration") +public class ProvisionExceptionTest extends TestCase { + + public void testExceptionsCollapsed() { + try { + Guice.createInjector().getInstance(A.class); + fail(); + } catch (ProvisionException e) { + assertTrue(e.getCause() instanceof UnsupportedOperationException); + assertContains(e.getMessage(), "Error injecting constructor", + "for parameter 0 at com.google.inject.ProvisionExceptionTest$C.setD", + "for field at com.google.inject.ProvisionExceptionTest$B.c", + "for parameter 0 at com.google.inject.ProvisionExceptionTest$A"); + } + } + + /** + * There's a pass-through of user code in the scope. We want exceptions thrown by Guice to be + * limited to a single exception, even if it passes through user code. + */ + public void testExceptionsCollapsedWithScopes() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(B.class).in(Scopes.SINGLETON); + } + }).getInstance(A.class); + fail(); + } catch (ProvisionException e) { + assertTrue(e.getCause() instanceof UnsupportedOperationException); + assertFalse(e.getMessage().contains("custom provider")); + assertContains(e.getMessage(), "Error injecting constructor", + "for parameter 0 at com.google.inject.ProvisionExceptionTest$C.setD", + "for field at com.google.inject.ProvisionExceptionTest$B.c", + "for parameter 0 at com.google.inject.ProvisionExceptionTest$A"); + } + } + + public void testMethodInjectionExceptions() { + try { + Guice.createInjector().getInstance(E.class); + fail(); + } catch (ProvisionException e) { + assertTrue(e.getCause() instanceof UnsupportedOperationException); + assertContains(e.getMessage(), "Error injecting method", + "at " + E.class.getName() + ".setObject("); + } + } + + public void testBindToProviderInstanceExceptions() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(D.class).toProvider(new DProvider()); + } + }).getInstance(D.class); + fail(); + } catch (ProvisionException e) { + assertTrue(e.getCause() instanceof UnsupportedOperationException); + assertContains(e.getMessage(), + "1) Error in custom provider, java.lang.UnsupportedOperationException", + "at " + ProvisionExceptionTest.class.getName(), + getDeclaringSourcePart(getClass())); + } + } + + /** + * This test demonstrates that if the user throws a ProvisionException, we wrap it to add context. + */ + public void testProvisionExceptionsAreWrappedForBindToType() { + try { + Guice.createInjector().getInstance(F.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), "1) User Exception", + "at " + F.class.getName() + ".("); + } + } + + public void testProvisionExceptionsAreWrappedForBindToProviderType() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(F.class).toProvider(FProvider.class); + } + }).getInstance(F.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), "1) User Exception", + "while locating ", FProvider.class.getName(), + "while locating ", F.class.getName()); + } + } + + public void testProvisionExceptionsAreWrappedForBindToProviderInstance() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(F.class).toProvider(new FProvider()); + } + }).getInstance(F.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), "1) User Exception", + "at " + ProvisionExceptionTest.class.getName(), + getDeclaringSourcePart(getClass())); + } + } + + public void testProvisionExceptionIsSerializable() throws IOException { + try { + Guice.createInjector().getInstance(A.class); + fail(); + } catch (ProvisionException expected) { + //ProvisionException reserialized = reserialize(expected); + /*assertContains(reserialized.getMessage(), + "1) Error injecting constructor, java.lang.UnsupportedOperationException", + "at com.google.inject.ProvisionExceptionTest$RealD.()", + "at Key[type=com.google.inject.ProvisionExceptionTest$RealD, annotation=[none]]", + "@com.google.inject.ProvisionExceptionTest$C.setD()[0]", + "at Key[type=com.google.inject.ProvisionExceptionTest$C, annotation=[none]]", + "@com.google.inject.ProvisionExceptionTest$B.c", + "at Key[type=com.google.inject.ProvisionExceptionTest$B, annotation=[none]]", + "@com.google.inject.ProvisionExceptionTest$A.()[0]", + "at Key[type=com.google.inject.ProvisionExceptionTest$A, annotation=[none]]");*/ + } + } + + public void testMultipleCauses() { + try { + Guice.createInjector().getInstance(G.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error injecting method, java.lang.IllegalArgumentException", + "Caused by: java.lang.IllegalArgumentException: java.lang.UnsupportedOperationException", + "Caused by: java.lang.UnsupportedOperationException: Unsupported", + "2) Error injecting method, java.lang.NullPointerException: can't inject second either", + "Caused by: java.lang.NullPointerException: can't inject second either", + "2 errors"); + } + } + + public void testInjectInnerClass() throws Exception { + Injector injector = Guice.createInjector(); + try { + injector.getInstance(InnerClass.class); + fail(); + } catch (Exception expected) { + assertContains(expected.getMessage(), + "Injecting into inner classes is not supported.", + "while locating " + InnerClass.class.getName()); + } + } + + public void testInjectLocalClass() throws Exception { + class LocalClass {} + + Injector injector = Guice.createInjector(); + try { + injector.getInstance(LocalClass.class); + fail(); + } catch (Exception expected) { + assertContains(expected.getMessage(), + "Injecting into inner classes is not supported.", + "while locating " + LocalClass.class.getName()); + } + } + + public void testBindingAnnotationsOnMethodsAndConstructors() { + try { + Injector injector = Guice.createInjector(); + injector.getInstance(MethodWithBindingAnnotation.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), MethodWithBindingAnnotation.class.getName() + + ".injectMe() is annotated with @", Green.class.getName() + "(), ", + "but binding annotations should be applied to its parameters instead.", + "while locating " + MethodWithBindingAnnotation.class.getName()); + } + + try { + Guice.createInjector().getInstance(ConstructorWithBindingAnnotation.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), ConstructorWithBindingAnnotation.class.getName() + + ".() is annotated with @", Green.class.getName() + "(), ", + "but binding annotations should be applied to its parameters instead.", + "at " + ConstructorWithBindingAnnotation.class.getName() + ".class", + "while locating " + ConstructorWithBindingAnnotation.class.getName()); + } + } + + public void testBindingAnnotationWarningForScala() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).annotatedWith(Green.class).toInstance("lime!"); + } + }); + injector.getInstance(LikeScala.class); + } + + public void testLinkedBindings() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(D.class).to(RealD.class); + } + }); + + try { + injector.getInstance(D.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "at " + RealD.class.getName() + ".(", + "while locating " + RealD.class.getName(), + "while locating " + D.class.getName()); + } + } + + public void testProviderKeyBindings() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(D.class).toProvider(DProvider.class); + } + }); + + try { + injector.getInstance(D.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "while locating " + DProvider.class.getName(), + "while locating " + D.class.getName()); + } + } + + private class InnerClass {} + + static class A { + @Inject + A(B b) { } + } + static class B { + @Inject C c; + } + static class C { + @Inject + void setD(RealD d) { } + } + static class E { + @Inject void setObject(Object o) { + throw new UnsupportedOperationException(); + } + } + + static class MethodWithBindingAnnotation { + @Inject @Green void injectMe(String greenString) {} + } + + static class ConstructorWithBindingAnnotation { + // Suppress compiler errors by the error-prone checker InjectedConstructorAnnotations, + // which catches injected constructors with binding annotations. + @SuppressWarnings("InjectedConstructorAnnotations") + @Inject @Green ConstructorWithBindingAnnotation(String greenString) {} + } + + /** + * In Scala, fields automatically get accessor methods with the same name. So we don't do + * misplaced-binding annotation detection if the offending method has a matching field. + */ + static class LikeScala { + @Inject @Green String green; + @Inject @Green String green() { return green; } + } + + @Retention(RUNTIME) + @Target({ FIELD, PARAMETER, CONSTRUCTOR, METHOD }) + @BindingAnnotation + @interface Green {} + + interface D {} + + static class RealD implements D { + @Inject RealD() { + throw new UnsupportedOperationException(); + } + } + + static class DProvider implements Provider { + public D get() { + throw new UnsupportedOperationException(); + } + } + + static class F { + @Inject public F() { + throw new ProvisionException("User Exception", new RuntimeException()); + } + } + + static class FProvider implements Provider { + public F get() { + return new F(); + } + } + + static class G { + @Inject void injectFirst() { + throw new IllegalArgumentException(new UnsupportedOperationException("Unsupported")); + } + @Inject void injectSecond() { + throw new NullPointerException("can't inject second either"); + } + } +} diff --git a/src/test/java/com/google/inject/ProvisionExceptionsTest.java b/src/test/java/com/google/inject/ProvisionExceptionsTest.java new file mode 100644 index 0000000..23c323c --- /dev/null +++ b/src/test/java/com/google/inject/ProvisionExceptionsTest.java @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2010 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; + +import com.google.inject.internal.Errors; +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import junit.framework.TestCase; + +import java.io.IOException; + +/** + * Tests that ProvisionExceptions are readable and clearly indicate to the user what went wrong with + * their code. + * + * @author sameb@google.com (Sam Berlin) + */ +public class ProvisionExceptionsTest extends TestCase { + + public void testConstructorRuntimeException() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(Names.named("runtime")).to(true); + bind(Exploder.class).to(Explosion.class); + bind(Tracer.class).to(TracerImpl.class); + } + }); + try { + injector.getInstance(Tracer.class); + fail(); + } catch(ProvisionException pe) { + // Make sure our initial error message gives the user exception. + Asserts.assertContains(pe.getMessage(), + "1) Error injecting constructor", "java.lang.IllegalStateException: boom!"); + assertEquals(1, pe.getErrorMessages().size()); + assertEquals(IllegalStateException.class, pe.getCause().getClass()); + assertEquals(IllegalStateException.class, Errors.getOnlyCause(pe.getErrorMessages()).getClass()); + } + } + + public void testConstructorCheckedException() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(Names.named("runtime")).to(false); + bind(Exploder.class).to(Explosion.class); + bind(Tracer.class).to(TracerImpl.class); + } + }); + try { + injector.getInstance(Tracer.class); + fail(); + } catch(ProvisionException pe) { + // Make sure our initial error message gives the user exception. + Asserts.assertContains(pe.getMessage(), + "1) Error injecting constructor", "java.io.IOException: boom!"); + assertEquals(1, pe.getErrorMessages().size()); + assertEquals(IOException.class, pe.getCause().getClass()); + assertEquals(IOException.class, Errors.getOnlyCause(pe.getErrorMessages()).getClass()); + } + } + + public void testCustomProvidersRuntimeException() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Exploder.class).toProvider(new Provider() { + public Exploder get() { + return Explosion.createRuntime(); + } + }); + bind(Tracer.class).to(TracerImpl.class); + } + }); + try { + injector.getInstance(Tracer.class); + fail(); + } catch(ProvisionException pe) { + // Make sure our initial error message gives the user exception. + Asserts.assertContains(pe.getMessage(), + "1) Error in custom provider", "java.lang.IllegalStateException: boom!"); + assertEquals(1, pe.getErrorMessages().size()); + assertEquals(IllegalStateException.class, pe.getCause().getClass()); + assertEquals(IllegalStateException.class, Errors.getOnlyCause(pe.getErrorMessages()).getClass()); + } + } + + public void testProviderMethodRuntimeException() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Tracer.class).to(TracerImpl.class); + } + @Provides Exploder exploder() { + return Explosion.createRuntime(); + } + }); + try { + injector.getInstance(Tracer.class); + fail(); + } catch(ProvisionException pe) { + // Make sure our initial error message gives the user exception. + Asserts.assertContains(pe.getMessage(), + "1) Error in custom provider", "java.lang.IllegalStateException: boom!"); + assertEquals(1, pe.getErrorMessages().size()); + assertEquals(IllegalStateException.class, pe.getCause().getClass()); + assertEquals(IllegalStateException.class, Errors.getOnlyCause(pe.getErrorMessages()).getClass()); + } + } + + public void testProviderMethodCheckedException() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Tracer.class).to(TracerImpl.class); + } + @Provides Exploder exploder() throws IOException { + return Explosion.createChecked(); + } + }); + try { + injector.getInstance(Tracer.class); + fail(); + } catch(ProvisionException pe) { + pe.printStackTrace(); + // Make sure our initial error message gives the user exception. + Asserts.assertContains(pe.getMessage(), + "1) Error in custom provider", "java.io.IOException: boom!"); + assertEquals(1, pe.getErrorMessages().size()); + assertEquals(IOException.class, pe.getCause().getClass()); + assertEquals(IOException.class, Errors.getOnlyCause(pe.getErrorMessages()).getClass()); + } + } + + private static interface Exploder {} + public static class Explosion implements Exploder { + @Inject public Explosion(@Named("runtime") boolean runtime) throws IOException { + if(runtime) { + throw new IllegalStateException("boom!"); + } else { + throw new IOException("boom!"); + } + } + + public static Explosion createRuntime() { + try { + return new Explosion(true); + } catch(IOException iox) { + throw new RuntimeException(); + } + } + + public static Explosion createChecked() throws IOException { + return new Explosion(false); + } + } + private static interface Tracer {} + private static class TracerImpl implements Tracer { + @Inject TracerImpl(Exploder explosion) { + } + } +} diff --git a/src/test/java/com/google/inject/ProvisionListenerTest.java b/src/test/java/com/google/inject/ProvisionListenerTest.java new file mode 100644 index 0000000..6e1be84 --- /dev/null +++ b/src/test/java/com/google/inject/ProvisionListenerTest.java @@ -0,0 +1,796 @@ +/** + * Copyright (C) 2011 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; + +import static com.google.common.collect.ImmutableList.of; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.name.Names.named; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; +import com.google.inject.matcher.Matchers; +import com.google.inject.name.Named; +import com.google.inject.spi.DependencyAndSource; +import com.google.inject.spi.InstanceBinding; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.util.Providers; + +import junit.framework.TestCase; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Tests for {@link Binder#bindListener(Matcher, ProvisionListener...)} + * + * @author sameb@google.com (Sam Berlin) + */ +// TODO(sameb): Add some tests for private modules & child injectors. +public class ProvisionListenerTest extends TestCase { + + public void testExceptionInListenerBeforeProvisioning() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new FailBeforeProvision()); + } + }); + try { + injector.getInstance(Foo.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error notifying ProvisionListener " + FailBeforeProvision.class.getName() + + " of " + Foo.class.getName(), + "Reason: java.lang.RuntimeException: boo", + "while locating " + Foo.class.getName()); + assertEquals("boo", pe.getCause().getMessage()); + } + } + + public void testExceptionInListenerAfterProvisioning() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new FailAfterProvision()); + } + }); + try { + injector.getInstance(Foo.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error notifying ProvisionListener " + FailAfterProvision.class.getName() + + " of " + Foo.class.getName(), + "Reason: java.lang.RuntimeException: boo", + "while locating " + Foo.class.getName()); + assertEquals("boo", pe.getCause().getMessage()); + } + } + + public void testExceptionInProvisionExplicitlyCalled() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new JustProvision()); + } + }); + try { + injector.getInstance(FooBomb.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail", + " at " + FooBomb.class.getName(), + " while locating " + FooBomb.class.getName()); + assertEquals("Retry, Abort, Fail", pe.getCause().getMessage()); + } + } + + public void testExceptionInProvisionAutomaticallyCalled() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new NoProvision()); + } + }); + try { + injector.getInstance(FooBomb.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail", + " at " + FooBomb.class.getName(), + " while locating " + FooBomb.class.getName()); + assertEquals("Retry, Abort, Fail", pe.getCause().getMessage()); + } + } + + public void testExceptionInFieldProvision() throws Exception { + final CountAndCaptureExceptionListener listener = new CountAndCaptureExceptionListener(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(new AbstractMatcher>() { + @Override public boolean matches(Binding binding) { + return binding.getKey().getRawType().equals(DependsOnFooBombInField.class); + } + }, listener); + } + }); + assertEquals(0, listener.beforeProvision); + String expectedMsg = null; + try { + injector.getInstance(DependsOnFooBombInField.class); + fail(); + } catch (ProvisionException expected) { + assertEquals(1, expected.getErrorMessages().size()); + expectedMsg = expected.getMessage(); + assertContains(listener.capture.get().getMessage(), + "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail", + " at " + FooBomb.class.getName(), + " while locating " + FooBomb.class.getName(), + " while locating " + DependsOnFooBombInField.class.getName()); + } + assertEquals(1, listener.beforeProvision); + assertEquals(expectedMsg, listener.capture.get().getMessage()); + assertEquals(0, listener.afterProvision); + } + + public void testExceptionInCxtorProvision() throws Exception { + final CountAndCaptureExceptionListener listener = new CountAndCaptureExceptionListener(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(new AbstractMatcher>() { + @Override public boolean matches(Binding binding) { + return binding.getKey().getRawType().equals(DependsOnFooBombInCxtor.class); + } + }, listener); + } + }); + assertEquals(0, listener.beforeProvision); + String expectedMsg = null; + try { + injector.getInstance(DependsOnFooBombInCxtor.class); + fail(); + } catch (ProvisionException expected) { + assertEquals(1, expected.getErrorMessages().size()); + expectedMsg = expected.getMessage(); + assertContains(listener.capture.get().getMessage(), + "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail", + " at " + FooBomb.class.getName(), + " while locating " + FooBomb.class.getName(), + " while locating " + DependsOnFooBombInCxtor.class.getName()); + } + assertEquals(1, listener.beforeProvision); + assertEquals(expectedMsg, listener.capture.get().getMessage()); + assertEquals(0, listener.afterProvision); + } + + public void testListenerCallsProvisionTwice() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new ProvisionTwice()); + } + }); + try { + injector.getInstance(Foo.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error notifying ProvisionListener " + ProvisionTwice.class.getName() + + " of " + Foo.class.getName(), + "Reason: java.lang.IllegalStateException: Already provisioned in this listener.", + "while locating " + Foo.class.getName()); + assertEquals("Already provisioned in this listener.", pe.getCause().getMessage()); + } + } + + public void testCachedInScopePreventsProvisionNotify() { + final Counter count1 = new Counter(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), count1); + bind(Foo.class).in(Scopes.SINGLETON); + } + }); + Foo foo = injector.getInstance(Foo.class); + assertNotNull(foo); + assertEquals(1, count1.count); + + // not notified the second time because nothing is provisioned + // (it's cached in the scope) + count1.count = 0; + assertSame(foo, injector.getInstance(Foo.class)); + assertEquals(0, count1.count); + } + + public void testCombineAllBindListenerCalls() { + final Counter count1 = new Counter(); + final Counter count2 = new Counter(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), count1); + bindListener(Matchers.any(), count2); + } + }); + assertNotNull(injector.getInstance(Foo.class)); + assertEquals(1, count1.count); + assertEquals(1, count2.count); + } + + public void testNotifyEarlyListenersIfFailBeforeProvision() { + final Counter count1 = new Counter(); + final Counter count2 = new Counter(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), count1, new FailBeforeProvision(), count2); + } + }); + try { + injector.getInstance(Foo.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error notifying ProvisionListener " + FailBeforeProvision.class.getName() + + " of " + Foo.class.getName(), + "Reason: java.lang.RuntimeException: boo", + "while locating " + Foo.class.getName()); + assertEquals("boo", pe.getCause().getMessage()); + + assertEquals(1, count1.count); + assertEquals(0, count2.count); + } + } + + public void testNotifyLaterListenersIfFailAfterProvision() { + final Counter count1 = new Counter(); + final Counter count2 = new Counter(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), count1, new FailAfterProvision(), count2); + } + }); + try { + injector.getInstance(Foo.class); + fail(); + } catch(ProvisionException pe) { + assertEquals(1, pe.getErrorMessages().size()); + assertContains(pe.getMessage(), + "1) Error notifying ProvisionListener " + FailAfterProvision.class.getName() + + " of " + Foo.class.getName(), + "Reason: java.lang.RuntimeException: boo", + "while locating " + Foo.class.getName()); + assertEquals("boo", pe.getCause().getMessage()); + + assertEquals(1, count1.count); + assertEquals(1, count2.count); + } + } + + public void testNotifiedKeysOfAllBindTypes() { + final Capturer capturer = new Capturer(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), capturer); + bind(Foo.class).annotatedWith(named("pk")).toProvider(FooP.class); + try { + bind(Foo.class).annotatedWith(named("cxtr")).toConstructor(Foo.class.getDeclaredConstructor()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + bind(LinkedFoo.class).to(Foo.class); + bind(Interface.class).toInstance(new Implementation()); + bindConstant().annotatedWith(named("constant")).to("MyConstant"); + } + + @Provides @Named("pi") Foo provideFooBar() { + return new Foo(); + } + }); + + // toInstance & constant bindings are notified in random order, at the very beginning. + assertEquals( + ImmutableSet.of(Key.get(Interface.class), Key.get(String.class, named("constant"))), + capturer.getAsSetAndClear()); + + // simple binding + assertNotNull(injector.getInstance(Foo.class)); + assertEquals(of(Key.get(Foo.class)), capturer.getAndClear()); + + // provider key binding -- notifies about provider & the object, always + assertNotNull(injector.getInstance(Key.get(Foo.class, named("pk")))); + assertEquals(of(Key.get(FooP.class), Key.get(Foo.class, named("pk"))), capturer.getAndClear()); + assertNotNull(injector.getInstance(Key.get(Foo.class, named("pk")))); + assertEquals(of(Key.get(FooP.class), Key.get(Foo.class, named("pk"))), capturer.getAndClear()); + + // JIT provider key binding -- notifies about provider & the object, always + assertNotNull(injector.getInstance(JitFoo2.class)); + assertEquals(of(Key.get(JitFoo2P.class), Key.get(JitFoo2.class)), capturer.getAndClear()); + assertNotNull(injector.getInstance(JitFoo2.class)); + assertEquals(of(Key.get(JitFoo2P.class), Key.get(JitFoo2.class)), capturer.getAndClear()); + + // provider instance binding -- just the object (not the provider) + assertNotNull(injector.getInstance(Key.get(Foo.class, named("pi")))); + assertEquals(of(Key.get(Foo.class, named("pi"))), capturer.getAndClear()); + + // toConstructor binding + assertNotNull(injector.getInstance(Key.get(Foo.class, named("cxtr")))); + assertEquals(of(Key.get(Foo.class, named("cxtr"))), capturer.getAndClear()); + + // linked binding -- notifies about the target (that's what's provisioned), not the link + assertNotNull(injector.getInstance(LinkedFoo.class)); + assertEquals(of(Key.get(Foo.class)), capturer.getAndClear()); + + // JIT linked binding -- notifies about the target (that's what's provisioned), not the link + assertNotNull(injector.getInstance(JitFoo.class)); + assertEquals(of(Key.get(Foo.class)), capturer.getAndClear()); + } + + public void testSingletonMatcher() { + final Counter counter = new Counter(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(new AbstractMatcher>() { + @Override + public boolean matches(Binding t) { + return Scopes.isSingleton(t); + } + }, counter); + } + }); + assertEquals(0, counter.count); + // no increment for getting Many. + injector.getInstance(Many.class); + assertEquals(0, counter.count); + // but an increment for getting Sole, since it's a singleton. + injector.getInstance(Sole.class); + assertEquals(1, counter.count); + } + + public void testCallingBindingDotGetProviderDotGet() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new ProvisionListener() { + @Override + public void onProvision(ProvisionInvocation provision) { + provision.getBinding().getProvider().get(); // AGH! + } + }); + } + }); + + try { + injector.getInstance(Sole.class); + fail(); + } catch(ProvisionException expected) { + // We don't really care what kind of error you get, we only care you get an error. + } + + try { + injector.getInstance(Many.class); + fail(); + } catch(ProvisionException expected) { + // We don't really care what kind of error you get, we only care you get an error. + } + } + + interface Interface {} + class Implementation implements Interface {} + + @Singleton static class Sole {} + static class Many {} + + @ImplementedBy(Foo.class) static interface JitFoo {} + @ProvidedBy(JitFoo2P.class) static class JitFoo2 {} + static interface LinkedFoo {} + static class Foo implements JitFoo, LinkedFoo {} + static class FooP implements Provider { + public Foo get() { + return new Foo(); + } + } + static class JitFoo2P implements Provider { + public JitFoo2 get() { + return new JitFoo2(); + } + } + + static class FooBomb { + FooBomb() { + throw new RuntimeException("Retry, Abort, Fail"); + } + } + + static class DependsOnFooBombInField { + @Inject FooBomb fooBomb; + } + + static class DependsOnFooBombInCxtor { + @Inject DependsOnFooBombInCxtor(FooBomb fooBomb) {} + } + + private static class Counter implements ProvisionListener { + int count = 0; + public void onProvision(ProvisionInvocation provision) { + count++; + } + } + + private static class CountAndCaptureExceptionListener implements ProvisionListener { + int beforeProvision = 0; + int afterProvision = 0; + AtomicReference capture = new AtomicReference(); + public void onProvision(ProvisionInvocation provision) { + beforeProvision++; + try { + provision.provision(); + } catch (RuntimeException re) { + capture.set(re); + throw re; + } + afterProvision++; + } + } + + private static class Capturer implements ProvisionListener { + List keys = Lists.newArrayList(); + public void onProvision(ProvisionInvocation provision) { + keys.add(provision.getBinding().getKey()); + T provisioned = provision.provision(); + // InstanceBindings are the only kind of binding where the key can + // be an instanceof the provisioned, because it isn't linked to any + // direct implementation. I guess maybe it'd also be possible + // with a toConstructor binding... but we don't use that in our tests. + if (provision.getBinding() instanceof InstanceBinding) { + Class expected = provision.getBinding().getKey().getRawType(); + assertTrue("expected instanceof: " + expected + ", but was: " + provisioned, + expected.isInstance(provisioned)); + } else { + assertEquals(provision.getBinding().getKey().getRawType(), provisioned.getClass()); + } + } + + Set getAsSetAndClear() { + Set copy = ImmutableSet.copyOf(keys); + keys.clear(); + return copy; + } + + List getAndClear() { + List copy = ImmutableList.copyOf(keys); + keys.clear(); + return copy; + } + } + + private static class FailBeforeProvision implements ProvisionListener { + public void onProvision(ProvisionInvocation provision) { + throw new RuntimeException("boo"); + } + } + private static class FailAfterProvision implements ProvisionListener { + public void onProvision(ProvisionInvocation provision) { + provision.provision(); + throw new RuntimeException("boo"); + } + } + private static class JustProvision implements ProvisionListener { + public void onProvision(ProvisionInvocation provision) { + provision.provision(); + } + } + private static class NoProvision implements ProvisionListener { + public void onProvision(ProvisionInvocation provision) { + } + } + private static class ProvisionTwice implements ProvisionListener { + public void onProvision(ProvisionInvocation provision) { + provision.provision(); + provision.provision(); + } + } + + private static class ChainAsserter implements ProvisionListener { + private final List> provisionList; + private final List> expected; + + public ChainAsserter(List> provisionList, Iterable> expected) { + this.provisionList = provisionList; + this.expected = ImmutableList.copyOf(expected); + } + + public void onProvision(ProvisionInvocation provision) { + List> actual = Lists.newArrayList(); + for (DependencyAndSource dep : provision.getDependencyChain()) { + actual.add(dep.getDependency().getKey().getRawType()); + } + assertEquals(expected, actual); + provisionList.add(provision.getBinding().getKey().getRawType()); + } + } + + private static Matcher> keyMatcher(final Class clazz) { + return new AbstractMatcher>() { + @Override + public boolean matches(Binding t) { + return t.getKey().equals(Key.get(clazz)); + } + }; + } + + @SuppressWarnings("unchecked") + public void testDependencyChain() { + final List> pList = Lists.newArrayList(); + final List> totalList = Lists.newArrayList(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Instance.class).toInstance(new Instance()); + bind(B.class).to(BImpl.class); + bind(D.class).toProvider(DP.class); + + bindListener(Matchers.any(), new ProvisionListener() { + public void onProvision(ProvisionInvocation provision) { + totalList.add(provision.getBinding().getKey().getRawType()); + } + }); + + // Build up a list of asserters for our dependency chains. + ImmutableList.Builder> chain = ImmutableList.builder(); + chain.add(Instance.class); + bindListener(keyMatcher(Instance.class), new ChainAsserter(pList, chain.build())); + + chain.add(A.class); + bindListener(keyMatcher(A.class), new ChainAsserter(pList, chain.build())); + + chain.add(B.class).add(BImpl.class); + bindListener(keyMatcher(BImpl.class), new ChainAsserter(pList, chain.build())); + + chain.add(C.class); + bindListener(keyMatcher(C.class), new ChainAsserter(pList, chain.build())); + + // the chain has D before DP even though DP is provisioned & notified first + // because we do DP because of D, and need DP to provision D. + chain.add(D.class).add(DP.class); + bindListener(keyMatcher(D.class), new ChainAsserter(pList, chain.build())); + bindListener(keyMatcher(DP.class), new ChainAsserter(pList, chain.build())); + + chain.add(E.class); + bindListener(keyMatcher(E.class), new ChainAsserter(pList, chain.build())); + + chain.add(F.class); + bindListener(keyMatcher(F.class), new ChainAsserter(pList, chain.build())); + } + @Provides C c(D d) { + return new C() {}; + } + }); + Instance instance = injector.getInstance(Instance.class); + // make sure we're checking all of the chain asserters.. + assertEquals( + of(Instance.class, A.class, BImpl.class, C.class, DP.class, D.class, E.class, F.class), + pList); + // and make sure that nothing else was notified that we didn't expect. + assertEquals(totalList, pList); + } + + public void testModuleRequestInjection() { + final AtomicBoolean notified = new AtomicBoolean(); + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + requestInjection(new Object() { + @Inject Foo foo; + }); + bindListener(Matchers.any(), + new SpecialChecker(Foo.class, getClass().getName() + ".configure(", notified)); + } + }); + assertTrue(notified.get()); + } + + public void testToProviderInstance() { + final AtomicBoolean notified = new AtomicBoolean(); + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Object.class).toProvider(new Provider() { + @Inject Foo foo; + public Object get() { + return null; + } + }); + bindListener(Matchers.any(), + new SpecialChecker(Foo.class, getClass().getName() + ".configure(", notified)); + } + }); + assertTrue(notified.get()); + } + + public void testInjectorInjectMembers() { + final Object object = new Object() { + @Inject Foo foo; + }; + final AtomicBoolean notified = new AtomicBoolean(); + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), + new SpecialChecker(Foo.class, object.getClass().getName(), notified)); + } + }).injectMembers(object); + assertTrue(notified.get()); + } + + private static class SpecialChecker implements ProvisionListener { + private final Class notifyType; + private final String firstSource; + private final AtomicBoolean notified; + + public SpecialChecker(Class notifyType, String firstSource, AtomicBoolean notified) { + this.notifyType = notifyType; + this.firstSource = firstSource; + this.notified = notified; + } + + public void onProvision(ProvisionInvocation provision) { + notified.set(true); + assertEquals(notifyType, provision.getBinding().getKey().getRawType()); + assertEquals(2, provision.getDependencyChain().size()); + + assertNull(provision.getDependencyChain().get(0).getDependency()); + assertContains(provision.getDependencyChain().get(0).getBindingSource(), firstSource); + + assertEquals(notifyType, + provision.getDependencyChain().get(1).getDependency().getKey().getRawType()); + assertContains(provision.getDependencyChain().get(1).getBindingSource(), + notifyType.getName() + ".class("); + } + } + + private static class Instance { + @Inject A a; + } + private static class A { + @Inject A(B b) {} + } + private interface B {} + private static class BImpl implements B { + @Inject void inject(C c) {} + } + private interface C {} + private interface D {} + private static class DP implements Provider { + @Inject Provider ep; + public D get() { + ep.get(); + return new D() {}; + } + } + private static class E { + @SuppressWarnings("unused") + @Inject F f; + } + private static class F { + } + + public void testBindToInjectorWithListeningGivesSaneException() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new Counter()); + bind(Injector.class).toProvider(Providers.of(null)); + } + }); + fail(); + } catch (CreationException ce) { + assertContains( + ce.getMessage(), "Binding to core guice framework type is not allowed: Injector."); + } + } + + public void testProvisionIsNotifiedAfterContextsClear() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new ProvisionListener() { + @Override + public void onProvision(ProvisionInvocation provision) { + Object provisioned = provision.provision(); + if (provisioned instanceof X) { + ((X)provisioned).init(); + } else if (provisioned instanceof Y) { + X.createY = false; + ((Y)provisioned).init(); + } + } + }); + } + }); + + X.createY = true; + X x = injector.getInstance(X.class); + assertNotSame(x, x.y.x); + assertFalse("x.ID: " + x.ID + ", x.y.x.iD: " + x.y.x.ID, x.ID == x.y.x.ID); + } + + private static class X { + final static AtomicInteger COUNTER = new AtomicInteger(); + static boolean createY; + + final int ID = COUNTER.getAndIncrement(); + final Provider yProvider; + Y y; + + @Inject X(Provider yProvider) { + this.yProvider = yProvider; + } + + void init() { + if (createY) { + this.y = yProvider.get(); + } + } + } + + private static class Y { + final Provider xProvider; + X x; + + @Inject Y(Provider xProvider) { + this.xProvider = xProvider; + } + + void init() { + this.x = xProvider.get(); + } + } + + public void testDeDuplicateProvisionListeners() { + final Counter counter = new Counter(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), counter); + bindListener(Matchers.any(), counter); + } + }); + injector.getInstance(Many.class); + assertEquals("ProvisionListener not de-duplicated", 1, counter.count); + } +} diff --git a/src/test/java/com/google/inject/ReflectionTest.java b/src/test/java/com/google/inject/ReflectionTest.java new file mode 100644 index 0000000..610f099 --- /dev/null +++ b/src/test/java/com/google/inject/ReflectionTest.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2006 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; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.spi.ElementSource; + +import junit.framework.TestCase; + +import java.lang.annotation.Retention; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ReflectionTest extends TestCase { + + @Retention(RUNTIME) + @BindingAnnotation @interface I {} + + public void testNormalBinding() throws CreationException { + final Foo foo = new Foo(); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class).toInstance(foo); + } + }); + + Binding fooBinding = injector.getBinding(Key.get(Foo.class)); + assertSame(foo, fooBinding.getProvider().get()); + ElementSource source = (ElementSource) fooBinding.getSource(); + assertNotNull(source.getDeclaringSource()); + assertEquals(Key.get(Foo.class), fooBinding.getKey()); + } + + public void testConstantBinding() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(I.class).to(5); + } + }); + + Binding i = injector.getBinding(Key.get(int.class, I.class)); + assertEquals(5, i.getProvider().get()); + ElementSource source = (ElementSource) i.getSource(); + assertNotNull(source.getDeclaringSource()); + assertEquals(Key.get(int.class, I.class), i.getKey()); + } + + public void testLinkedBinding() throws CreationException { + final Bar bar = new Bar(); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Bar.class).toInstance(bar); + bind(Key.get(Foo.class)).to(Key.get(Bar.class)); + } + }); + + Binding fooBinding = injector.getBinding(Key.get(Foo.class)); + assertSame(bar, fooBinding.getProvider().get()); + ElementSource source = (ElementSource) fooBinding.getSource(); + assertNotNull(source.getDeclaringSource()); + assertEquals(Key.get(Foo.class), fooBinding.getKey()); + } + + static class Foo {} + + static class Bar extends Foo {} +} diff --git a/src/test/java/com/google/inject/RequestInjectionTest.java b/src/test/java/com/google/inject/RequestInjectionTest.java new file mode 100644 index 0000000..e7e225d --- /dev/null +++ b/src/test/java/com/google/inject/RequestInjectionTest.java @@ -0,0 +1,235 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.matcher.Matchers; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; + +import junit.framework.TestCase; + +import java.lang.annotation.Retention; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class RequestInjectionTest extends TestCase { + + @Retention(RUNTIME) + @BindingAnnotation @interface ForField {} + + @Retention(RUNTIME) + @BindingAnnotation @interface ForMethod {} + + @Override + protected void setUp() throws Exception { + super.setUp(); + HasInjections.staticField = 0; + HasInjections.staticMethod = null; + } + + public void testInjectMembers() { + final HasInjections hi = new HasInjections(); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(ForMethod.class).to("test"); + bindConstant().annotatedWith(ForField.class).to(5); + requestInjection(hi); + } + }); + + assertEquals("test", hi.instanceMethod); + assertEquals(5, hi.instanceField); + assertNull(HasInjections.staticMethod); + assertEquals(0, HasInjections.staticField); + } + + public void testInjectStatics() throws CreationException { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(ForMethod.class).to("test"); + bindConstant().annotatedWith(ForField.class).to(5); + requestStaticInjection(HasInjections.class); + } + }); + + assertEquals("test", HasInjections.staticMethod); + assertEquals(5, HasInjections.staticField); + } + + public void testInjectMembersAndStatics() { + final HasInjections hi = new HasInjections(); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(ForMethod.class).to("test"); + bindConstant().annotatedWith(ForField.class).to(5); + requestStaticInjection(HasInjections.class); + requestInjection(hi); + } + }); + + assertEquals("test", hi.instanceMethod); + assertEquals(5, hi.instanceField); + assertEquals("test", HasInjections.staticMethod); + assertEquals(5, HasInjections.staticField); + } + + public void testValidationErrorOnInjectedMembers() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + requestInjection(new NeedsRunnable()); + } + }); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) No implementation for java.lang.Runnable was bound", + "at " + NeedsRunnable.class.getName(), ".runnable("); + } + } + + public void testInjectionErrorOnInjectedMembers() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Runnable.class).toProvider(new Provider() { + public Runnable get() { + throw new UnsupportedOperationException(); + } + }); + requestInjection(new NeedsRunnable()); + } + }); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Error in custom provider, java.lang.UnsupportedOperationException", + "for field at " + NeedsRunnable.class.getName() + ".runnable(", + "at " + getClass().getName(), getDeclaringSourcePart(getClass())); + } + } + + public void testUserExceptionWhileInjectingInstance() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + requestInjection(new BlowsUpOnInject()); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Error injecting method, java.lang.UnsupportedOperationException: Pop", + "at " + BlowsUpOnInject.class.getName() + ".injectInstance("); + } + } + + public void testUserExceptionWhileInjectingStatically() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(BlowsUpOnInject.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Error injecting method, java.lang.UnsupportedOperationException: Snap", + "at " + BlowsUpOnInject.class.getName() + ".injectStatically("); + } + } + + static class NeedsRunnable { + @Inject Runnable runnable; + } + + static class HasInjections { + + @Inject @ForField static int staticField; + @Inject @ForField int instanceField; + + static String staticMethod; + String instanceMethod; + + @Inject static void setStaticMethod(@ForMethod String staticMethod) { + HasInjections.staticMethod = staticMethod; + } + + @Inject void setInstanceS(@ForMethod String instanceS) { + this.instanceMethod = instanceS; + } + } + + static class BlowsUpOnInject { + @Inject void injectInstance() { + throw new UnsupportedOperationException("Pop"); + } + + @Inject static void injectStatically() { + throw new UnsupportedOperationException("Snap"); + } + } + + /* + * Tests that initializables of the same instance don't clobber + * membersInjectors in InitializableReference, so that they ultimately + * can be requested in any order. + */ + public void testEarlyInjectableReferencesWithSameIdentity() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + // Add a listener to trigger all toInstance bindings to get an Initializable. + bindListener(Matchers.any(), new TypeListener() { + @Override + public void hear(TypeLiteral type, TypeEncounter encounter) { + } + }); + + // Bind two different Keys to the IDENTITICAL object + // ORDER MATTERS! We want the String binding to push out the Object one + String fail = new String("better not fail!"); + bind(Object.class).toInstance(fail); + bind(String.class).toInstance(fail); + + // Then try to inject those objects in a requestInjection, + // letting us get into InjectableReference.get before it has + // finished running through all its injections. + // Each of these technically has its own InjectableReference internally. + // ORDER MATTERS!.. because Object is injected first, that InjectableReference + // attempts to process its members injector, but it wasn't initialized, + // because String pushed it out of the queue! + requestInjection(new Object() { + @SuppressWarnings("unused") @Inject Object obj; + @SuppressWarnings("unused") @Inject String str; + }); + } + }); + } +} diff --git a/src/test/java/com/google/inject/RequireAtInjectOnConstructorsTest.java b/src/test/java/com/google/inject/RequireAtInjectOnConstructorsTest.java new file mode 100644 index 0000000..e5f90c2 --- /dev/null +++ b/src/test/java/com/google/inject/RequireAtInjectOnConstructorsTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2012 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; + +import junit.framework.TestCase; + +/** + * Tests for {@link Binder#requireAtInjectOnConstructors()} + * + * @author sameb@google.com (Sam Berlin) + */ +public class RequireAtInjectOnConstructorsTest extends TestCase { + + public void testNoCxtors_explicitBinding() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(NoCxtors.class); + binder().requireAtInjectOnConstructors(); + } + }); + fail(); + } catch (CreationException ce) { + assertEquals(1, ce.getErrorMessages().size()); + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + NoCxtors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure"); + } + } + + public void testNoCxtors_jitBinding() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireAtInjectOnConstructors(); + } + }); + try { + injector.getInstance(NoCxtors.class); + fail(); + } catch (ConfigurationException ce) { + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + NoCxtors.class.getName() + " has no constructors annotated with @Inject", + "while locating " + NoCxtors.class.getName()); + } + } + + public void testNoCxtors_implicitBinding() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Interface.class).to(NoCxtors.class); + binder().requireAtInjectOnConstructors(); + } + }); + fail(); + } catch (CreationException ce) { + assertEquals(1, ce.getErrorMessages().size()); + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + NoCxtors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure"); + } + } + + public void testNoCxtors_inheritedByPrivateModules() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + binder().requireAtInjectOnConstructors(); + install(new PrivateModule() { + @Override + protected void configure() { + bind(NoCxtors.class); + } + }); + } + }); + fail(); + } catch (CreationException ce) { + assertEquals(1, ce.getErrorMessages().size()); + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + NoCxtors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure"); + } + } + + public void testNoCxtors_accumulatesAllErrors() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(NoCxtors.class); + bind(AnotherNoCxtors.class); + binder().requireAtInjectOnConstructors(); + } + }); + fail(); + } catch (CreationException ce) { + assertEquals(2, ce.getErrorMessages().size()); + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + NoCxtors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure", + "2) Explicit @Inject annotations are required on constructors, but " + + AnotherNoCxtors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure"); + } + } + + public void testNoCxtors_separateOptionsForPrivateModules() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(AnotherNoCxtors.class); + install(new PrivateModule() { + @Override + protected void configure() { + binder().requireAtInjectOnConstructors(); + bind(NoCxtors.class); + } + }); + } + }); + fail(); + } catch (CreationException ce) { + // This is testing that the parent module doesn't fail because it isn't included + // in the error message. + assertEquals(1, ce.getErrorMessages().size()); + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + NoCxtors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure"); + } + } + + public void testManyConstructorsButNoneWithAtInject() { + try { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(ManyConstructors.class); + binder().requireAtInjectOnConstructors(); + } + }); + fail(); + } catch (CreationException ce) { + assertEquals(1, ce.getErrorMessages().size()); + Asserts.assertContains(ce.getMessage(), + "1) Explicit @Inject annotations are required on constructors, but " + + ManyConstructors.class.getName() + " has no constructors annotated with @Inject", + "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure"); + } + } + + public void testRequireAtInjectStillAllowsToConstructorBindings() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + try { + bind(ManyConstructors.class) + .toConstructor(ManyConstructors.class.getDeclaredConstructor()); + } catch (Exception e) { + throw new RuntimeException(e); + } + binder().requireAtInjectOnConstructors(); + } + }); + injector.getInstance(ManyConstructors.class); + } + + private static interface Interface {} + private static class NoCxtors implements Interface {} + private static class AnotherNoCxtors {} + private static class ManyConstructors { + @SuppressWarnings("unused") ManyConstructors() {} + @SuppressWarnings("unused") ManyConstructors(String a) {} + @SuppressWarnings("unused") ManyConstructors(int a) {} + } +} diff --git a/src/test/java/com/google/inject/ScopesTest.java b/src/test/java/com/google/inject/ScopesTest.java new file mode 100644 index 0000000..15c2d2e --- /dev/null +++ b/src/test/java/com/google/inject/ScopesTest.java @@ -0,0 +1,1192 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.asModuleChain; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static com.google.inject.name.Names.named; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.name.Named; +import com.google.inject.spi.Element; +import com.google.inject.spi.Elements; +import com.google.inject.spi.PrivateElements; +import com.google.inject.util.Providers; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ScopesTest extends TestCase { + + static final long DEADLOCK_TIMEOUT_SECONDS = 1; + + private final AbstractModule singletonsModule = new AbstractModule() { + @Override protected void configure() { + bind(BoundAsSingleton.class).in(Scopes.SINGLETON); + bind(AnnotatedSingleton.class); + bind(EagerSingleton.class).asEagerSingleton(); + bind(LinkedSingleton.class).to(RealLinkedSingleton.class); + bind(DependsOnJustInTimeSingleton.class); + bind(NotASingleton.class); + bind(ImplementedBySingleton.class).in(Scopes.SINGLETON); + bind(ProvidedBySingleton.class).in(Scopes.SINGLETON); + } + }; + + @Override protected void setUp() throws Exception { + AnnotatedSingleton.nextInstanceId = 0; + BoundAsSingleton.nextInstanceId = 0; + EagerSingleton.nextInstanceId = 0; + RealLinkedSingleton.nextInstanceId = 0; + JustInTimeSingleton.nextInstanceId = 0; + NotASingleton.nextInstanceId = 0; + Implementation.nextInstanceId = 0; + ProvidedBySingleton.nextInstanceId = 0; + ThrowingSingleton.nextInstanceId = 0; + } + + public void testSingletons() { + Injector injector = Guice.createInjector(singletonsModule); + + assertSame( + injector.getInstance(BoundAsSingleton.class), + injector.getInstance(BoundAsSingleton.class)); + + assertSame( + injector.getInstance(AnnotatedSingleton.class), + injector.getInstance(AnnotatedSingleton.class)); + + assertSame( + injector.getInstance(EagerSingleton.class), + injector.getInstance(EagerSingleton.class)); + + assertSame( + injector.getInstance(LinkedSingleton.class), + injector.getInstance(LinkedSingleton.class)); + + assertSame( + injector.getInstance(JustInTimeSingleton.class), + injector.getInstance(JustInTimeSingleton.class)); + + assertNotSame( + injector.getInstance(NotASingleton.class), + injector.getInstance(NotASingleton.class)); + + assertSame( + injector.getInstance(ImplementedBySingleton.class), + injector.getInstance(ImplementedBySingleton.class)); + + assertSame( + injector.getInstance(ProvidedBySingleton.class), + injector.getInstance(ProvidedBySingleton.class)); + } + + public void testJustInTimeAnnotatedSingleton() { + Injector injector = Guice.createInjector(); + + assertSame( + injector.getInstance(AnnotatedSingleton.class), + injector.getInstance(AnnotatedSingleton.class)); + } + + public void testSingletonIsPerInjector() { + assertNotSame( + Guice.createInjector().getInstance(AnnotatedSingleton.class), + Guice.createInjector().getInstance(AnnotatedSingleton.class)); + } + + public void testOverriddingAnnotation() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(AnnotatedSingleton.class).in(Scopes.NO_SCOPE); + } + }); + + assertNotSame( + injector.getInstance(AnnotatedSingleton.class), + injector.getInstance(AnnotatedSingleton.class)); + } + + public void testScopingAnnotationsOnAbstractTypeViaBind() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(A.class).to(AImpl.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + A.class.getName() + " is annotated with " + Singleton.class.getName(), + "but scope annotations are not supported for abstract types.", + "at " + A.class.getName() + ".class(ScopesTest.java:"); + } + } + + @Singleton + interface A {} + static class AImpl implements A {} + + @Retention(RUNTIME) + @interface Component {} + + @Component + @Singleton + interface ComponentAnnotationTest {} + static class ComponentAnnotationTestImpl implements ComponentAnnotationTest {} + + public void testScopingAnnotationsOnAbstractTypeIsValidForComponent() { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(ComponentAnnotationTest.class).to(ComponentAnnotationTestImpl.class); + } + }); + } + + public void testScopingAnnotationsOnAbstractTypeViaImplementedBy() { + try { + Guice.createInjector().getInstance(D.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + D.class.getName() + " is annotated with " + Singleton.class.getName(), + "but scope annotations are not supported for abstract types.", + "at " + D.class.getName() + ".class(ScopesTest.java:"); + } + } + + @Singleton @ImplementedBy(DImpl.class) + interface D {} + static class DImpl implements D {} + + public void testScopingAnnotationsOnAbstractTypeViaProvidedBy() { + try { + Guice.createInjector().getInstance(E.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + E.class.getName() + " is annotated with " + Singleton.class.getName(), + "but scope annotations are not supported for abstract types.", + "at " + E.class.getName() + ".class(ScopesTest.java:"); + } + } + + @Singleton @ProvidedBy(EProvider.class) + interface E {} + static class EProvider implements Provider { + public E get() { + return null; + } + } + + public void testScopeUsedButNotBound() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(B.class).in(CustomScoped.class); + bind(C.class); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) No scope is bound to " + CustomScoped.class.getName(), + "at " + getClass().getName(), getDeclaringSourcePart(getClass()), + "2) No scope is bound to " + CustomScoped.class.getName(), + "at " + C.class.getName() + ".class"); + } + } + + static class B {} + + @CustomScoped + static class C {} + + public void testSingletonsInProductionStage() { + Guice.createInjector(Stage.PRODUCTION, singletonsModule); + + assertEquals(1, AnnotatedSingleton.nextInstanceId); + assertEquals(1, BoundAsSingleton.nextInstanceId); + assertEquals(1, EagerSingleton.nextInstanceId); + assertEquals(1, RealLinkedSingleton.nextInstanceId); + assertEquals(1, JustInTimeSingleton.nextInstanceId); + assertEquals(0, NotASingleton.nextInstanceId); + } + + public void testSingletonsInDevelopmentStage() { + Guice.createInjector(Stage.DEVELOPMENT, singletonsModule); + + assertEquals(0, AnnotatedSingleton.nextInstanceId); + assertEquals(0, BoundAsSingleton.nextInstanceId); + assertEquals(1, EagerSingleton.nextInstanceId); + assertEquals(0, RealLinkedSingleton.nextInstanceId); + assertEquals(0, JustInTimeSingleton.nextInstanceId); + assertEquals(0, NotASingleton.nextInstanceId); + } + + public void testSingletonScopeIsNotSerializable() throws IOException { + Asserts.assertNotSerializable(Scopes.SINGLETON); + } + + public void testNoScopeIsNotSerializable() throws IOException { + Asserts.assertNotSerializable(Scopes.NO_SCOPE); + } + + public void testUnscopedProviderWorksOutsideOfRequestedScope() { + final RememberProviderScope scope = new RememberProviderScope(); + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindScope(CustomScoped.class, scope); + bind(List.class).to(ArrayList.class).in(CustomScoped.class); + } + }); + + injector.getInstance(List.class); + Provider listProvider = scope.providers.get(Key.get(List.class)); + + // this line fails with a NullPointerException because the Providers + // passed to Scope.scope() don't work outside of the scope() method. + assertTrue(listProvider.get() instanceof ArrayList); + } + + static class OuterRuntimeModule extends AbstractModule { + @Override protected void configure() { + install(new InnerRuntimeModule()); + } + } + static class InnerRuntimeModule extends AbstractModule { + @Override protected void configure() { + bindScope(NotRuntimeRetainedScoped.class, Scopes.NO_SCOPE); + } + } + public void testScopeAnnotationWithoutRuntimeRetention() { + try { + Guice.createInjector(new OuterRuntimeModule()); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Please annotate " + NotRuntimeRetainedScoped.class.getName() + + " with @Retention(RUNTIME).", + "at " + InnerRuntimeModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterRuntimeModule.class, InnerRuntimeModule.class)); + } + } + + static class OuterDeprecatedModule extends AbstractModule { + @Override protected void configure() { + install(new InnerDeprecatedModule()); + } + } + static class InnerDeprecatedModule extends AbstractModule { + @Override protected void configure() { + bindScope(Deprecated.class, Scopes.NO_SCOPE); + } + } + public void testBindScopeToAnnotationWithoutScopeAnnotation() { + try { + Guice.createInjector(new OuterDeprecatedModule()); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Please annotate " + Deprecated.class.getName() + " with @ScopeAnnotation.", + "at " + InnerDeprecatedModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterDeprecatedModule.class, InnerDeprecatedModule.class)); + } + } + + static class OuterScopeModule extends AbstractModule { + @Override protected void configure() { + install(new CustomNoScopeModule()); + install(new CustomSingletonModule()); + } + } + static class CustomNoScopeModule extends AbstractModule { + @Override protected void configure() { + bindScope(CustomScoped.class, Scopes.NO_SCOPE); + } + } + static class CustomSingletonModule extends AbstractModule { + @Override protected void configure() { + bindScope(CustomScoped.class, Scopes.SINGLETON); + } + } + + public void testBindScopeTooManyTimes() { + try { + Guice.createInjector(new OuterScopeModule()); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Scope Scopes.NO_SCOPE is already bound to " + CustomScoped.class.getName() + + " at " + CustomNoScopeModule.class.getName() + getDeclaringSourcePart(getClass()), + asModuleChain(OuterScopeModule.class, CustomNoScopeModule.class), + "Cannot bind Scopes.SINGLETON.", + "at " + ScopesTest.class.getName(), getDeclaringSourcePart(getClass()), + asModuleChain(OuterScopeModule.class, CustomSingletonModule.class)); + } + } + + public void testBindDuplicateScope() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindScope(CustomScoped.class, Scopes.SINGLETON); + bindScope(CustomScoped.class, Scopes.SINGLETON); + } + }); + + assertSame( + injector.getInstance(AnnotatedCustomScoped.class), + injector.getInstance(AnnotatedCustomScoped.class)); + } + + public void testDuplicateScopeAnnotations() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindScope(CustomScoped.class, Scopes.NO_SCOPE); + } + }); + + try { + injector.getInstance(SingletonAndCustomScoped.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "1) More than one scope annotation was found: ", + "while locating " + SingletonAndCustomScoped.class.getName()); + } + } + + public void testNullScopedAsASingleton() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() {} + + final Iterator values = Arrays.asList(null, "A").iterator(); + + @Provides @Singleton String provideString() { + return values.next(); + } + }); + + assertNull(injector.getInstance(String.class)); + assertNull(injector.getInstance(String.class)); + assertNull(injector.getInstance(String.class)); + } + + class RememberProviderScope implements Scope { + final Map, Provider> providers = Maps.newHashMap(); + public Provider scope(Key key, Provider unscoped) { + providers.put(key, unscoped); + return unscoped; + } + } + + public void testSingletonAnnotationOnParameterizedType() { + Injector injector = Guice.createInjector(); + assertSame(injector.getInstance(new Key>() {}), + injector.getInstance(new Key>() {})); + assertSame(injector.getInstance(new Key>() {}), + injector.getInstance(new Key>() {})); + } + + @ImplementedBy(Injected.class) public interface In {} + @Singleton public static class Injected implements In {} + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RUNTIME) + @ScopeAnnotation + public @interface CustomScoped {} + + static final Scope CUSTOM_SCOPE = new Scope() { + public Provider scope(Key key, Provider unscoped) { + return Scopes.SINGLETON.scope(key, unscoped); + } + }; + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @ScopeAnnotation + public @interface NotRuntimeRetainedScoped {} + + @CustomScoped + static class AnnotatedCustomScoped {} + + @Singleton + static class AnnotatedSingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + static class BoundAsSingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + static class EagerSingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + interface LinkedSingleton {} + + @Singleton + static class RealLinkedSingleton implements LinkedSingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + static class DependsOnJustInTimeSingleton { + @Inject JustInTimeSingleton justInTimeSingleton; + } + + @Singleton + static class JustInTimeSingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + static class NotASingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + @SuppressWarnings("MoreThanOneScopeAnnotationOnClass") // suppress compiler error for testing + @Singleton + @CustomScoped + static class SingletonAndCustomScoped {} + + @ImplementedBy(Implementation.class) + static interface ImplementedBySingleton {} + + @ProvidedBy(ImplementationProvider.class) + static class ProvidedBySingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + static class Implementation implements ImplementedBySingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + } + + static class ImplementationProvider implements Provider { + public ProvidedBySingleton get() { + return new ProvidedBySingleton(); + } + } + + public void testScopeThatGetsAnUnrelatedObject() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(B.class); + bind(C.class); + ProviderGetScope providerGetScope = new ProviderGetScope(); + requestInjection(providerGetScope); + bindScope(CustomScoped.class, providerGetScope); + } + }); + + injector.getInstance(C.class); + } + + class ProviderGetScope implements Scope { + @Inject Provider bProvider; + + public Provider scope(Key key, final Provider unscoped) { + return new Provider() { + public T get() { + bProvider.get(); + return unscoped.get(); + } + }; + } + } + + public void testIsSingletonPositive() { + final Key a = Key.get(String.class, named("A")); + final Key b = Key.get(String.class, named("B")); + final Key c = Key.get(String.class, named("C")); + final Key d = Key.get(String.class, named("D")); + final Key e = Key.get(String.class, named("E")); + final Key f = Key.get(String.class, named("F")); + final Key g = Key.get(String.class, named("G")); + final Key h = Key.get(Object.class, named("H")); + final Key i = Key.get(String.class, named("I")); + + Module singletonBindings = new AbstractModule() { + @Override protected void configure() { + bind(a).to(b); + bind(b).to(c); + bind(c).toProvider(Providers.of("c")).in(Scopes.SINGLETON); + bind(d).toInstance("d"); + bind(e).toProvider(Providers.of("e")).asEagerSingleton(); + bind(f).toProvider(Providers.of("f")).in(Singleton.class); + bind(h).to(AnnotatedSingleton.class); + install(new PrivateModule() { + @Override protected void configure() { + bind(i).toProvider(Providers.of("i")).in(Singleton.class); + expose(i); + } + }); + } + + @Provides @Named("G") @Singleton String provideG() { + return "g"; + } + }; + + @SuppressWarnings("unchecked") // we know the module contains only bindings + List moduleBindings = Elements.getElements(singletonBindings); + ImmutableMap, Binding> map = indexBindings(moduleBindings); + assertFalse(Scopes.isSingleton(map.get(a))); // linked bindings are not followed by modules + assertFalse(Scopes.isSingleton(map.get(b))); + assertTrue(Scopes.isSingleton(map.get(c))); + assertTrue(Scopes.isSingleton(map.get(d))); + assertTrue(Scopes.isSingleton(map.get(e))); + assertTrue(Scopes.isSingleton(map.get(f))); + assertTrue(Scopes.isSingleton(map.get(g))); + assertFalse(Scopes.isSingleton(map.get(h))); // annotated classes are not followed by modules + assertTrue(Scopes.isSingleton(map.get(i))); + + Injector injector = Guice.createInjector(singletonBindings); + assertTrue(Scopes.isSingleton(injector.getBinding(a))); + assertTrue(Scopes.isSingleton(injector.getBinding(b))); + assertTrue(Scopes.isSingleton(injector.getBinding(c))); + assertTrue(Scopes.isSingleton(injector.getBinding(d))); + assertTrue(Scopes.isSingleton(injector.getBinding(e))); + assertTrue(Scopes.isSingleton(injector.getBinding(f))); + assertTrue(Scopes.isSingleton(injector.getBinding(g))); + assertTrue(Scopes.isSingleton(injector.getBinding(h))); + assertTrue(Scopes.isSingleton(injector.getBinding(i))); + } + + public void testIsSingletonNegative() { + final Key a = Key.get(String.class, named("A")); + final Key b = Key.get(String.class, named("B")); + final Key c = Key.get(String.class, named("C")); + final Key d = Key.get(String.class, named("D")); + final Key e = Key.get(String.class, named("E")); + final Key f = Key.get(String.class, named("F")); + + Module singletonBindings = new AbstractModule() { + @Override protected void configure() { + bind(a).to(b); + bind(b).to(c); + bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); + bind(d).toProvider(Providers.of("d")).in(CustomScoped.class); + bindScope(CustomScoped.class, Scopes.NO_SCOPE); + install(new PrivateModule() { + @Override protected void configure() { + bind(f).toProvider(Providers.of("f")).in(CustomScoped.class); + expose(f); + } + }); + } + + @Provides @Named("E") @CustomScoped String provideE() { + return "e"; + } + }; + + @SuppressWarnings("unchecked") // we know the module contains only bindings + List moduleBindings = Elements.getElements(singletonBindings); + ImmutableMap, Binding> map = indexBindings(moduleBindings); + assertFalse(Scopes.isSingleton(map.get(a))); + assertFalse(Scopes.isSingleton(map.get(b))); + assertFalse(Scopes.isSingleton(map.get(c))); + assertFalse(Scopes.isSingleton(map.get(d))); + assertFalse(Scopes.isSingleton(map.get(e))); + assertFalse(Scopes.isSingleton(map.get(f))); + + Injector injector = Guice.createInjector(singletonBindings); + assertFalse(Scopes.isSingleton(injector.getBinding(a))); + assertFalse(Scopes.isSingleton(injector.getBinding(b))); + assertFalse(Scopes.isSingleton(injector.getBinding(c))); + assertFalse(Scopes.isSingleton(injector.getBinding(d))); + assertFalse(Scopes.isSingleton(injector.getBinding(e))); + assertFalse(Scopes.isSingleton(injector.getBinding(f))); + } + + public void testIsScopedPositive() { + final Key a = Key.get(String.class, named("A")); + final Key b = Key.get(String.class, named("B")); + final Key c = Key.get(String.class, named("C")); + final Key d = Key.get(String.class, named("D")); + final Key e = Key.get(String.class, named("E")); + final Key f = Key.get(Object.class, named("F")); + final Key g = Key.get(String.class, named("G")); + + Module customBindings = new AbstractModule() { + @Override protected void configure() { + bindScope(CustomScoped.class, CUSTOM_SCOPE); + bind(a).to(b); + bind(b).to(c); + bind(c).toProvider(Providers.of("c")).in(CUSTOM_SCOPE); + bind(d).toProvider(Providers.of("d")).in(CustomScoped.class); + bind(f).to(AnnotatedCustomScoped.class); + install(new PrivateModule() { + @Override protected void configure() { + bind(g).toProvider(Providers.of("g")).in(CustomScoped.class); + expose(g); + } + }); + } + + @Provides @Named("E") @CustomScoped String provideE() { + return "e"; + } + }; + + @SuppressWarnings("unchecked") // we know the module contains only bindings + List moduleBindings = Elements.getElements(customBindings); + ImmutableMap, Binding> map = indexBindings(moduleBindings); + assertFalse(isCustomScoped(map.get(a))); // linked bindings are not followed by modules + assertFalse(isCustomScoped(map.get(b))); + assertTrue(isCustomScoped(map.get(c))); + assertTrue(isCustomScoped(map.get(d))); + assertTrue(isCustomScoped(map.get(e))); + assertFalse(isCustomScoped(map.get(f))); // annotated classes are not followed by modules + assertTrue(isCustomScoped(map.get(g))); + + Injector injector = Guice.createInjector(customBindings); + assertTrue(isCustomScoped(injector.getBinding(a))); + assertTrue(isCustomScoped(injector.getBinding(b))); + assertTrue(isCustomScoped(injector.getBinding(c))); + assertTrue(isCustomScoped(injector.getBinding(d))); + assertTrue(isCustomScoped(injector.getBinding(e))); + assertTrue(isCustomScoped(injector.getBinding(f))); + assertTrue(isCustomScoped(injector.getBinding(g))); + } + + public void testIsScopedNegative() { + final Key a = Key.get(String.class, named("A")); + final Key b = Key.get(String.class, named("B")); + final Key c = Key.get(String.class, named("C")); + final Key d = Key.get(String.class, named("D")); + final Key e = Key.get(String.class, named("E")); + final Key f = Key.get(String.class, named("F")); + final Key g = Key.get(String.class, named("G")); + final Key h = Key.get(String.class, named("H")); + + Module customBindings = new AbstractModule() { + @Override protected void configure() { + bind(a).to(b); + bind(b).to(c); + bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); + bind(d).toProvider(Providers.of("d")).in(Singleton.class); + install(new PrivateModule() { + @Override + protected void configure() { + bind(f).toProvider(Providers.of("f")).in(Singleton.class); + expose(f); + } + }); + bind(g).toInstance("g"); + bind(h).toProvider(Providers.of("h")).asEagerSingleton(); + } + + @Provides @Named("E") @Singleton String provideE() { + return "e"; + } + }; + + @SuppressWarnings("unchecked") // we know the module contains only bindings + List moduleBindings = Elements.getElements(customBindings); + ImmutableMap, Binding> map = indexBindings(moduleBindings); + assertFalse(isCustomScoped(map.get(a))); + assertFalse(isCustomScoped(map.get(b))); + assertFalse(isCustomScoped(map.get(c))); + assertFalse(isCustomScoped(map.get(d))); + assertFalse(isCustomScoped(map.get(e))); + assertFalse(isCustomScoped(map.get(f))); + assertFalse(isCustomScoped(map.get(g))); + assertFalse(isCustomScoped(map.get(h))); + + Injector injector = Guice.createInjector(customBindings); + assertFalse(isCustomScoped(injector.getBinding(a))); + assertFalse(isCustomScoped(injector.getBinding(b))); + assertFalse(isCustomScoped(injector.getBinding(c))); + assertFalse(isCustomScoped(injector.getBinding(d))); + assertFalse(isCustomScoped(injector.getBinding(e))); + assertFalse(isCustomScoped(injector.getBinding(f))); + assertFalse(isCustomScoped(injector.getBinding(g))); + assertFalse(isCustomScoped(injector.getBinding(h))); + } + + private boolean isCustomScoped(Binding binding) { + return Scopes.isScoped(binding, CUSTOM_SCOPE, CustomScoped.class); + } + + ImmutableMap, Binding> indexBindings(Iterable elements) { + ImmutableMap.Builder, Binding> builder = ImmutableMap.builder(); + for (Element element : elements) { + if (element instanceof Binding) { + Binding binding = (Binding) element; + builder.put(binding.getKey(), binding); + } else if (element instanceof PrivateElements) { + PrivateElements privateElements = (PrivateElements)element; + Map, Binding> privateBindings = indexBindings(privateElements.getElements()); + for(Key exposed : privateElements.getExposedKeys()) { + builder.put(exposed, privateBindings.get(exposed)); + } + } + } + return builder.build(); + } + + @Singleton + static class ThrowingSingleton { + static int nextInstanceId; + final int instanceId = nextInstanceId++; + + ThrowingSingleton() { + if (instanceId == 0) { + throw new RuntimeException(); + } + } + } + + public void testSingletonConstructorThrows() { + Injector injector = Guice.createInjector(); + + try { + injector.getInstance(ThrowingSingleton.class); + fail(); + } catch (ProvisionException expected) { + } + + // this behaviour is unspecified. If we change Guice to re-throw the exception, this test + // should be changed + injector.getInstance(ThrowingSingleton.class); + assertEquals(2, ThrowingSingleton.nextInstanceId); + } + + /** + * Should only be created by {@link SBarrierProvider}. + * + *

{@code S} stands for synchronization. + * + * @see SBarrierProvider + */ + static class S { + + private S(int preventInjectionWithoutProvider) { + } + } + + /** + * Provides all the instances of S simultaneously using {@link CyclicBarrier} with {@code + * nThreads}. Intended to be used for threads synchronization during injection. + */ + static class SBarrierProvider implements Provider { + + final CyclicBarrier barrier; + volatile boolean barrierPassed = false; + + SBarrierProvider(int nThreads) { + barrier = new CyclicBarrier(nThreads, new Runnable() { + public void run() { + // would finish before returning from await() for any thread + barrierPassed = true; + } + }); + } + + public S get() { + try { + if (!barrierPassed) { + // only if we're triggering barrier for the first time + barrier.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return new S(0); + } + } + + /** + * Tests that different injectors should not affect each other. + * + *

This creates a second thread to work in parallel, to create two instances of + * {@link S} as the same time. If the lock if not granular enough (i.e. JVM-wide) + * then they would block each other creating a deadlock and await timeout. + */ + + public void testInjectorsDontDeadlockOnSingletons() throws Exception { + final Provider provider = new SBarrierProvider(2); + final Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + Thread.currentThread().setName("S.class[1]"); + bind(S.class).toProvider(provider).in(Scopes.SINGLETON); + } + }); + final Injector secondInjector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + Thread.currentThread().setName("S.class[2]"); + bind(S.class).toProvider(provider).in(Scopes.SINGLETON); + } + }); + + Future secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { + public S call() { + return secondInjector.getInstance(S.class); + } + }); + + S firstS = injector.getInstance(S.class); + S secondS = secondThreadResult.get(); + + assertNotSame(firstS, secondS); + } + + @ImplementedBy(GImpl.class) + interface G { + + } + + @Singleton + static class GImpl implements G { + + final H h; + + /** + * Relies on Guice implementation to inject S first and H later, which provides a barrier . + */ + @Inject + GImpl(S synchronizationBarrier, H h) { + this.h = h; + } + } + + @ImplementedBy(HImpl.class) + interface H { + + } + + @Singleton + static class HImpl implements H { + + final G g; + + /** + * Relies on Guice implementation to inject S first and G later, which provides a barrier . + */ + @Inject + HImpl(S synchronizationBarrier, G g) throws Exception { + this.g = g; + } + } + + /** + * Tests that injector can create two singletons with circular dependency in parallel. + * + *

This creates two threads to work in parallel, to create instances of + * {@link G} and {@link H}. Creation is synchronized by injection of {@link S}, + * first thread would block until second would be inside a singleton creation as well. + * + *

Both instances are created by sibling injectors, that share singleton scope. + * Verifies that exactly one circular proxy object is created. + */ + + public void testSiblingInjectorGettingCircularSingletonsOneCircularProxy() throws Exception { + final Provider provider = new SBarrierProvider(2); + final Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(S.class).toProvider(provider); + } + }); + + Future firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { + public G call() { + Thread.currentThread().setName("G.class"); + return injector.createChildInjector().getInstance(G.class); + } + }); + Future secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { + public H call() { + Thread.currentThread().setName("H.class"); + return injector.createChildInjector().getInstance(H.class); + } + }); + + // using separate threads to avoid potential deadlock on the main thread + // waiting twice as much to be sure that both would time out in their respective barriers + GImpl g = (GImpl) firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + HImpl h = (HImpl) secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + + // Check that G and H created are not proxied + assertTrue(!Scopes.isCircularProxy(g) && !Scopes.isCircularProxy(h)); + + // Check that we have no more than one circular proxy created + assertFalse(Scopes.isCircularProxy(g.h) && Scopes.isCircularProxy(h.g)); + + // Check that non-proxy variable points to another singleton + assertTrue(g.h == h || h.g == g); + + // Check correct proxy initialization as default equals implementation would + assertEquals(g.h, h); + assertEquals(h.g, g); + } + + @Singleton + static class I0 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + I0(I1 i) { + } + } + + @Singleton + static class I1 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + I1(S synchronizationBarrier, I2 i) { + } + } + + @Singleton + static class I2 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + I2(J1 j) { + } + } + + @Singleton + static class J0 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + J0(J1 j) { + } + } + + @Singleton + static class J1 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + J1(S synchronizationBarrier, J2 j) { + } + } + + @Singleton + static class J2 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + J2(K1 k) { + } + } + + @Singleton + static class K0 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + K0(K1 k) { + } + } + + @Singleton + static class K1 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + K1(S synchronizationBarrier, K2 k) { + } + } + + @Singleton + static class K2 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + K2(I1 i) { + } + } + + /** + * Check that circular dependencies on non-interfaces are correctly resolved in multi-threaded + * case. And that an error message constructed is a good one. + * + *

I0 -> I1 -> I2 -> J1 and J0 -> J1 -> J2 -> K1 and K0 -> K1 -> K2, + * where I1, J1 and K1 are created in parallel. + * + *

Creation is synchronized by injection of {@link S}, first thread would block until second + * would be inside a singleton creation as well. + * + *

Verifies that provision results in an error, that spans two threads and + * has a dependency cycle. + */ + + public void testUnresolvableSingletonCircularDependencyErrorMessage() throws Exception { + final Provider provider = new SBarrierProvider(3); + final Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(S.class).toProvider(provider); + } + }); + + Future firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { + public I0 call() { + Thread.currentThread().setName("I0.class"); + return injector.getInstance(I0.class); + } + }); + Future secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { + public J0 call() { + Thread.currentThread().setName("J0.class"); + return injector.getInstance(J0.class); + } + }); + Future thirdThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { + public K0 call() { + Thread.currentThread().setName("K0.class"); + return injector.getInstance(K0.class); + } + }); + + // using separate threads to avoid potential deadlock on the main thread + // waiting twice as much to be sure that both would time out in their respective barriers + Throwable firstException = null; + Throwable secondException = null; + Throwable thirdException = null; + try { + firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + fail(); + } catch (ExecutionException e) { + firstException = e.getCause(); + } + try { + secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + fail(); + } catch (ExecutionException e) { + secondException = e.getCause(); + } + try { + thirdThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + fail(); + } catch (ExecutionException e) { + thirdException = e.getCause(); + } + + // verification of error messages generated + assertEquals(firstException.getClass(), ProvisionException.class); + assertEquals(secondException.getClass(), ProvisionException.class); + assertEquals(thirdException.getClass(), ProvisionException.class); + List errorMessages = Lists.newArrayList( + String.format("%s\n%s\n%s", + firstException.getMessage(), secondException.getMessage(), thirdException.getMessage()) + .split("\\n\\n")); + Collections.sort(errorMessages, new Comparator() { + @Override + public int compare(String s1, String s2) { + return s2.length() - s1.length(); + } + }); + // this is brittle, but turns out that second to longest message spans all threads + String errorMessage = errorMessages.get(1); + assertContains(errorMessage, + "Encountered circular dependency spanning several threads. Tried proxying " + + this.getClass().getName()); + assertFalse("Both I0 and J0 can not be a part of a dependency cycle", + errorMessage.contains(I0.class.getName()) && errorMessage.contains(J0.class.getName())); + assertFalse("Both J0 and K0 can not be a part of a dependency cycle", + errorMessage.contains(J0.class.getName()) && errorMessage.contains(K0.class.getName())); + assertFalse("Both K0 and I0 can not be a part of a dependency cycle", + errorMessage.contains(K0.class.getName()) && errorMessage.contains(I0.class.getName())); + + List firstErrorLineForThread = new ArrayList(); + boolean addNextLine = false; + for (String errorLine : errorMessage.split("\\n")) { + if (errorLine.contains("in thread")) { + addNextLine = true; + firstErrorLineForThread.add(errorLine); + } else if (addNextLine) { + addNextLine = false; + firstErrorLineForThread.add(errorLine); + } + } + assertEquals("we expect to see [T1, $A, T2, $B, T3, $C, T1, $A]", + 8, firstErrorLineForThread.size()); + assertEquals("first four elements should be different", + 6, new HashSet(firstErrorLineForThread.subList(0, 6)).size()); + assertEquals(firstErrorLineForThread.get(6), firstErrorLineForThread.get(0)); + assertEquals(firstErrorLineForThread.get(7), firstErrorLineForThread.get(1)); + assertFalse("K0 thread could not be blocked by J0", + firstErrorLineForThread.get(0).contains("J0") + && firstErrorLineForThread.get(2).contains("K0")); + assertFalse("J0 thread could not be blocked by I0", + firstErrorLineForThread.get(0).contains("I0") + && firstErrorLineForThread.get(2).contains("J0")); + assertFalse("I0 thread could not be blocked by K0", + firstErrorLineForThread.get(0).contains("K0") + && firstErrorLineForThread.get(2).contains("I0")); + } +} diff --git a/src/test/java/com/google/inject/SuiteUtils.java b/src/test/java/com/google/inject/SuiteUtils.java new file mode 100644 index 0000000..cd9eb10 --- /dev/null +++ b/src/test/java/com/google/inject/SuiteUtils.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2011 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; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import java.util.Enumeration; +import java.util.Set; + +public class SuiteUtils { + + public static TestSuite removeSuppressedTests(TestSuite suite, Set suppressedTestNames) { + TestSuite result = new TestSuite(suite.getName()); + + for(Enumeration e = suite.tests(); e.hasMoreElements(); ) { + Test test = (Test) e.nextElement(); + + if (suppressedTestNames.contains(test.toString())) { + continue; + } + + if (test instanceof TestSuite) { + result.addTest(removeSuppressedTests((TestSuite) test, suppressedTestNames)); + } else { + result.addTest(test); + } + } + + return result; + } + +} diff --git a/src/test/java/com/google/inject/SuperclassTest.java b/src/test/java/com/google/inject/SuperclassTest.java new file mode 100644 index 0000000..3dda585 --- /dev/null +++ b/src/test/java/com/google/inject/SuperclassTest.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2006 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; + +import junit.framework.TestCase; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class SuperclassTest extends TestCase { + + public void testSuperclassInjection() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(Foo.class); + } + }); + + Provider creator = injector.getProvider(Sub.class); + Sub sub = creator.get(); + sub = creator.get(); + sub = creator.get(); + sub = creator.get(); + sub = creator.get(); + assertNotNull(sub.field); + assertNotNull(sub.fromMethod); + } + + static abstract class Super { + @Inject Foo field; + + Foo fromMethod; + @Inject void setC(Foo foo) { + fromMethod = foo; + } + } + + static class Sub extends Super { + } + + static class Foo {} +} diff --git a/src/test/java/com/google/inject/TypeConversionTest.java b/src/test/java/com/google/inject/TypeConversionTest.java new file mode 100644 index 0000000..e318477 --- /dev/null +++ b/src/test/java/com/google/inject/TypeConversionTest.java @@ -0,0 +1,495 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.asModuleChain; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.Iterables; +import com.google.inject.matcher.Matchers; +import com.google.inject.spi.ConvertedConstantBinding; +import com.google.inject.spi.TypeConverter; +import com.google.inject.spi.TypeConverterBinding; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import java.lang.annotation.Retention; +import java.util.Date; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class TypeConversionTest extends TestCase { + + @Retention(RUNTIME) + @BindingAnnotation @interface NumericValue {} + + @Retention(RUNTIME) + @BindingAnnotation @interface BooleanValue {} + + @Retention(RUNTIME) + @BindingAnnotation @interface EnumValue {} + + @Retention(RUNTIME) + @BindingAnnotation @interface ClassName {} + + public static class Foo { + @Inject @BooleanValue Boolean booleanField; + @Inject @BooleanValue boolean primitiveBooleanField; + @Inject @NumericValue Byte byteField; + @Inject @NumericValue byte primitiveByteField; + @Inject @NumericValue Short shortField; + @Inject @NumericValue short primitiveShortField; + @Inject @NumericValue Integer integerField; + @Inject @NumericValue int primitiveIntField; + @Inject @NumericValue Long longField; + @Inject @NumericValue long primitiveLongField; + @Inject @NumericValue Float floatField; + @Inject @NumericValue float primitiveFloatField; + @Inject @NumericValue Double doubleField; + @Inject @NumericValue double primitiveDoubleField; + @Inject @EnumValue Bar enumField; + @Inject @ClassName Class classField; + } + + public enum Bar { + TEE, BAZ, BOB + } + + public void testOneConstantInjection() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(NumericValue.class).to("5"); + bind(Simple.class); + } + }); + + Simple simple = injector.getInstance(Simple.class); + assertEquals(5, simple.i); + } + + static class Simple { + @Inject @NumericValue int i; + } + + public void testConstantInjection() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(NumericValue.class).to("5"); + bindConstant().annotatedWith(BooleanValue.class).to("true"); + bindConstant().annotatedWith(EnumValue.class).to("TEE"); + bindConstant().annotatedWith(ClassName.class).to(Foo.class.getName()); + } + }); + + Foo foo = injector.getInstance(Foo.class); + + checkNumbers( + foo.integerField, + foo.primitiveIntField, + foo.longField, + foo.primitiveLongField, + foo.byteField, + foo.primitiveByteField, + foo.shortField, + foo.primitiveShortField, + foo.floatField, + foo.primitiveFloatField, + foo.doubleField, + foo.primitiveDoubleField + ); + + assertEquals(Bar.TEE, foo.enumField); + assertEquals(Foo.class, foo.classField); + } + + public void testConstantInjectionWithExplicitBindingsRequired() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().requireExplicitBindings(); + bind(Foo.class); + bindConstant().annotatedWith(NumericValue.class).to("5"); + bindConstant().annotatedWith(BooleanValue.class).to("true"); + bindConstant().annotatedWith(EnumValue.class).to("TEE"); + bindConstant().annotatedWith(ClassName.class).to(Foo.class.getName()); + } + }); + + Foo foo = injector.getInstance(Foo.class); + + checkNumbers( + foo.integerField, + foo.primitiveIntField, + foo.longField, + foo.primitiveLongField, + foo.byteField, + foo.primitiveByteField, + foo.shortField, + foo.primitiveShortField, + foo.floatField, + foo.primitiveFloatField, + foo.doubleField, + foo.primitiveDoubleField + ); + + assertEquals(Bar.TEE, foo.enumField); + assertEquals(Foo.class, foo.classField); + } + + void checkNumbers(Number... ns) { + for (Number n : ns) { + assertEquals(5, n.intValue()); + } + } + + static class OuterErrorModule extends AbstractModule { + @Override protected void configure() { + install(new InnerErrorModule()); + } + } + + static class InnerErrorModule extends AbstractModule { + @Override protected void configure() { + bindConstant().annotatedWith(NumericValue.class).to("invalid"); + } + } + + public void testInvalidInteger() throws CreationException { + Injector injector = Guice.createInjector(new OuterErrorModule()); + try { + injector.getInstance(InvalidInteger.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "Error converting 'invalid' (bound at " + InnerErrorModule.class.getName() + + getDeclaringSourcePart(getClass()), + asModuleChain(OuterErrorModule.class, InnerErrorModule.class), + "using TypeConverter which matches identicalTo(class java.lang.Integer)" + + " (bound at [unknown source]).", + "Reason: java.lang.RuntimeException: For input string: \"invalid\""); + } + } + + public static class InvalidInteger { + @Inject @NumericValue Integer integerField; + } + + public void testInvalidCharacter() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(NumericValue.class).to("invalid"); + } + }); + + try { + injector.getInstance(InvalidCharacter.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), "Error converting 'invalid'"); + assertContains(expected.getMessage(), "bound at " + getClass().getName()); + assertContains(expected.getMessage(), "to java.lang.Character"); + } + } + + public static class InvalidCharacter { + @Inject @NumericValue char foo; + } + + public void testInvalidEnum() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(NumericValue.class).to("invalid"); + } + }); + + try { + injector.getInstance(InvalidEnum.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), "Error converting 'invalid'"); + assertContains(expected.getMessage(), "bound at " + getClass().getName()); + assertContains(expected.getMessage(), "to " + Bar.class.getName()); + } + } + + public static class InvalidEnum { + @Inject @NumericValue Bar foo; + } + + public void testToInstanceIsTreatedLikeConstant() throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("5"); + bind(LongHolder.class); + } + }); + + assertEquals(5L, (long) injector.getInstance(LongHolder.class).foo); + } + + static class LongHolder { + @Inject Long foo; + } + + public void testCustomTypeConversion() throws CreationException { + final Date result = new Date(); + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)) , mockTypeConverter(result)); + bindConstant().annotatedWith(NumericValue.class).to("Today"); + bind(DateHolder.class); + } + }); + + assertSame(result, injector.getInstance(DateHolder.class).date); + + Binding binding = injector.getBinding(Key.get(Date.class, NumericValue.class)); + assertTrue(binding instanceof ConvertedConstantBinding); + + TypeConverterBinding converterBinding = ((ConvertedConstantBinding)binding).getTypeConverterBinding(); + assertEquals("CustomConverter", converterBinding.getTypeConverter().toString()); + + assertTrue(injector.getTypeConverterBindings().contains(converterBinding)); + } + + static class InvalidCustomValueModule extends AbstractModule { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), failingTypeConverter()); + bindConstant().annotatedWith(NumericValue.class).to("invalid"); + bind(DateHolder.class); + } + } + + public void testInvalidCustomValue() throws CreationException { + Module module = new InvalidCustomValueModule(); + try { + Guice.createInjector(module); + fail(); + } catch (CreationException expected) { + Throwable cause = Iterables.getOnlyElement(expected.getErrorMessages()).getCause(); + assertTrue(cause instanceof UnsupportedOperationException); + assertContains(expected.getMessage(), + "1) Error converting 'invalid' (bound at ", getClass().getName(), + getDeclaringSourcePart(getClass()), "to java.util.Date", + "using BrokenConverter which matches only(java.util.Date) ", + "(bound at " + getClass().getName(), getDeclaringSourcePart(getClass()), + "Reason: java.lang.UnsupportedOperationException: Cannot convert", + "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:"); + } + } + + static class OuterModule extends AbstractModule { + private final Module converterModule; + OuterModule(Module converterModule) { + this.converterModule = converterModule; + } + + @Override protected void configure() { + install(new InnerModule(converterModule)); + } + } + + static class InnerModule extends AbstractModule { + private final Module converterModule; + InnerModule(Module converterModule) { + this.converterModule = converterModule; + } + + @Override protected void configure() { + install(converterModule); + bindConstant().annotatedWith(NumericValue.class).to("foo"); + bind(DateHolder.class); + } + } + + class ConverterNullModule extends AbstractModule { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(null)); + } + } + + public void testNullCustomValue() { + try { + Guice.createInjector(new OuterModule(new ConverterNullModule())); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Received null converting 'foo' (bound at ", + getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterModule.class, InnerModule.class), + "to java.util.Date", + "using CustomConverter which matches only(java.util.Date) ", + "(bound at " + getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterModule.class, InnerModule.class, ConverterNullModule.class), + "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:", + asModuleChain(OuterModule.class, InnerModule.class)); + } + } + + class ConverterCustomModule extends AbstractModule { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(-1)); + } + } + + public void testCustomValueTypeMismatch() { + try { + Guice.createInjector(new OuterModule(new ConverterCustomModule())); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Type mismatch converting 'foo' (bound at ", + getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterModule.class, InnerModule.class), + "to java.util.Date", + "using CustomConverter which matches only(java.util.Date) ", + "(bound at " + getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterModule.class, InnerModule.class, ConverterCustomModule.class), + "Converter returned -1.", + "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:", + asModuleChain(OuterModule.class, InnerModule.class)); + } + } + + public void testStringIsConvertedOnlyOnce() { + final TypeConverter converter = new TypeConverter() { + boolean converted = false; + public Object convert(String value, TypeLiteral toType) { + if (converted) { + throw new AssertionFailedError("converted multiple times!"); + } + converted = true; + return new Date(); + } + }; + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), converter); + bindConstant().annotatedWith(NumericValue.class).to("unused"); + } + }); + + Date first = injector.getInstance(Key.get(Date.class, NumericValue.class)); + Date second = injector.getInstance(Key.get(Date.class, NumericValue.class)); + assertSame(first, second); + } + + class OuterAmbiguousModule extends AbstractModule { + @Override protected void configure() { + install(new InnerAmbiguousModule()); + } + } + + class InnerAmbiguousModule extends AbstractModule { + @Override protected void configure() { + install(new Ambiguous1Module()); + install(new Ambiguous2Module()); + bindConstant().annotatedWith(NumericValue.class).to("foo"); + bind(DateHolder.class); + } + } + + class Ambiguous1Module extends AbstractModule { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(new Date())); + } + } + + class Ambiguous2Module extends AbstractModule { + @Override protected void configure() { + convertToTypes(Matchers.only(TypeLiteral.get(Date.class)), mockTypeConverter(new Date())); + } + } + + public void testAmbiguousTypeConversion() { + try { + Guice.createInjector(new OuterAmbiguousModule()); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Multiple converters can convert 'foo' (bound at ", getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterAmbiguousModule.class, InnerAmbiguousModule.class), + "to java.util.Date:", + "CustomConverter which matches only(java.util.Date) (bound at " + + Ambiguous1Module.class.getName() + + getDeclaringSourcePart(getClass()), + asModuleChain( + OuterAmbiguousModule.class, InnerAmbiguousModule.class, Ambiguous1Module.class), + "and", + "CustomConverter which matches only(java.util.Date) (bound at " + + Ambiguous2Module.class.getName() + + getDeclaringSourcePart(getClass()), + asModuleChain( + OuterAmbiguousModule.class, InnerAmbiguousModule.class, Ambiguous2Module.class), + "Please adjust your type converter configuration to avoid overlapping matches.", + "at " + DateHolder.class.getName() + ".date(TypeConversionTest.java:"); + } + } + + TypeConverter mockTypeConverter(final Object result) { + return new TypeConverter() { + public Object convert(String value, TypeLiteral toType) { + return result; + } + + @Override public String toString() { + return "CustomConverter"; + } + }; + } + + private static TypeConverter failingTypeConverter() { + return new TypeConverter() { + public Object convert(String value, TypeLiteral toType) { + throw new UnsupportedOperationException("Cannot convert"); + } + @Override public String toString() { + return "BrokenConverter"; + } + }; + } + + static class DateHolder { + @Inject @NumericValue Date date; + } + + public void testCannotConvertUnannotatedBindings() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("55"); + } + }); + + try { + injector.getInstance(Integer.class); + fail("Converted an unannotated String to an Integer"); + } catch (ConfigurationException expected) { + Asserts.assertContains(expected.getMessage(), + "Could not find a suitable constructor in java.lang.Integer."); + } + } +} diff --git a/src/test/java/com/google/inject/TypeListenerTest.java b/src/test/java/com/google/inject/TypeListenerTest.java new file mode 100644 index 0000000..a19f7c3 --- /dev/null +++ b/src/test/java/com/google/inject/TypeListenerTest.java @@ -0,0 +1,662 @@ +/** + * 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.google.inject; + +import static com.google.inject.Asserts.asModuleChain; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static com.google.inject.matcher.Matchers.any; +import static com.google.inject.matcher.Matchers.only; +import static com.google.inject.name.Names.named; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.inject.matcher.Matcher; +import com.google.inject.matcher.Matchers; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.Message; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; + +import junit.framework.TestCase; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class TypeListenerTest extends TestCase { + + private final Matcher onlyAbcd = Matchers.only(new TypeLiteral() {}) + .or(only(new TypeLiteral() {})) + .or(only(new TypeLiteral() {})) + .or(only(new TypeLiteral() {})); + + final TypeListener failingTypeListener = new TypeListener() { + int failures = 0; + + public void hear(TypeLiteral type, TypeEncounter encounter) { + throw new ClassCastException("whoops, failure #" + (++failures)); + } + + @Override public String toString() { + return "clumsy"; + } + }; + + final InjectionListener failingInjectionListener = new InjectionListener() { + int failures = 0; + + public void afterInjection(Object injectee) { + throw new ClassCastException("whoops, failure #" + (++failures)); + } + + @Override public String toString() { + return "goofy"; + } + }; + + final MembersInjector failingMembersInjector = new MembersInjector() { + int failures = 0; + + public void injectMembers(Object instance) { + throw new ClassCastException("whoops, failure #" + (++failures)); + } + + @Override public String toString() { + return "awkward"; + } + }; + + public void testTypeListenersAreFired() { + final AtomicInteger firedCount = new AtomicInteger(); + + final TypeListener typeListener = new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + assertEquals(new TypeLiteral() {}, type); + firedCount.incrementAndGet(); + } + }; + + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, typeListener); + bind(A.class); + } + }); + + assertEquals(1, firedCount.get()); + } + + public void testInstallingInjectionListener() { + final List injectees = Lists.newArrayList(); + final InjectionListener injectionListener = new InjectionListener() { + public void afterInjection(Object injectee) { + injectees.add(injectee); + } + }; + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register(injectionListener); + } + }); + bind(A.class); + } + }); + + assertEquals(ImmutableList.of(), injectees); + + Object a1 = injector.getInstance(A.class); + assertEquals(ImmutableList.of(a1), injectees); + + Object a2 = injector.getInstance(A.class); + assertEquals(ImmutableList.of(a1, a2), injectees); + + Object b1 = injector.getInstance(B.class); + assertEquals(ImmutableList.of(a1, a2, b1), injectees); + + Provider aProvider = injector.getProvider(A.class); + assertEquals(ImmutableList.of(a1, a2, b1), injectees); + A a3 = aProvider.get(); + A a4 = aProvider.get(); + assertEquals(ImmutableList.of(a1, a2, b1, a3, a4), injectees); + } + + class OuterThrowsModule extends AbstractModule { + @Override protected void configure() { + install(new InnerThrowsModule()); + } + } + class InnerThrowsModule extends AbstractModule { + @Override protected void configure() { + bindListener(onlyAbcd, failingTypeListener); + bind(B.class); + bind(C.class); + } + } + public void testTypeListenerThrows() { + try { + Guice.createInjector(new OuterThrowsModule()); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Error notifying TypeListener clumsy (bound at " + getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterThrowsModule.class, InnerThrowsModule.class), + "of " + B.class.getName(), + "Reason: java.lang.ClassCastException: whoops, failure #1", + "2) Error notifying TypeListener clumsy (bound at " + getClass().getName(), + getDeclaringSourcePart(getClass()), + asModuleChain(OuterThrowsModule.class, InnerThrowsModule.class), + "of " + C.class.getName(), + "Reason: java.lang.ClassCastException: whoops, failure #2"); + } + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, failingTypeListener); + } + }); + try { + injector.getProvider(B.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "1) Error notifying TypeListener clumsy (bound at " + getClass().getName(), + getDeclaringSourcePart(getClass()), + "of " + B.class.getName(), + "Reason: java.lang.ClassCastException: whoops, failure #3"); + } + + // getting it again should yield the same exception #3 + try { + injector.getInstance(B.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "1) Error notifying TypeListener clumsy (bound at " + getClass().getName(), + getDeclaringSourcePart(getClass()), + "of " + B.class.getName(), + "Reason: java.lang.ClassCastException: whoops, failure #3"); + } + + // non-injected types do not participate + assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); + } + + public void testInjectionListenerThrows() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register(failingInjectionListener); + } + }); + bind(B.class); + } + }); + + try { + injector.getInstance(A.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error notifying InjectionListener goofy of " + A.class.getName(), + " Reason: java.lang.ClassCastException: whoops, failure #1"); + } + + // second time through should be a new cause (#2) + try { + injector.getInstance(A.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error notifying InjectionListener goofy of " + A.class.getName(), + " Reason: java.lang.ClassCastException: whoops, failure #2"); + } + + // we should get errors for all types, but only on getInstance() + Provider bProvider = injector.getProvider(B.class); + try { + bProvider.get(); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error notifying InjectionListener goofy of " + B.class.getName(), + " Reason: java.lang.ClassCastException: whoops, failure #3"); + } + + // non-injected types do not participate + assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); + } + + public void testInjectMembersTypeListenerFails() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + getMembersInjector(A.class); + bindListener(onlyAbcd, failingTypeListener); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Error notifying TypeListener clumsy (bound at ", + TypeListenerTest.class.getName(), getDeclaringSourcePart(getClass()), + "of " + A.class.getName(), + " Reason: java.lang.ClassCastException: whoops, failure #1"); + } + } + + public void testConstructedTypeListenerIsTheSameAsMembersInjectorListener() { + final AtomicInteger typeEncounters = new AtomicInteger(); + final AtomicInteger injections = new AtomicInteger(); + + final InjectionListener listener = new InjectionListener() { + public void afterInjection(A injectee) { + injections.incrementAndGet(); + assertNotNull(injectee.injector); + } + }; + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, new TypeListener() { + @SuppressWarnings("unchecked") + public void hear(TypeLiteral type, TypeEncounter encounter) { + typeEncounters.incrementAndGet(); + encounter.register((InjectionListener) listener); + } + }); + + bind(A.class); + getMembersInjector(A.class); + } + }); + + // creating the injector shouldn't trigger injections + assertEquals(0, injections.getAndSet(0)); + + // constructing an A should trigger an injection + injector.getInstance(A.class); + assertEquals(1, injections.getAndSet(0)); + + // injecting an A should trigger an injection + injector.injectMembers(new A()); + assertEquals(1, injections.getAndSet(0)); + + // getting a provider shouldn't + Provider aProvider = injector.getProvider(A.class); + MembersInjector aMembersInjector = injector.getMembersInjector(A.class); + assertEquals(0, injections.getAndSet(0)); + + // exercise the provider + aProvider.get(); + aProvider.get(); + assertEquals(2, injections.getAndSet(0)); + + // exercise the members injector + aMembersInjector.injectMembers(new A()); + aMembersInjector.injectMembers(new A()); + assertEquals(2, injections.getAndSet(0)); + + // we should only have encountered one type + assertEquals(1, typeEncounters.getAndSet(0)); + } + + public void testLookupsAtInjectorCreateTime() { + final AtomicReference> bProviderReference = new AtomicReference>(); + final AtomicReference> aMembersInjectorReference + = new AtomicReference>(); + + final InjectionListener lookupsTester = new InjectionListener() { + public void afterInjection(Object injectee) { + assertNotNull(bProviderReference.get().get()); + + A a = new A(); + aMembersInjectorReference.get().injectMembers(a); + assertNotNull(a.injector); + } + }; + + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(only(TypeLiteral.get(C.class)), new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + Provider bProvider = encounter.getProvider(B.class); + try { + bProvider.get(); + fail(); + } catch (IllegalStateException expected) { + assertEquals("This Provider cannot be used until the Injector has been created.", + expected.getMessage()); + } + bProviderReference.set(bProvider); + + MembersInjector aMembersInjector = encounter.getMembersInjector(A.class); + try { + aMembersInjector.injectMembers(new A()); + fail(); + } catch (IllegalStateException expected) { + assertEquals( + "This MembersInjector cannot be used until the Injector has been created.", + expected.getMessage()); + } + aMembersInjectorReference.set(aMembersInjector); + + encounter.register(lookupsTester); + } + }); + + // this ensures the type listener fires, and also the afterInjection() listener + bind(C.class).asEagerSingleton(); + } + }); + + lookupsTester.afterInjection(null); + } + + public void testLookupsPostCreate() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(only(TypeLiteral.get(C.class)), new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + assertNotNull(encounter.getProvider(B.class).get()); + + A a = new A(); + encounter.getMembersInjector(A.class).injectMembers(a); + assertNotNull(a.injector); + } + }); + } + }); + + injector.getInstance(C.class); + } + + public void testMembersInjector() { + final MembersInjector membersInjector = new MembersInjector() { + public void injectMembers(D instance) { + instance.userInjected++; + assertEquals(instance.guiceInjected, instance.userInjected); + } + }; + + final InjectionListener injectionListener = new InjectionListener() { + public void afterInjection(D injectee) { + assertTrue(injectee.userInjected > 0); + injectee.listenersNotified++; + assertEquals(injectee.guiceInjected, injectee.listenersNotified); + } + }; + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, new TypeListener() { + @SuppressWarnings("unchecked") + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register((MembersInjector) membersInjector); + encounter.register((InjectionListener) injectionListener); + } + }); + + D boundThreeTimes = new D(); + bind(D.class).annotatedWith(named("i")).toInstance(boundThreeTimes); + bind(D.class).annotatedWith(named("ii")).toInstance(boundThreeTimes); + bind(D.class).annotatedWith(named("iii")).toInstance(boundThreeTimes); + } + }); + + D boundThreeTimes = injector.getInstance(Key.get(D.class, named("iii"))); + boundThreeTimes.assertAllCounts(1); + + D getInstance = injector.getInstance(D.class); + getInstance.assertAllCounts(1); + + D memberInjection = new D(); + injector.injectMembers(memberInjection); + memberInjection.assertAllCounts(1); + + injector.injectMembers(memberInjection); + injector.injectMembers(memberInjection); + memberInjection.assertAllCounts(3); + + injector.getMembersInjector(D.class).injectMembers(memberInjection); + memberInjection.assertAllCounts(4); + } + + public void testMembersInjectorThrows() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register(failingMembersInjector); + } + }); + bind(B.class); + } + }); + + try { + injector.getInstance(A.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error injecting " + A.class.getName() + " using awkward.", + "Reason: java.lang.ClassCastException: whoops, failure #1"); + } + + // second time through should be a new cause (#2) + try { + injector.getInstance(A.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error injecting " + A.class.getName() + " using awkward.", + "Reason: java.lang.ClassCastException: whoops, failure #2"); + } + + // we should get errors for all types, but only on getInstance() + Provider bProvider = injector.getProvider(B.class); + try { + bProvider.get(); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "1) Error injecting " + B.class.getName() + " using awkward.", + "Reason: java.lang.ClassCastException: whoops, failure #3"); + } + + // non-injected types do not participate + assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); + } + + /** + * We had a bug where we weren't notifying of types encountered for member injection when those + * types had no members to be injected. Constructed types are always injected because they always + * have at least one injection point: the class constructor. + */ + public void testTypesWithNoInjectableMembersAreNotified() { + final AtomicInteger notificationCount = new AtomicInteger(); + + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(onlyAbcd, new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + notificationCount.incrementAndGet(); + } + }); + + bind(C.class).toInstance(new C()); + } + }); + + assertEquals(1, notificationCount.get()); + } + + public void testEncounterCannotBeUsedAfterHearReturns() { + final AtomicReference> encounterReference = new AtomicReference>(); + + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bindListener(any(), new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounterReference.set(encounter); + } + }); + + bind(C.class); + } + }); + TypeEncounter encounter = encounterReference.get(); + + try { + encounter.register(new InjectionListener() { + public void afterInjection(Object injectee) {} + }); + fail(); + } catch (IllegalStateException expected) { + } + + try { + encounter.addError(new Exception()); + fail(); + } catch (IllegalStateException expected) { + } + + try { + encounter.getMembersInjector(A.class); + fail(); + } catch (IllegalStateException expected) { + } + + try { + encounter.getProvider(B.class); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testAddErrors() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + requestInjection(new Object()); + bindListener(Matchers.any(), new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.addError("There was an error on %s", type); + encounter.addError(new IllegalArgumentException("whoops!")); + encounter.addError(new Message("And another problem")); + encounter.addError(new IllegalStateException()); + } + }); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) There was an error on java.lang.Object", + "2) An exception was caught and reported. Message: whoops!", + "3) And another problem", + "4) An exception was caught and reported. Message: null", + "4 errors"); + } + } + + private static class CountingMembersInjector implements MembersInjector { + public void injectMembers(D instance) { + ++instance.userInjected; + } + } + + private static class CountingInjectionListener implements InjectionListener { + public void afterInjection(D injectee) { + ++injectee.listenersNotified; + } + } + + private static class DuplicatingTypeListener implements TypeListener { + int count = 0; + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void hear(TypeLiteral type, TypeEncounter encounter) { + ++count; + + MembersInjector membersInjector = new CountingMembersInjector(); + encounter.register(membersInjector); + encounter.register(membersInjector); + + InjectionListener injectionListener = new CountingInjectionListener(); + encounter.register(injectionListener); + encounter.register(injectionListener); + } + } + + public void testDeDuplicateTypeListeners() { + final DuplicatingTypeListener typeListener = new DuplicatingTypeListener(); + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bindListener(any(), typeListener); + bindListener(only(new TypeLiteral() {}), typeListener); + } + }); + D d = injector.getInstance(D.class); + d.assertAllCounts(1); + assertEquals(1, typeListener.count); + } + + // TODO: recursively accessing a lookup should fail + + static class A { + @Inject Injector injector; + @Inject Stage stage; + } + + static class B {} + + public static class C { + public String buzz() { + return "buzz"; + } + + public String beep() { + return "beep"; + } + } + + static class D { + int guiceInjected = 0; + int userInjected = 0; + int listenersNotified = 0; + + @Inject void guiceInjected() { + guiceInjected++; + } + + void assertAllCounts(int expected) { + assertEquals(expected, guiceInjected); + assertEquals(expected, userInjected); + assertEquals(expected, listenersNotified); + } + } +} diff --git a/src/test/java/com/google/inject/TypeLiteralInjectionTest.java b/src/test/java/com/google/inject/TypeLiteralInjectionTest.java new file mode 100644 index 0000000..1231a81 --- /dev/null +++ b/src/test/java/com/google/inject/TypeLiteralInjectionTest.java @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2008 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; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.util.Types.listOf; + +import com.google.inject.util.Types; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Demonstrates type reification. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class TypeLiteralInjectionTest extends TestCase { + + public void testBindingToRawTypeLiteralIsNotAllowed() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(TypeLiteral.class).toInstance(TypeLiteral.get(String.class)); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "Binding to core guice framework type is not allowed: TypeLiteral"); + } + } + + public void testBindingToParameterizedTypeLiteralIsNotAllowed() { + try { + Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(new TypeLiteral>() {}) + .toInstance(TypeLiteral.get(String.class)); + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "Binding to core guice framework type is not allowed: TypeLiteral"); + } + } + + public void testInjectTypeLiteralWithRawTypes() { + C c = Guice.createInjector().getInstance(C.class); + assertEquals(TypeLiteral.get(String.class), c.string); + assertEquals(TypeLiteral.get(A.class), c.a); + + try { + Guice.createInjector().getInstance(B.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), TypeLiteral.class.getName() + "> " + + "cannot be used as a key; It is not fully specified."); + } + } + + public void testInjectTypeLiteralWithClassTypes() { + B b = Guice.createInjector().getInstance(new Key>() {}); + assertEquals(TypeLiteral.get(String.class), b.string); + assertEquals(TypeLiteral.get(Integer.class), b.t); + assertEquals(TypeLiteral.get(listOf(Integer.class)), b.listOfT); + assertEquals(TypeLiteral.get(listOf(Types.subtypeOf(Integer.class))), b.listOfWildcardT); + } + + public void testInjectRawTypeLiteral() { + try { + Guice.createInjector().getInstance(TypeLiteral.class); + fail(); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), + "Cannot inject a TypeLiteral that has no type parameter"); + } + } + + static class A { + @Inject TypeLiteral string; + @Inject TypeLiteral> listOfT; + @Inject TypeLiteral> listOfWildcardT; + } + + static class B extends A { + @Inject TypeLiteral t; + } + + static class C { + @Inject TypeLiteral string; + @Inject TypeLiteral a; + T t; + } +} diff --git a/src/test/java/com/google/inject/TypeLiteralTest.java b/src/test/java/com/google/inject/TypeLiteralTest.java new file mode 100644 index 0000000..de8b5da --- /dev/null +++ b/src/test/java/com/google/inject/TypeLiteralTest.java @@ -0,0 +1,198 @@ +/** + * Copyright (C) 2006 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; + +import static com.google.inject.Asserts.assertEqualsBothWays; +import static com.google.inject.Asserts.assertNotSerializable; + +import com.google.common.collect.ImmutableList; +import com.google.inject.util.Types; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class TypeLiteralTest extends TestCase { + + public void testWithParameterizedType() { + TypeLiteral> a = new TypeLiteral>() {}; + TypeLiteral> b = new TypeLiteral>( + Types.listOf(String.class)) {}; + assertEqualsBothWays(a, b); + } + + public void testEquality() { + TypeLiteral> t1 = new TypeLiteral>() {}; + TypeLiteral> t2 = new TypeLiteral>() {}; + TypeLiteral> t3 = new TypeLiteral>() {}; + TypeLiteral t4 = new TypeLiteral() {}; + + assertEqualsBothWays(t1, t2); + + assertFalse(t2.equals(t3)); + assertFalse(t3.equals(t2)); + + assertFalse(t2.equals(t4)); + assertFalse(t4.equals(t2)); + + TypeLiteral t5 = TypeLiteral.get(String.class); + assertEqualsBothWays(t4, t5); + } + + public List wildcardExtends; + + public void testWithWildcardType() throws NoSuchFieldException, IOException { + TypeLiteral a = TypeLiteral.get(getClass().getField("wildcardExtends").getGenericType()); + TypeLiteral b = TypeLiteral.get(Types.listOf(Types.subtypeOf(CharSequence.class))); + TypeLiteral c = new TypeLiteral>() {}; + assertEqualsBothWays(a, b); + assertEqualsBothWays(b, c); + assertEquals("java.util.List", a.toString()); + assertEquals("java.util.List", b.toString()); + assertEquals("java.util.List", c.toString()); + assertNotSerializable(a); + assertNotSerializable(b); + assertNotSerializable(c); + } + + public void testMissingTypeParameter() { + try { + new TypeLiteral() {}; + fail(); + } catch (RuntimeException e) { /* expected */ } + } + + public void testTypesInvolvingArraysForEquality() { + TypeLiteral stringArray = new TypeLiteral() {}; + assertEquals(stringArray, new TypeLiteral() {}); + + TypeLiteral> listOfStringArray + = new TypeLiteral>() {}; + assertEquals(listOfStringArray, new TypeLiteral>() {}); + } + + public void testEqualityOfGenericArrayAndClassArray() { + TypeLiteral arrayAsClass = TypeLiteral.get(String[].class); + TypeLiteral arrayAsType = new TypeLiteral() {}; + assertEquals(arrayAsClass, arrayAsType); + } + + public void testEqualityOfMultidimensionalGenericArrayAndClassArray() { + TypeLiteral arrayAsClass = TypeLiteral.get(String[][][].class); + TypeLiteral arrayAsType = new TypeLiteral() {}; + assertEquals(arrayAsClass, arrayAsType); + } + + public void testTypeLiteralsMustHaveRawTypes() { + try { + TypeLiteral.get(Types.subtypeOf(Runnable.class)); + fail(); + } catch (IllegalArgumentException expected) { + Asserts.assertContains(expected.getMessage(), "Expected a Class, ParameterizedType, or " + + "GenericArrayType, but is of type " + + "com.google.inject.internal.MoreTypes$WildcardTypeImpl"); + } + } + + /** + * Unlike Key, TypeLiteral retains full type information and differentiates + * between {@code int.class} and {@code Integer.class}. + */ + public void testDifferentiationBetweenWrappersAndPrimitives() { + Class[] primitives = new Class[] { + boolean.class, byte.class, short.class, int.class, long.class, + float.class, double.class, char.class, void.class + }; + Class[] wrappers = new Class[] { + Boolean.class, Byte.class, Short.class, Integer.class, Long.class, + Float.class, Double.class, Character.class, Void.class + }; + + for (int t = 0; t < primitives.length; t++) { + @SuppressWarnings("unchecked") + TypeLiteral primitiveTl = TypeLiteral.get(primitives[t]); + @SuppressWarnings("unchecked") + TypeLiteral wrapperTl = TypeLiteral.get(wrappers[t]); + + assertFalse(primitiveTl.equals(wrapperTl)); + assertEquals(primitives[t], primitiveTl.getType()); + assertEquals(wrappers[t], wrapperTl.getType()); + assertEquals(primitives[t], primitiveTl.getRawType()); + assertEquals(wrappers[t], wrapperTl.getRawType()); + } + } + + public void testSerialization() throws IOException { + assertNotSerializable(new TypeLiteral>() {}); + } + + public void testTypeVariableWithNoBound() { + TypeVariable>[] typeVariables + = HasTypeParameters.class.getTypeParameters(); + + TypeLiteral aTl = TypeLiteral.get(typeVariables[0]); + assertEquals(Object.class, aTl.getRawType()); + assertEquals("A", aTl.toString()); + TypeVariable aTv = (TypeVariable) aTl.getType(); + assertEquals(HasTypeParameters.class, aTv.getGenericDeclaration()); + assertEquals("A", aTv.getName()); + assertEquals(ImmutableList.of(Object.class), ImmutableList.copyOf(aTv.getBounds())); + assertEquals("A", aTv.toString()); + assertEqualsBothWays(aTl, TypeLiteral.get(HasTypeParameters.class.getTypeParameters()[0])); + } + + public void testTypeVariablesWithSingleBound() { + TypeVariable>[] typeVariables + = HasTypeParameters.class.getTypeParameters(); + + TypeLiteral cTl = TypeLiteral.get(typeVariables[2]); + assertEquals(Object.class, cTl.getRawType()); + assertEquals("C", cTl.toString()); + TypeVariable cTv = (TypeVariable) cTl.getType(); + assertEquals(HasTypeParameters.class, cTv.getGenericDeclaration()); + assertEquals("C", cTv.getName()); + assertEquals(ImmutableList.of(Runnable.class), ImmutableList.copyOf(cTv.getBounds())); + assertEquals("C", cTv.toString()); + assertEqualsBothWays(cTl, TypeLiteral.get(HasTypeParameters.class.getTypeParameters()[2])); + } + + public void testTypeVariableWithMultipleBounds() { + TypeVariable>[] typeVariables + = HasTypeParameters.class.getTypeParameters(); + + TypeLiteral bTl = TypeLiteral.get(typeVariables[1]); + assertEquals(Object.class, bTl.getRawType()); + assertEquals("B", bTl.toString()); + TypeVariable bTv = (TypeVariable) bTl.getType(); + assertEquals(HasTypeParameters.class, bTv.getGenericDeclaration()); + assertEquals("B", bTv.getName()); + assertEquals(ImmutableList.of(Types.listOf(typeVariables[0]), Runnable.class), + ImmutableList.copyOf(bTv.getBounds())); + assertEquals("B", bTv.toString()); + assertEqualsBothWays(bTl, TypeLiteral.get(HasTypeParameters.class.getTypeParameters()[1])); + } + + class HasTypeParameters & Runnable, C extends Runnable> { + A a; B b; C c; + } +} diff --git a/src/test/java/com/google/inject/TypeLiteralTypeResolutionTest.java b/src/test/java/com/google/inject/TypeLiteralTypeResolutionTest.java new file mode 100644 index 0000000..64bc4c7 --- /dev/null +++ b/src/test/java/com/google/inject/TypeLiteralTypeResolutionTest.java @@ -0,0 +1,331 @@ +/** + * Copyright (C) 2008 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; + +import static com.google.inject.Asserts.assertEqualsBothWays; +import static com.google.inject.Asserts.assertNotSerializable; +import static com.google.inject.util.Types.arrayOf; +import static com.google.inject.util.Types.listOf; +import static com.google.inject.util.Types.newParameterizedType; +import static com.google.inject.util.Types.newParameterizedTypeWithOwner; +import static com.google.inject.util.Types.setOf; + +import com.google.common.collect.ImmutableList; +import com.google.inject.util.Types; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.AbstractCollection; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This test checks that TypeLiteral can perform type resolution on its members. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class TypeLiteralTypeResolutionTest extends TestCase { + Type arrayListOfString = newParameterizedType(ArrayList.class, String.class); + Type hasGenericFieldsOfShort = newParameterizedTypeWithOwner( + getClass(), HasGenericFields.class, Short.class); + Type hasGenericConstructorOfShort = newParameterizedTypeWithOwner( + getClass(), GenericConstructor.class, Short.class); + Type throwerOfNpe = newParameterizedTypeWithOwner( + getClass(), Thrower.class, NullPointerException.class); + Type hasArrayOfShort = newParameterizedTypeWithOwner(getClass(), HasArray.class, Short.class); + Type hasRelatedOfString = newParameterizedTypeWithOwner( + getClass(), HasRelated.class, String.class, String.class); + Type mapK = Map.class.getTypeParameters()[0]; + Type hashMapK = HashMap.class.getTypeParameters()[0]; + Type setEntryKV; + Type entryStringInteger = setOf(newParameterizedTypeWithOwner( + Map.class, Map.Entry.class, String.class, Integer.class)); + Field list; + Field instance; + Constructor newHasGenericConstructor; + Constructor newThrower; + Constructor newString; + Method stringIndexOf; + Method comparableCompareTo; + Method getArray; + Method getSetOfArray; + Method echo; + Method throwS; + + @Override protected void setUp() throws Exception { + super.setUp(); + + list = HasGenericFields.class.getField("list"); + instance = HasGenericFields.class.getField("instance"); + newHasGenericConstructor = GenericConstructor.class.getConstructor(Object.class, Object.class); + newThrower = Thrower.class.getConstructor(); + stringIndexOf = String.class.getMethod("indexOf", String.class); + newString = String.class.getConstructor(String.class); + comparableCompareTo = Comparable.class.getMethod("compareTo", Object.class); + getArray = HasArray.class.getMethod("getArray"); + getSetOfArray = HasArray.class.getMethod("getSetOfArray"); + echo = HasRelated.class.getMethod("echo", Object.class); + throwS = Thrower.class.getMethod("throwS"); + setEntryKV = HashMap.class.getMethod("entrySet").getGenericReturnType(); + } + + public void testDirectInheritance() throws NoSuchMethodException { + TypeLiteral resolver = TypeLiteral.get(arrayListOfString); + assertEquals(listOf(String.class), + resolver.getReturnType(List.class.getMethod("subList", int.class, int.class)).getType()); + assertEquals(ImmutableList.>of(TypeLiteral.get(String.class)), + resolver.getParameterTypes(Collection.class.getMethod("add", Object.class))); + } + + public void testGenericSupertype() { + TypeLiteral resolver = TypeLiteral.get(arrayListOfString); + assertEquals(newParameterizedType(Collection.class, String.class), + resolver.getSupertype(Collection.class).getType()); + assertEquals(newParameterizedType(Iterable.class, String.class), + resolver.getSupertype(Iterable.class).getType()); + assertEquals(newParameterizedType(AbstractList.class, String.class), + resolver.getSupertype(AbstractList.class).getType()); + assertEquals(Object.class, resolver.getSupertype(Object.class).getType()); + } + + public void testRecursiveTypeVariable() { + TypeLiteral resolver = TypeLiteral.get(MyInteger.class); + assertEquals(MyInteger.class, resolver.getParameterTypes(comparableCompareTo).get(0).getType()); + } + + interface MyComparable> extends Comparable {} + + static class MyInteger implements MyComparable { + int value; + public int compareTo(MyInteger o) { + return value - o.value; + } + } + + public void testFields() { + TypeLiteral resolver = TypeLiteral.get(hasGenericFieldsOfShort); + assertEquals(listOf(Short.class), resolver.getFieldType(list).getType()); + assertEquals(Short.class, resolver.getFieldType(instance).getType()); + } + + static class HasGenericFields { + public List list; + public T instance; + } + + public void testGenericConstructor() throws NoSuchMethodException { + TypeLiteral resolver = TypeLiteral.get(hasGenericConstructorOfShort); + assertEquals(Short.class, + resolver.getParameterTypes(newHasGenericConstructor).get(0).getType()); + } + + static class GenericConstructor { + @SuppressWarnings("UnusedDeclaration") + public GenericConstructor(S s, T t) {} + } + + public void testThrowsExceptions() { + TypeLiteral type = TypeLiteral.get(throwerOfNpe); + assertEquals(NullPointerException.class, type.getExceptionTypes(newThrower).get(0).getType()); + assertEquals(NullPointerException.class, type.getExceptionTypes(throwS).get(0).getType()); + } + + static class Thrower { + public Thrower() throws S {} + public void throwS() throws S {} + } + + public void testArrays() { + TypeLiteral resolver = TypeLiteral.get(hasArrayOfShort); + assertEquals(arrayOf(Short.class), resolver.getReturnType(getArray).getType()); + assertEquals(setOf(arrayOf(Short.class)), resolver.getReturnType(getSetOfArray).getType()); + } + + static interface HasArray { + T[] getArray(); + Set getSetOfArray(); + } + + public void testRelatedTypeVariables() { + TypeLiteral resolver = TypeLiteral.get(hasRelatedOfString); + assertEquals(String.class, resolver.getParameterTypes(echo).get(0).getType()); + assertEquals(String.class, resolver.getReturnType(echo).getType()); + } + + interface HasRelated { + T echo(R r); + } + + /** Ensure the cache doesn't cache too much */ + public void testCachingAndReindexing() throws NoSuchMethodException { + TypeLiteral resolver = TypeLiteral.get( + newParameterizedTypeWithOwner(getClass(), HasLists.class, String.class, Short.class)); + assertEquals(listOf(String.class), + resolver.getReturnType(HasLists.class.getMethod("listS")).getType()); + assertEquals(listOf(Short.class), + resolver.getReturnType(HasLists.class.getMethod("listT")).getType()); + } + + interface HasLists { + List listS(); + List listT(); + List> listEntries(); + } + + public void testUnsupportedQueries() throws NoSuchMethodException { + TypeLiteral resolver = TypeLiteral.get(arrayListOfString); + + try { + resolver.getExceptionTypes(stringIndexOf); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a " + + "supertype of java.util.ArrayList", e.getMessage()); + } + try { + resolver.getParameterTypes(stringIndexOf); + fail(); + } catch (Exception e) { + assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a " + + "supertype of java.util.ArrayList", e.getMessage()); + } + try { + resolver.getReturnType(stringIndexOf); + fail(); + } catch (Exception e) { + assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a " + + "supertype of java.util.ArrayList", e.getMessage()); + } + try { + resolver.getSupertype(String.class); + fail(); + } catch (Exception e) { + assertEquals("class java.lang.String is not a supertype of " + + "java.util.ArrayList", e.getMessage()); + } + try { + resolver.getExceptionTypes(newString); + fail(); + } catch (Exception e) { + assertEquals("public java.lang.String(java.lang.String) does not construct " + + "a supertype of java.util.ArrayList", e.getMessage()); + } + try { + resolver.getParameterTypes(newString); + fail(); + } catch (Exception e) { + assertEquals("public java.lang.String(java.lang.String) does not construct " + + "a supertype of java.util.ArrayList", e.getMessage()); + } + } + + public void testResolve() { + TypeLiteral typeResolver = TypeLiteral.get(StringIntegerMap.class); + assertEquals(String.class, typeResolver.resolveType(mapK)); + + typeResolver = new TypeLiteral>() {}; + assertEquals(String.class, typeResolver.resolveType(mapK)); + assertEquals(Types.mapOf(String.class, Integer.class), + typeResolver.getSupertype(Map.class).getType()); + + typeResolver = new TypeLiteral>() {}; + assertEquals(String.class, typeResolver.resolveType(mapK)); + + typeResolver = new TypeLiteral>() {}; + assertEquals(String.class, typeResolver.resolveType(mapK)); + + typeResolver = TypeLiteral.get(StringIntegerHashMap.class); + assertEquals(String.class, typeResolver.resolveType(mapK)); + assertEquals(String.class, typeResolver.resolveType(hashMapK)); + assertEquals(entryStringInteger, typeResolver.resolveType(setEntryKV)); + assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType()); + } + + public void testOnObject() { + TypeLiteral typeResolver = TypeLiteral.get(Object.class); + assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType()); + assertEquals(Object.class, typeResolver.getRawType()); + + // interfaces also resolve Object + typeResolver = TypeLiteral.get(Types.setOf(Integer.class)); + assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType()); + } + + interface StringIntegerMap extends Map {} + interface BetterMap extends Map {} + interface BestMap extends BetterMap {} + static class StringIntegerHashMap extends HashMap {} + + public void testGetSupertype() { + TypeLiteral> listOfString = new TypeLiteral>() {}; + assertEquals(Types.newParameterizedType(AbstractCollection.class, String.class), + listOfString.getSupertype(AbstractCollection.class).getType()); + + TypeLiteral arrayListOfE = TypeLiteral.get(newParameterizedType( + ArrayList.class, ArrayList.class.getTypeParameters())); + assertEquals( + newParameterizedType(AbstractCollection.class, ArrayList.class.getTypeParameters()), + arrayListOfE.getSupertype(AbstractCollection.class).getType()); + } + + public void testGetSupertypeForArraysAsList() { + Class arraysAsListClass = Arrays.asList().getClass(); + Type anotherE = arraysAsListClass.getTypeParameters()[0]; + TypeLiteral type = TypeLiteral.get(newParameterizedType(AbstractList.class, anotherE)); + assertEquals(newParameterizedType(AbstractCollection.class, anotherE), + type.getSupertype(AbstractCollection.class).getType()); + } + + public void testWildcards() throws NoSuchFieldException { + TypeLiteral> ofString = new TypeLiteral>() {}; + + assertEquals(new TypeLiteral>() {}.getType(), + ofString.getFieldType(Parameterized.class.getField("t")).getType()); + assertEquals(new TypeLiteral>() {}.getType(), + ofString.getFieldType(Parameterized.class.getField("extendsT")).getType()); + assertEquals(new TypeLiteral>() {}.getType(), + ofString.getFieldType(Parameterized.class.getField("superT")).getType()); + } + + static class Parameterized { + public List t; + public List extendsT; + public List superT; + } + + // TODO(jessewilson): tests for tricky bounded types like + + public void testEqualsAndHashCode() throws IOException { + TypeLiteral a1 = TypeLiteral.get(arrayListOfString); + TypeLiteral a2 = TypeLiteral.get(arrayListOfString); + TypeLiteral b = TypeLiteral.get(listOf(String.class)); + assertEqualsBothWays(a1, a2); + assertNotSerializable(a1); + assertFalse(a1.equals(b)); + } +} diff --git a/src/test/java/com/google/inject/example/ClientServiceWithDependencyInjection.java b/src/test/java/com/google/inject/example/ClientServiceWithDependencyInjection.java new file mode 100644 index 0000000..838a60b --- /dev/null +++ b/src/test/java/com/google/inject/example/ClientServiceWithDependencyInjection.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2006 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.example; + +import static junit.framework.Assert.assertTrue; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ClientServiceWithDependencyInjection { + +// 62 lines + +public interface Service { + void go(); +} + +public static class ServiceImpl implements ClientServiceWithDependencyInjection.Service { + public void go() { + // ... + } +} + +public static class ServiceFactory { + + private ServiceFactory() {} + + private static final Service service = new ServiceImpl(); + + public static Service getInstance() { + return service; + } +} + +public static class Client { + + private final Service service; + + public Client(Service service) { + this.service = service; + } + + public void go() { + service.go(); + } +} + +public static class ClientFactory { + + private ClientFactory() {} + + public static Client getInstance() { + Service service = ServiceFactory.getInstance(); + return new Client(service); + } +} + +public void testClient() { + MockService mock = new MockService(); + Client client = new Client(mock); + client.go(); + assertTrue(mock.isGone()); +} + +public static class MockService implements Service { + + private boolean gone = false; + + public void go() { + gone = true; + } + + public boolean isGone() { + return gone; + } +} + + public static void main(String[] args) { + new ClientServiceWithDependencyInjection().testClient(); + } +} diff --git a/src/test/java/com/google/inject/example/ClientServiceWithFactories.java b/src/test/java/com/google/inject/example/ClientServiceWithFactories.java new file mode 100644 index 0000000..b3829a1 --- /dev/null +++ b/src/test/java/com/google/inject/example/ClientServiceWithFactories.java @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2006 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.example; + +import static junit.framework.Assert.assertTrue; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ClientServiceWithFactories { + +// 58 lines + +public interface Service { + void go(); +} + +public static class ServiceImpl implements Service { + public void go() { + // ... + } +} + +public static class ServiceFactory { + + private ServiceFactory() {} + + private static Service instance = new ServiceImpl(); + + public static Service getInstance() { + return instance; + } + + public static void setInstance(Service service) { + instance = service; + } +} + +public static class Client { + + public void go() { + Service service = ServiceFactory.getInstance(); + service.go(); + } +} + +public void testClient() { + Service previous = ServiceFactory.getInstance(); + try { + final MockService mock = new MockService(); + ServiceFactory.setInstance(mock); + Client client = new Client(); + client.go(); + assertTrue(mock.isGone()); + } + finally { + ServiceFactory.setInstance(previous); + } +} + +public static class MockService implements Service { + + private boolean gone = false; + + public void go() { + gone = true; + } + + public boolean isGone() { + return gone; + } +} + + public static void main(String[] args) { + new ClientServiceWithFactories().testClient(); + } +} diff --git a/src/test/java/com/google/inject/example/ClientServiceWithGuice.java b/src/test/java/com/google/inject/example/ClientServiceWithGuice.java new file mode 100644 index 0000000..c027377 --- /dev/null +++ b/src/test/java/com/google/inject/example/ClientServiceWithGuice.java @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2006 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.example; + +import static junit.framework.Assert.assertTrue; + +import com.google.inject.AbstractModule; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Scopes; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ClientServiceWithGuice { + +// 48 lines + +public interface Service { + void go(); +} + +public static class ServiceImpl implements Service { + public void go() { + // ... + } +} + +public static class MyModule extends AbstractModule { + protected void configure() { + bind(Service.class).to(ServiceImpl.class).in(Scopes.SINGLETON); + } +} + +public static class Client { + + private final Service service; + + @Inject + public Client(Service service) { + this.service = service; + } + + public void go() { + service.go(); + } +} + +public void testClient() { + MockService mock = new MockService(); + Client client = new Client(mock); + client.go(); + assertTrue(mock.isGone()); +} + +public static class MockService implements Service { + + private boolean gone = false; + + public void go() { + gone = true; + } + + public boolean isGone() { + return gone; + } +} + +public static void main(String[] args) throws CreationException { + new ClientServiceWithGuice().testClient(); + Injector injector = Guice.createInjector(new MyModule()); + Client client = injector.getInstance(Client.class); +} +} diff --git a/src/test/java/com/google/inject/example/ClientServiceWithGuiceDefaults.java b/src/test/java/com/google/inject/example/ClientServiceWithGuiceDefaults.java new file mode 100644 index 0000000..5a3d9a7 --- /dev/null +++ b/src/test/java/com/google/inject/example/ClientServiceWithGuiceDefaults.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2006 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.example; + +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.ImplementedBy; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Singleton; + +import junit.framework.Assert; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ClientServiceWithGuiceDefaults { + +// 44 lines + +@ImplementedBy(ServiceImpl.class) +public interface Service { + void go(); +} + +@Singleton +public static class ServiceImpl implements ClientServiceWithGuiceDefaults.Service { + public void go() { + // ... + } +} + +public static class Client { + + private final Service service; + + @Inject + public Client(Service service) { + this.service = service; + } + + public void go() { + service.go(); + } +} + +public void testClient() { + MockService mock = new MockService(); + Client client = new Client(mock); + client.go(); + Assert.assertTrue(mock.isGone()); +} + +public static class MockService implements Service { + + private boolean gone = false; + + public void go() { + gone = true; + } + + public boolean isGone() { + return gone; + } +} + +public static void main(String[] args) throws CreationException { + new ClientServiceWithGuiceDefaults().testClient(); + Injector injector = Guice.createInjector(); + Client client = injector.getProvider(Client.class).get(); +} +} diff --git a/src/test/java/com/google/inject/example/JndiProvider.java b/src/test/java/com/google/inject/example/JndiProvider.java new file mode 100644 index 0000000..fd90dc0 --- /dev/null +++ b/src/test/java/com/google/inject/example/JndiProvider.java @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2006 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.example; + +import com.google.inject.Inject; +import com.google.inject.Provider; + +import javax.naming.Context; +import javax.naming.NamingException; + +class JndiProvider implements Provider { + + @Inject Context context; + final String name; + final Class type; + + JndiProvider(Class type, String name) { + this.name = name; + this.type = type; + } + + public T get() { + try { + return type.cast(context.lookup(name)); + } + catch (NamingException e) { + throw new RuntimeException(e); + } + } + + /** + * Creates a JNDI provider for the given + * type and name. + */ + static Provider fromJndi( + Class type, String name) { + return new JndiProvider(type, name); + } +} diff --git a/src/test/java/com/google/inject/example/JndiProviderClient.java b/src/test/java/com/google/inject/example/JndiProviderClient.java new file mode 100644 index 0000000..897b249 --- /dev/null +++ b/src/test/java/com/google/inject/example/JndiProviderClient.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2006 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.example; + +import static com.google.inject.example.JndiProvider.fromJndi; + +import com.google.inject.AbstractModule; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Injector; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.sql.DataSource; + +class JndiProviderClient { + + public static void main(String[] args) throws CreationException { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { +// Bind Context to the default InitialContext. +bind(Context.class).to(InitialContext.class); + +// Bind to DataSource from JNDI. +bind(DataSource.class) + .toProvider(fromJndi(DataSource.class, "...")); + } + }); + } +} diff --git a/src/test/java/com/google/inject/internal/CycleDetectingLockTest.java b/src/test/java/com/google/inject/internal/CycleDetectingLockTest.java new file mode 100644 index 0000000..0d10824 --- /dev/null +++ b/src/test/java/com/google/inject/internal/CycleDetectingLockTest.java @@ -0,0 +1,101 @@ +package com.google.inject.internal; + +import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory; + +import junit.framework.TestCase; + +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class CycleDetectingLockTest extends TestCase { + + static final long DEADLOCK_TIMEOUT_SECONDS = 1; + + /** + * Verifies that graph of threads' dependencies is not static and is calculated in runtime using + * information about specific locks. + * + *
+   *   T1: Waits on S1
+   *   T2: Locks B, sends S1, waits on S2
+   *   T1: Locks A, start locking B, sends S2, waits on S3
+   *   T2: Unlocks B, start locking A, sends S3, finishes locking A, unlocks A
+   *   T1: Finishes locking B, unlocks B, unlocks A
+   * 
+ * + *

This should succeed, even though T1 was locked on T2 and T2 is locked on T1 when T2 locks + * A. Incorrect implementation detects a cycle waiting on S3. + */ + + public void testSingletonThreadsRuntimeCircularDependency() throws Exception { + final CyclicBarrier signal1 = new CyclicBarrier(2); + final CyclicBarrier signal2 = new CyclicBarrier(2); + final CyclicBarrier signal3 = new CyclicBarrier(2); + CycleDetectingLockFactory lockFactory = new CycleDetectingLockFactory(); + final CycleDetectingLock lockA = + lockFactory.new ReentrantCycleDetectingLock("A", new ReentrantLock() { + @Override + public void lock() { + if (Thread.currentThread().getName().equals("T2")) { + try { + signal3.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + assertEquals("T1", Thread.currentThread().getName()); + } + super.lock(); + } + }); + final CycleDetectingLock lockB = + lockFactory.new ReentrantCycleDetectingLock("B", new ReentrantLock() { + @Override + public void lock() { + if (Thread.currentThread().getName().equals("T1")) { + try { + signal2.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + signal3.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + assertEquals("T2", Thread.currentThread().getName()); + } + super.lock(); + } + }); + Future firstThreadResult = Executors.newSingleThreadExecutor().submit( + new Callable() { + public Void call() throws Exception { + Thread.currentThread().setName("T1"); + signal1.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(lockA.lockOrDetectPotentialLocksCycle().isEmpty()); + assertTrue(lockB.lockOrDetectPotentialLocksCycle().isEmpty()); + lockB.unlock(); + lockA.unlock(); + return null; + } + }); + Future secondThreadResult = Executors.newSingleThreadExecutor().submit( + new Callable() { + public Void call() throws Exception { + Thread.currentThread().setName("T2"); + assertTrue(lockB.lockOrDetectPotentialLocksCycle().isEmpty()); + signal1.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + signal2.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + lockB.unlock(); + assertTrue(lockA.lockOrDetectPotentialLocksCycle().isEmpty()); + lockA.unlock(); + return null; + } + }); + + firstThreadResult.get(); + secondThreadResult.get(); + } +} diff --git a/src/test/java/com/google/inject/internal/MoreTypesTest.java b/src/test/java/com/google/inject/internal/MoreTypesTest.java new file mode 100644 index 0000000..0ce5504 --- /dev/null +++ b/src/test/java/com/google/inject/internal/MoreTypesTest.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2010 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.internal; + +import com.google.inject.TypeLiteral; + +import junit.framework.TestCase; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Set; + +/** + * @author schmitt@google.com (Peter Schmitt) + */ +public class MoreTypesTest extends TestCase { + + public void testParameterizedTypeToString() { + TypeLiteral> innerString = new TypeLiteral>(){}; + assertEquals("com.google.inject.internal.MoreTypesTest$Inner", + MoreTypes.typeToString(innerString.getType())); + + TypeLiteral>> mapInnerInteger = new TypeLiteral>>() {}; + assertEquals("java.util.Set>", + MoreTypes.typeToString(mapInnerInteger.getType())); + + TypeLiteral, Set>>> mapInnerLongToSetInnerLong = + new TypeLiteral, Set>>>() {}; + assertEquals("java.util.Map, " + + "java.util.Set>>", + MoreTypes.typeToString(mapInnerLongToSetInnerLong.getType())); + } + + public void testEquals_typeVariable() throws Exception { + Type type = getClass().getMethod("testEquals_typeVariable").getTypeParameters()[0]; + assertTrue(MoreTypes.equals(new TypeLiteral() {}.getType(), type)); + } + + public static class Inner {} +} diff --git a/src/test/java/com/google/inject/internal/UniqueAnnotationsTest.java b/src/test/java/com/google/inject/internal/UniqueAnnotationsTest.java new file mode 100644 index 0000000..9676eca --- /dev/null +++ b/src/test/java/com/google/inject/internal/UniqueAnnotationsTest.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2008 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.internal; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class UniqueAnnotationsTest extends TestCase { + + @UniqueAnnotations.Internal(31) public Void unused; + + public void testEqualsHashCodeToString() { + Annotation actual = UniqueAnnotations.create(31); + + Annotation expected = getClass().getFields()[0].getAnnotations()[0]; + + assertEquals(expected.toString(), actual.toString()); + assertEquals(expected.hashCode(), actual.hashCode()); + assertEquals(expected, actual); + } +} diff --git a/src/test/java/com/google/inject/internal/WeakKeySetTest.java b/src/test/java/com/google/inject/internal/WeakKeySetTest.java new file mode 100644 index 0000000..f11b393 --- /dev/null +++ b/src/test/java/com/google/inject/internal/WeakKeySetTest.java @@ -0,0 +1,518 @@ +/** + * 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.internal; + +import static com.google.inject.Asserts.awaitClear; +import static com.google.inject.Asserts.awaitFullGc; +import static com.google.inject.internal.WeakKeySetUtils.assertBlacklisted; +import static com.google.inject.internal.WeakKeySetUtils.assertInSet; +import static com.google.inject.internal.WeakKeySetUtils.assertNotBlacklisted; +import static com.google.inject.internal.WeakKeySetUtils.assertNotInSet; +import static com.google.inject.internal.WeakKeySetUtils.assertSourceNotInSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Scope; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; +import com.google.inject.spi.ProvisionListenerBinding; +import com.google.inject.spi.ScopeBinding; +import com.google.inject.spi.TypeConverterBinding; +import com.google.inject.spi.TypeListenerBinding; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Tests for {@link WeakKeySet}. + *

+ * Multibinding specific tests can be found in MultibinderTest and MapBinderTest. + * + * @author dweis@google.com (Daniel Weis) + */ +public class WeakKeySetTest extends TestCase { + + private WeakKeySet set; + + @Override + protected void setUp() throws Exception { + set = new WeakKeySet(new Object()); + } + + public void testEviction() { + TestState state = new TestState(); + Key key = Key.get(Integer.class); + Object source = new Object(); + + WeakReference> weakKeyRef = new WeakReference>(key); + + set.add(key, state, source); + assertInSet(set, key, 1, source); + + state = null; + + awaitFullGc(); + + assertNotInSet(set, Key.get(Integer.class)); + + // Ensure there are no hanging references. + key = null; + awaitClear(weakKeyRef); + } + + public void testEviction_nullSource() { + TestState state = new TestState(); + Key key = Key.get(Integer.class); + Object source = null; + + WeakReference> weakKeyRef = new WeakReference>(key); + + set.add(key, state, source); + assertInSet(set, key, 1, source); + + state = null; + + awaitFullGc(); + + assertNotInSet(set, Key.get(Integer.class)); + + // Ensure there are no hanging references. + key = null; + awaitClear(weakKeyRef); + } + + public void testEviction_keyOverlap_2x() { + TestState state1 = new TestState(); + TestState state2 = new TestState(); + Key key1 = Key.get(Integer.class); + Key key2 = Key.get(Integer.class); + Object source1 = new Object(); + Object source2 = new Object(); + + set.add(key1, state1, source1); + assertInSet(set, key1, 1, source1); + + set.add(key2, state2, source2); + assertInSet(set, key2, 2, source1, source2); + + WeakReference> weakKey1Ref = new WeakReference>(key1); + WeakReference> weakKey2Ref = new WeakReference>(key2); + WeakReference weakSource1Ref = new WeakReference(source1); + WeakReference weakSource2Ref = new WeakReference(source2); + + Key key = key1 = key2 = Key.get(Integer.class); + state1 = null; + + awaitFullGc(); + + assertSourceNotInSet(set, key, source1); + assertInSet(set, key, 1, source2); + + source1 = source2 = null; + + awaitClear(weakSource1Ref); + // Key1 will be referenced as the key in the sources backingSet and won't be + // GC'd. + + // Should not be GC'd until state2 goes away. + assertNotNull(weakSource2Ref.get()); + + state2 = null; + + awaitFullGc(); + + assertNotInSet(set, key); + + awaitClear(weakKey2Ref); + awaitClear(weakSource2Ref); + // Now that the backing set is emptied, key1 is released. + awaitClear(weakKey1Ref); + } + + public void testNoEviction_keyOverlap_2x() { + TestState state1 = new TestState(); + TestState state2 = new TestState(); + Key key1 = Key.get(Integer.class); + Key key2 = Key.get(Integer.class); + Object source1 = new Object(); + Object source2 = new Object(); + + set.add(key1, state1, source1); + assertInSet(set, key1, 1, source1); + + set.add(key2, state2, source2); + assertInSet(set, key2, 2, source1, source2); + + WeakReference> weakKey1Ref = new WeakReference>(key1); + WeakReference> weakKey2Ref = new WeakReference>(key2); + + Key key = key1 = key2 = Key.get(Integer.class); + + awaitFullGc(); + assertInSet(set, key, 2, source1, source2); + + // Ensure the keys don't get GC'd when states are still referenced. key1 will be present in the + // as the map key but key2 could be GC'd if the implementation does something wrong. + assertNotNull(weakKey1Ref.get()); + assertNotNull(weakKey2Ref.get()); + } + + public void testEviction_keyAndSourceOverlap_null() { + TestState state1 = new TestState(); + TestState state2 = new TestState(); + Key key1 = Key.get(Integer.class); + Key key2 = Key.get(Integer.class); + Object source = null; + + set.add(key1, state1, source); + assertInSet(set, key1, 1, source); + + set.add(key2, state2, source); + // Same source so still only one value. + assertInSet(set, key2, 1, source); + assertInSet(set, key1, 1, source); + + WeakReference> weakKey1Ref = new WeakReference>(key1); + WeakReference> weakKey2Ref = new WeakReference>(key2); + WeakReference weakSourceRef = new WeakReference(source); + + Key key = key1 = key2 = Key.get(Integer.class); + state1 = null; + + awaitFullGc(); + // Should still have a single source. + assertInSet(set, key, 1, source); + + source = null; + + awaitClear(weakSourceRef); + // Key1 will be referenced as the key in the sources backingSet and won't be + // GC'd. + + state2 = null; + + awaitFullGc(); + assertNotInSet(set, key); + + awaitClear(weakKey2Ref); + awaitClear(weakSourceRef); + // Now that the backing set is emptied, key1 is released. + awaitClear(weakKey1Ref); + } + + public void testEviction_keyAndSourceOverlap_nonNull() { + TestState state1 = new TestState(); + TestState state2 = new TestState(); + Key key1 = Key.get(Integer.class); + Key key2 = Key.get(Integer.class); + Object source = new Object(); + + set.add(key1, state1, source); + assertInSet(set, key1, 1, source); + + set.add(key2, state2, source); + // Same source so still only one value. + assertInSet(set, key2, 1, source); + + WeakReference> weakKey1Ref = new WeakReference>(key1); + WeakReference> weakKey2Ref = new WeakReference>(key2); + WeakReference weakSourceRef = new WeakReference(source); + + Key key = key1 = key2 = Key.get(Integer.class); + state1 = null; + + awaitFullGc(); + + // Same source so still only one value. + assertInSet(set, key, 1, source); + assertInSet(set, key1, 1, source); + + source = null; + + awaitFullGc(); + assertNotNull(weakSourceRef.get()); + // Key1 will be referenced as the key in the sources backingSet and won't be + // GC'd. + + state2 = null; + + awaitFullGc(); + + assertNotInSet(set, key); + + awaitClear(weakKey2Ref); + awaitClear(weakSourceRef); + // Now that the backing set is emptied, key1 is released. + awaitClear(weakKey1Ref); + } + + public void testEviction_keyOverlap_3x() { + TestState state1 = new TestState(); + TestState state2 = new TestState(); + TestState state3 = new TestState(); + Key key1 = Key.get(Integer.class); + Key key2 = Key.get(Integer.class); + Key key3 = Key.get(Integer.class); + Object source1 = new Object(); + Object source2 = new Object(); + Object source3 = new Object(); + + set.add(key1, state1, source1); + assertInSet(set, key1, 1, source1); + + set.add(key2, state2, source2); + assertInSet(set, key1, 2, source1, source2); + + set.add(key3, state3, source3); + assertInSet(set, key1, 3, source1, source2, source3); + + WeakReference> weakKey1Ref = new WeakReference>(key1); + WeakReference> weakKey2Ref = new WeakReference>(key2); + WeakReference> weakKey3Ref = new WeakReference>(key3); + WeakReference weakSource1Ref = new WeakReference(source1); + WeakReference weakSource2Ref = new WeakReference(source2); + WeakReference weakSource3Ref = new WeakReference(source3); + + Key key = key1 = key2 = key3 = Key.get(Integer.class); + state1 = null; + + awaitFullGc(); + assertSourceNotInSet(set, key, source1); + assertInSet(set, key, 2, source2, source3); + + source1 = null; + // Key1 will be referenced as the key in the sources backingSet and won't be + // GC'd. + awaitClear(weakSource1Ref); + + state2 = null; + awaitFullGc(); + assertSourceNotInSet(set, key, source2); + assertInSet(set, key, 1, source3); + + awaitClear(weakKey2Ref); + + source2 = null; + awaitClear(weakSource2Ref); + // Key1 will be referenced as the key in the sources backingSet and won't be + // GC'd. + + state3 = null; + awaitFullGc(); + assertNotInSet(set, key); + + awaitClear(weakKey3Ref); + source3 = null; + awaitClear(weakSource3Ref); + // Now that the backing set is emptied, key1 is released. + awaitClear(weakKey1Ref); + } + + public void testWeakKeySet_integration() { + Injector parentInjector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(4); + } + }); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + + Injector childInjector = parentInjector.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("bar"); + } + }); + WeakReference weakRef = new WeakReference(childInjector); + assertBlacklisted(parentInjector, Key.get(String.class)); + + // Clear the ref, GC, and ensure that we are no longer blacklisting. + childInjector = null; + awaitClear(weakRef); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + } + + public void testWeakKeySet_integration_multipleChildren() { + Injector parentInjector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(4); + } + }); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + assertNotBlacklisted(parentInjector, Key.get(Long.class)); + + Injector childInjector1 = parentInjector.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("foo"); + } + }); + WeakReference weakRef1 = new WeakReference(childInjector1); + assertBlacklisted(parentInjector, Key.get(String.class)); + assertNotBlacklisted(parentInjector, Key.get(Long.class)); + + Injector childInjector2 = parentInjector.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(Long.class).toInstance(6L); + } + }); + WeakReference weakRef2 = new WeakReference(childInjector2); + assertBlacklisted(parentInjector, Key.get(String.class)); + assertBlacklisted(parentInjector, Key.get(Long.class)); + + // Clear ref1, GC, and ensure that we still blacklist. + childInjector1 = null; + awaitClear(weakRef1); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + assertBlacklisted(parentInjector, Key.get(Long.class)); + + // Clear the ref, GC, and ensure that we are no longer blacklisting. + childInjector2 = null; + awaitClear(weakRef2); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + assertNotBlacklisted(parentInjector, Key.get(Long.class)); + } + + public void testWeakKeySet_integration_multipleChildren_overlappingKeys() { + Injector parentInjector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(4); + } + }); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + + Injector childInjector1 = parentInjector.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("foo"); + } + }); + WeakReference weakRef1 = new WeakReference(childInjector1); + assertBlacklisted(parentInjector, Key.get(String.class)); + + Injector childInjector2 = parentInjector.createChildInjector(new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("bar"); + } + }); + WeakReference weakRef2 = new WeakReference(childInjector2); + assertBlacklisted(parentInjector, Key.get(String.class)); + + // Clear ref1, GC, and ensure that we still blacklist. + childInjector1 = null; + awaitClear(weakRef1); + assertBlacklisted(parentInjector, Key.get(String.class)); + + // Clear the ref, GC, and ensure that we are no longer blacklisting. + childInjector2 = null; + awaitClear(weakRef2); + assertNotBlacklisted(parentInjector, Key.get(String.class)); + } + + private static class TestState implements State { + public State parent() { + return new TestState(); + } + + public BindingImpl getExplicitBinding(Key key) { + return null; + } + + public Map, Binding> getExplicitBindingsThisLevel() { + throw new UnsupportedOperationException(); + } + + public void putBinding(Key key, BindingImpl binding) { + throw new UnsupportedOperationException(); + } + + public ScopeBinding getScopeBinding(Class scopingAnnotation) { + return null; + } + + public void putScopeBinding(Class annotationType, ScopeBinding scope) { + throw new UnsupportedOperationException(); + } + + public void addConverter(TypeConverterBinding typeConverterBinding) { + throw new UnsupportedOperationException(); + } + + public TypeConverterBinding getConverter(String stringValue, TypeLiteral type, Errors errors, + Object source) { + throw new UnsupportedOperationException(); + } + + public Iterable getConvertersThisLevel() { + return ImmutableSet.of(); + } + + public void addTypeListener(TypeListenerBinding typeListenerBinding) { + throw new UnsupportedOperationException(); + } + + public List getTypeListenerBindings() { + return ImmutableList.of(); + } + + public void addProvisionListener(ProvisionListenerBinding provisionListenerBinding) { + throw new UnsupportedOperationException(); + } + + public List getProvisionListenerBindings() { + return ImmutableList.of(); + } + + public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { + throw new UnsupportedOperationException(); + } + + public List getScannerBindings() { + return ImmutableList.of(); + } + + public void blacklist(Key key, State state, Object source) { + } + + public boolean isBlacklisted(Key key) { + return true; + } + + public Set getSourcesForBlacklistedKey(Key key) { + throw new UnsupportedOperationException(); + } + + public Object lock() { + throw new UnsupportedOperationException(); + } + + public Object singletonCreationLock() { + throw new UnsupportedOperationException(); + } + + public Map, Scope> getScopes() { + return ImmutableMap.of(); + } + } +} diff --git a/src/test/java/com/google/inject/internal/WeakKeySetUtils.java b/src/test/java/com/google/inject/internal/WeakKeySetUtils.java new file mode 100644 index 0000000..b023aa1 --- /dev/null +++ b/src/test/java/com/google/inject/internal/WeakKeySetUtils.java @@ -0,0 +1,102 @@ +/** + * 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.internal; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import com.google.inject.Injector; +import com.google.inject.Key; + +import java.util.Set; + +/** + * Utilities for verifying com.google.inject.internal.WeakKeySet is not leaking memory. + * + * @author dweis@google.com (Daniel Weis) + */ +public final class WeakKeySetUtils { + + private WeakKeySetUtils() {} + + public static void assertBlacklisted(Injector injector, Key key) { + assertBlacklistState(injector, key, true); + } + + public static void assertNotBlacklisted(Injector injector, Key key) { + assertBlacklistState(injector, key, false); + } + + public static void assertNotInSet(WeakKeySet set, Key key) { + // if we're expecting it to not be in the set, loop around and wait for threads to run. + for (int i = 0; i < 10; i++) { + if (!set.contains(key)) { + break; + } + sleep(); + } + assertFalse(set.contains(key)); + assertNull(set.getSources(Key.get(Integer.class))); + } + + public static void assertInSet(WeakKeySet set, Key key, int expectedSources, + Object... sources) { + assertTrue(set.contains(key)); + assertEquals(expectedSources, set.getSources(key).size()); + for (Object source : sources) { + assertTrue("didn't contain source: " + source, set.getSources(key).contains(source)); + } + } + + public static void assertSourceNotInSet(WeakKeySet set, Key key, Object source) { + // if we're expecting it to not be a source, loop around and wait for threads to run. + for (int i = 0; i < 10; i++) { + Set sources = set.getSources(key); + assertNotNull("expected at least one source", source); + if (!sources.contains(source)) { + break; + } + sleep(); + } + Set sources = set.getSources(key); + assertNotNull("expected at least one source", source); + assertFalse(sources.contains(source)); + } + + private static void assertBlacklistState(Injector injector, Key key, boolean isBlacklisted) { + // if we're expecting it to not be blacklisted, loop around and wait for threads to run. + if (!isBlacklisted) { + for (int i = 0; i < 10; i++) { + if (!((InjectorImpl) injector).state.isBlacklisted(key)) { + break; + } + sleep(); + } + } + assertEquals(isBlacklisted, ((InjectorImpl) injector).state.isBlacklisted(key)); + } + + private static void sleep() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + Thread.yield(); + } +} diff --git a/src/test/java/com/google/inject/matcher/MatcherTest.java b/src/test/java/com/google/inject/matcher/MatcherTest.java new file mode 100644 index 0000000..58fbdf2 --- /dev/null +++ b/src/test/java/com/google/inject/matcher/MatcherTest.java @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2006 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.matcher; + +import static com.google.inject.Asserts.assertEqualWhenReserialized; +import static com.google.inject.Asserts.assertEqualsBothWays; +import static com.google.inject.matcher.Matchers.annotatedWith; +import static com.google.inject.matcher.Matchers.any; +import static com.google.inject.matcher.Matchers.identicalTo; +import static com.google.inject.matcher.Matchers.inPackage; +import static com.google.inject.matcher.Matchers.inSubpackage; +import static com.google.inject.matcher.Matchers.not; +import static com.google.inject.matcher.Matchers.only; +import static com.google.inject.matcher.Matchers.returns; +import static com.google.inject.matcher.Matchers.subclassesOf; + +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.AbstractList; + +/** + * @author crazybob@google.com (Bob Lee) + */ + +public class MatcherTest extends TestCase { + + public void testAny() { + assertTrue(any().matches(null)); + assertEquals("any()", any().toString()); + assertEqualsBothWays(any(), any()); + assertFalse(any().equals(not(any()))); + } + + public void testNot() { + assertFalse(not(any()).matches(null)); + assertEquals("not(any())", not(any()).toString()); + assertEqualsBothWays(not(any()), not(any())); + assertFalse(not(any()).equals(any())); + } + + public void testAnd() { + assertTrue(any().and(any()).matches(null)); + assertFalse(any().and(not(any())).matches(null)); + assertEquals("and(any(), any())", any().and(any()).toString()); + assertEqualsBothWays(any().and(any()), any().and(any())); + assertFalse(any().and(any()).equals(not(any()))); + } + + public void testOr() { + assertTrue(any().or(not(any())).matches(null)); + assertFalse(not(any()).or(not(any())).matches(null)); + assertEquals("or(any(), any())", any().or(any()).toString()); + assertEqualsBothWays(any().or(any()), any().or(any())); + assertFalse(any().or(any()).equals(not(any()))); + } + + public void testAnnotatedWith() { + assertTrue(annotatedWith(Foo.class).matches(Bar.class)); + assertFalse(annotatedWith(Foo.class).matches( + MatcherTest.class.getMethods()[0])); + assertEquals("annotatedWith(Foo.class)", annotatedWith(Foo.class).toString()); + assertEqualsBothWays(annotatedWith(Foo.class), annotatedWith(Foo.class)); + assertFalse(annotatedWith(Foo.class).equals(annotatedWith(Named.class))); + + try { + annotatedWith(Baz.class); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testSubclassesOf() { + assertTrue(subclassesOf(Runnable.class).matches(Runnable.class)); + assertTrue(subclassesOf(Runnable.class).matches(MyRunnable.class)); + assertFalse(subclassesOf(Runnable.class).matches(Object.class)); + assertEquals("subclassesOf(Runnable.class)", subclassesOf(Runnable.class).toString()); + assertEqualsBothWays(subclassesOf(Runnable.class), subclassesOf(Runnable.class)); + assertFalse(subclassesOf(Runnable.class).equals(subclassesOf(Object.class))); + } + + public void testOnly() { + assertTrue(only(1000).matches(1000)); + assertFalse(only(1).matches(1000)); + assertEquals("only(1)", only(1).toString()); + assertEqualsBothWays(only(1), only(1)); + assertFalse(only(1).equals(only(2))); + } + + @SuppressWarnings("UnnecessaryBoxing") + public void testIdenticalTo() { + Object o = new Object(); + assertEquals("identicalTo(1)", identicalTo(1).toString()); + assertTrue(identicalTo(o).matches(o)); + assertFalse(identicalTo(o).matches(new Object())); + assertEqualsBothWays(identicalTo(o), identicalTo(o)); + assertFalse(identicalTo(1).equals(identicalTo(new Integer(1)))); + } + + public void testInPackage() { + Package matchersPackage = Matchers.class.getPackage(); + assertEquals("inPackage(com.google.inject.matcher)", inPackage(matchersPackage).toString()); + assertTrue(inPackage(matchersPackage).matches(MatcherTest.class)); + assertFalse(inPackage(matchersPackage).matches(Object.class)); + assertEqualsBothWays(inPackage(matchersPackage), inPackage(matchersPackage)); + assertFalse(inPackage(matchersPackage).equals(inPackage(Object.class.getPackage()))); + } + + public void testInSubpackage() { + String stringPackageName = String.class.getPackage().getName(); + assertEquals("inSubpackage(java.lang)", inSubpackage(stringPackageName).toString()); + assertTrue(inSubpackage(stringPackageName).matches(Object.class)); + assertTrue(inSubpackage(stringPackageName).matches(Method.class)); + assertFalse(inSubpackage(stringPackageName).matches(Matchers.class)); + assertFalse(inSubpackage("jav").matches(Object.class)); + assertEqualsBothWays(inSubpackage(stringPackageName), inSubpackage(stringPackageName)); + assertFalse(inSubpackage(stringPackageName).equals(inSubpackage(Matchers.class.getPackage().getName()))); + } + + public void testReturns() throws NoSuchMethodException { + Matcher predicate = returns(only(String.class)); + assertTrue(predicate.matches( + Object.class.getMethod("toString"))); + assertFalse(predicate.matches( + Object.class.getMethod("hashCode"))); + assertEquals("returns(only(class java.lang.String))", returns(only(String.class)).toString()); + assertEqualsBothWays(predicate, returns(only(String.class))); + assertFalse(predicate.equals(returns(only(Integer.class)))); + } + + public void testSerialization() throws IOException { + assertEqualWhenReserialized(any()); + assertEqualWhenReserialized(not(any())); + assertEqualWhenReserialized(annotatedWith(Named.class)); + assertEqualWhenReserialized(annotatedWith(Names.named("foo"))); + assertEqualWhenReserialized(only("foo")); + assertEqualWhenReserialized(identicalTo(Object.class)); + assertEqualWhenReserialized(inPackage(String.class.getPackage())); + assertEqualWhenReserialized(inSubpackage(String.class.getPackage().getName())); + assertEqualWhenReserialized(returns(any())); + assertEqualWhenReserialized(subclassesOf(AbstractList.class)); + assertEqualWhenReserialized(only("a").or(only("b"))); + assertEqualWhenReserialized(only("a").and(only("b"))); + } + + static abstract class MyRunnable implements Runnable {} + + @Retention(RetentionPolicy.RUNTIME) + @interface Foo {} + + @Foo + static class Bar {} + + @interface Baz {} + + @Baz + static class Car {} +} diff --git a/src/test/java/com/google/inject/name/NamedEquivalanceTest.java b/src/test/java/com/google/inject/name/NamedEquivalanceTest.java new file mode 100644 index 0000000..36dee0c --- /dev/null +++ b/src/test/java/com/google/inject/name/NamedEquivalanceTest.java @@ -0,0 +1,258 @@ +/** + * Copyright (C) 2010 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.name; + +import static com.google.inject.Asserts.assertContains; + +import com.google.inject.AbstractModule; +import com.google.inject.ConfigurationException; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provides; + +import junit.framework.TestCase; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.util.Properties; + +/** + * 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. + * + * @author cgdecker@gmail.com (Colin Decker) + */ +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 GUICE_PROVIDER_METHOD_MODULE = getGuiceBindingProviderMethodModule(); + private static final Module JSR330_PROVIDER_METHOD_MODULE = getJsr330BindingProviderMethodModule(); + + 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"))); + + assertEquals(keyForAnnotationType(com.google.inject.name.Named.class), + keyForAnnotationType(javax.inject.Named.class)); + } + + private static Key keyForAnnotation(Annotation annotation) { + return Key.get(String.class, annotation); + } + + private static Key keyForAnnotationType(Class annotationType) { + return Key.get(String.class, annotationType); + } + + public void testBindingWithNamesCanInjectBothTypes() { + assertInjectionsSucceed(GUICE_BINDING_MODULE); + } + + public void testBindingWithJsr330AnnotationCanInjectBothTypes() { + assertInjectionsSucceed(JSR330_BINDING_MODULE); + } + + public void testBindingWithGuiceNamedAnnotatedProviderMethodCanInjectBothTypes() { + assertInjectionsSucceed(GUICE_PROVIDER_METHOD_MODULE); + } + + public void testBindingWithJsr330NamedAnnotatedProviderMethodCanInjectBothTypes() { + assertInjectionsSucceed(JSR330_PROVIDER_METHOD_MODULE); + } + + public void testBindingDifferentTypesWithSameValueIsIgnored() { + assertDuplicateBinding(GUICE_BINDING_MODULE, JSR330_BINDING_MODULE, false); + assertDuplicateBinding(JSR330_BINDING_MODULE, GUICE_BINDING_MODULE, false); + } + + public void testBindingDifferentTypesWithSameValueIsAnErrorWithProviderMethods() { + assertDuplicateBinding(GUICE_PROVIDER_METHOD_MODULE, JSR330_PROVIDER_METHOD_MODULE, true); + assertDuplicateBinding(JSR330_PROVIDER_METHOD_MODULE, GUICE_PROVIDER_METHOD_MODULE, true); + } + + public void testBindingDifferentTypesWithSameValueIsAnErrorMixed() { + assertDuplicateBinding(GUICE_BINDING_MODULE, JSR330_PROVIDER_METHOD_MODULE, true); + assertDuplicateBinding(JSR330_BINDING_MODULE, GUICE_PROVIDER_METHOD_MODULE, true); + } + + public void testMissingBindingForGuiceNamedUsesSameTypeInErrorMessage() { + assertMissingBindingErrorMessageUsesType(GuiceNamedClient.class); + } + + public void testMissingBindingForJsr330NamedUsesSameTypeInErrorMessage() { + assertMissingBindingErrorMessageUsesType(Jsr330NamedClient.class); + } + + public void testBindPropertiesWorksWithJsr330() { + assertInjectionsSucceed(new AbstractModule() { + @Override protected void configure() { + Properties properties = new Properties(); + properties.put("foo", "bar"); + Names.bindProperties(binder(), properties); + } + }); + } + + private static void assertMissingBindingErrorMessageUsesType(Class clientType) { + try { + Guice.createInjector().getInstance(clientType); + fail("should have thrown ConfigurationException"); + } catch (ConfigurationException e) { + assertContains(e.getMessage(), + "No implementation for java.lang.String annotated with @com.google.inject.name.Named(value=foo) was bound."); + } + } + + private static void assertDuplicateBinding(Module a, Module b, boolean fails) { + try { + Guice.createInjector(a, b); + if(fails) { + fail("should have thrown CreationException"); + } + } catch (CreationException e) { + if(fails) { + assertContains(e.getMessage(), + "A binding to java.lang.String annotated with @com.google.inject.name.Named(value=foo) was already configured"); + } else { + throw e; + } + } + } + + private static Module moduleWithAnnotation(final Annotation annotation) { + return new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(annotation).to("bar"); + } + }; + } + + private static void assertInjectionsSucceed(Module module) { + Injector injector = Guice.createInjector(module); + assertInjected(injector.getInstance(GuiceNamedClient.class), injector + .getInstance(Jsr330NamedClient.class)); + } + + private static void assertInjected(GuiceNamedClient guiceClient, Jsr330NamedClient jsr330Client) { + assertEquals("bar", guiceClient.foo); + assertEquals("bar", jsr330Client.foo); + } + + private static Module getJsr330BindingProviderMethodModule() { + return new AbstractModule() { + @Override protected void configure() {} + @SuppressWarnings("unused") @Provides @javax.inject.Named("foo") String provideFoo() { + return "bar"; + } + }; + } + + private static Module getGuiceBindingProviderMethodModule() { + return new AbstractModule() { + @Override protected void configure() {} + @SuppressWarnings("unused") @Provides @Named("foo") String provideFoo() { + return "bar"; + } + }; + } + + private static class GuiceNamedClient { + @Inject @Named("foo") String foo; + } + + private static class Jsr330NamedClient { + @Inject @javax.inject.Named("foo") String foo; + } + + private static class JsrNamed implements javax.inject.Named, Serializable { + private final String value; + + public JsrNamed(String value) { + this.value = value; + } + + public String value() { + return this.value; + } + + public int hashCode() { + // This is specified in java.lang.Annotation. + return (127 * "value".hashCode()) ^ value.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof javax.inject.Named)) { + return false; + } + + javax.inject.Named other = (javax.inject.Named) o; + return value.equals(other.value()); + } + + public String toString() { + return "@" + javax.inject.Named.class.getName() + "(value=" + value + ")"; + } + + public Class annotationType() { + return javax.inject.Named.class; + } + + private static final long serialVersionUID = 0; + } + + private static class GuiceNamed implements com.google.inject.name.Named, Serializable { + private final String value; + + public GuiceNamed(String value) { + this.value = value; + } + + public String value() { + return this.value; + } + + public int hashCode() { + // This is specified in java.lang.Annotation. + return (127 * "value".hashCode()) ^ value.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof com.google.inject.name.Named)) { + return false; + } + + com.google.inject.name.Named other = (com.google.inject.name.Named) o; + return value.equals(other.value()); + } + + public String toString() { + return "@" + com.google.inject.name.Named.class.getName() + "(value=" + value + ")"; + } + + public Class annotationType() { + return com.google.inject.name.Named.class; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/test/java/com/google/inject/name/NamesTest.java b/src/test/java/com/google/inject/name/NamesTest.java new file mode 100644 index 0000000..0df3513 --- /dev/null +++ b/src/test/java/com/google/inject/name/NamesTest.java @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2008 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.name; + +import static com.google.inject.Asserts.assertEqualWhenReserialized; +import static com.google.inject.Asserts.assertEqualsBothWays; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class NamesTest extends TestCase { + + @Named("foo") private String foo; + private Named namedFoo; + + protected void setUp() throws Exception { + super.setUp(); + namedFoo = getClass().getDeclaredField("foo").getAnnotation(Named.class); + } + + public void testConsistentEqualsAndHashcode() { + Named actual = Names.named("foo"); + assertEqualsBothWays(namedFoo, actual); + assertEquals(namedFoo.toString(), actual.toString()); + } + + public void testNamedIsSerializable() throws IOException { + assertEqualWhenReserialized(Names.named("foo")); + } + + public void testBindPropertiesUsingProperties() { + final Properties teams = new Properties(); + teams.setProperty("SanJose", "Sharks"); + teams.setProperty("Edmonton", "Oilers"); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + Names.bindProperties(binder(), teams); + } + }); + + assertEquals("Sharks", injector.getInstance(Key.get(String.class, Names.named("SanJose")))); + assertEquals("Oilers", injector.getInstance(Key.get(String.class, Names.named("Edmonton")))); + } + + public void testBindPropertiesUsingMap() { + final Map properties = ImmutableMap.of( + "SanJose", "Sharks", "Edmonton", "Oilers"); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + Names.bindProperties(binder(), properties); + } + }); + + assertEquals("Sharks", injector.getInstance(Key.get(String.class, Names.named("SanJose")))); + assertEquals("Oilers", injector.getInstance(Key.get(String.class, Names.named("Edmonton")))); + } + + public void testBindPropertiesIncludesInheritedProperties() { + Properties defaults = new Properties(); + defaults.setProperty("Edmonton", "Eskimos"); + defaults.setProperty("Regina", "Pats"); + + final Properties teams = new Properties(defaults); + teams.setProperty("SanJose", "Sharks"); + teams.setProperty("Edmonton", "Oilers"); + + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + Names.bindProperties(binder(), teams); + } + }); + + assertEquals("Pats", injector.getInstance(Key.get(String.class, Names.named("Regina")))); + assertEquals("Oilers", injector.getInstance(Key.get(String.class, Names.named("Edmonton")))); + assertEquals("Sharks", injector.getInstance(Key.get(String.class, Names.named("SanJose")))); + + try { + injector.getInstance(Key.get(String.class, Names.named("Calgary"))); + fail(); + } catch (RuntimeException expected) { + } + } +} diff --git a/src/test/java/com/google/inject/spi/BindingTargetVisitorTest.java b/src/test/java/com/google/inject/spi/BindingTargetVisitorTest.java new file mode 100644 index 0000000..7740551 --- /dev/null +++ b/src/test/java/com/google/inject/spi/BindingTargetVisitorTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2008 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.spi; + +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Injector; + +import junit.framework.TestCase; + +/** + * Simple little test that should compile. Ensures that wildcards on the + * generics are correct. + * + * @author phopkins@gmail.com + */ +public class BindingTargetVisitorTest extends TestCase { + public void testBindingTargetVisitorTypeTest() throws Exception { + Injector injector = Guice.createInjector(); + for (Binding binding : injector.getBindings().values()) { + binding.acceptTargetVisitor(new DefaultBindingTargetVisitor() {}); + } + } +} diff --git a/src/test/java/com/google/inject/spi/ElementApplyToTest.java b/src/test/java/com/google/inject/spi/ElementApplyToTest.java new file mode 100644 index 0000000..d483ba4 --- /dev/null +++ b/src/test/java/com/google/inject/spi/ElementApplyToTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2008 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.spi; + +import com.google.inject.Module; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ElementApplyToTest extends ElementsTest { + + @Override + protected void checkModule(Module module, ElementVisitor... visitors) { + // convert from module to elements and back + super.checkModule(Elements.getModule(Elements.getElements(module)), visitors); + } +} \ No newline at end of file diff --git a/src/test/java/com/google/inject/spi/ElementSourceTest.java b/src/test/java/com/google/inject/spi/ElementSourceTest.java new file mode 100644 index 0000000..93fbeac --- /dev/null +++ b/src/test/java/com/google/inject/spi/ElementSourceTest.java @@ -0,0 +1,175 @@ +package com.google.inject.spi; + +import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.BindingAnnotation; +import com.google.inject.Module; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.List; + +/** + * Tests for {@link ElementSource}. + */ +public class ElementSourceTest extends TestCase { + + private static final StackTraceElement BINDER_INSTALL = + new StackTraceElement("com.google.inject.spi.Elements$RecordingBinder", "install", + "Unknown Source", 234 /* line number*/); + + public void testCallStackSize() { + ModuleSource moduleSource = createModuleSource(); + StackTraceElement[] bindingCallStack = new StackTraceElement[3]; + bindingCallStack[0] = new StackTraceElement( + "com.google.inject.spi.Elements$RecordingBinder", "bind", "Unknown Source", 200); + bindingCallStack[1] = new StackTraceElement( + "com.google.inject.spi.Elements$RecordingBinder", "bind", "Unknown Source", 100); + bindingCallStack[2] = new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$C", "configure", "Unknown Source", 100); + ElementSource elementSource = new ElementSource( + null /* No original element source */, "" /* Don't care */, moduleSource, bindingCallStack); + assertEquals(10 /* call stack size */, elementSource.getStackTrace().length); + } + + public void testGetCallStack_IntegrationTest() throws Exception { + List elements = Elements.getElements(new A()); + for (Element element : elements) { + if (element instanceof Binding) { + Binding binding = (Binding) element; + Class annotationType = binding.getKey().getAnnotationType(); + if (annotationType != null && annotationType.equals(SampleAnnotation.class)) { + ElementSource elementSource = (ElementSource) binding.getSource(); + List moduleClassNames = elementSource.getModuleClassNames(); + // Check module class names + // Module C + assertEquals("com.google.inject.spi.ElementSourceTest$C", moduleClassNames.get(0)); + // Module B + assertEquals("com.google.inject.spi.ElementSourceTest$B", moduleClassNames.get(1)); + // Module A + assertEquals("com.google.inject.spi.ElementSourceTest$A", moduleClassNames.get(2)); + StackTraceElement[] callStack = elementSource.getStackTrace(); + switch(getIncludeStackTraceOption()) { + case OFF: + // Check declaring source + StackTraceElement stackTraceElement = + (StackTraceElement) elementSource.getDeclaringSource(); + assertEquals(new StackTraceElement( + "com.google.inject.spi.ElementSourceTest$C", "configure", null, -1), + stackTraceElement); + // Check call stack + assertEquals(0, callStack.length); + return; + case ONLY_FOR_DECLARING_SOURCE: + // Check call stack + assertEquals(0, callStack.length); + return; + case COMPLETE: + // Check call stack + int skippedCallStackSize = new Throwable().getStackTrace().length - 1; + assertEquals(skippedCallStackSize + 15, elementSource.getStackTrace().length); + assertEquals("com.google.inject.spi.Elements$RecordingBinder", + callStack[0].getClassName()); + assertEquals("com.google.inject.spi.Elements$RecordingBinder", + callStack[1].getClassName()); + assertEquals("com.google.inject.AbstractModule", + callStack[2].getClassName()); + // Module C + assertEquals("com.google.inject.spi.ElementSourceTest$C", + callStack[3].getClassName()); + assertEquals("configure", + callStack[3].getMethodName()); + assertEquals("Unknown Source", + callStack[3].getFileName()); + assertEquals("com.google.inject.AbstractModule", + callStack[4].getClassName()); + assertEquals("com.google.inject.spi.Elements$RecordingBinder", + callStack[5].getClassName()); + // Module B + assertEquals("com.google.inject.spi.ElementSourceTest$B", + callStack[6].getClassName()); + assertEquals("com.google.inject.spi.Elements$RecordingBinder", + callStack[7].getClassName()); + // Module A + assertEquals("com.google.inject.AbstractModule", + callStack[8].getClassName()); + assertEquals("com.google.inject.spi.ElementSourceTest$A", + callStack[9].getClassName()); + assertEquals("com.google.inject.AbstractModule", + callStack[10].getClassName()); + assertEquals("com.google.inject.spi.Elements$RecordingBinder", + callStack[11].getClassName()); + assertEquals("com.google.inject.spi.Elements", + callStack[12].getClassName()); + assertEquals("com.google.inject.spi.Elements", + callStack[13].getClassName()); + assertEquals("com.google.inject.spi.ElementSourceTest", + callStack[14].getClassName()); + // Check modules index + List indexes = elementSource.getModuleConfigurePositionsInStackTrace(); + assertEquals((int) indexes.get(0), 4); + assertEquals((int) indexes.get(1), 6); + assertEquals((int) indexes.get(2), 10); + return; + } + } + } + } + fail("The test should not reach this line."); + } + + private ModuleSource createModuleSource() { + // First module + StackTraceElement[] partialCallStack = new StackTraceElement[1]; + partialCallStack[0] = BINDER_INSTALL; + ModuleSource moduleSource = new ModuleSource(new A(), partialCallStack); + // Second module + partialCallStack = new StackTraceElement[2]; + partialCallStack[0] = BINDER_INSTALL; + partialCallStack[1] = new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$A", "configure", "Unknown Source", 100); + moduleSource = moduleSource.createChild(new B(), partialCallStack); + // Third module + partialCallStack = new StackTraceElement[4]; + partialCallStack[0] = BINDER_INSTALL; + partialCallStack[1] = new StackTraceElement("class1", "method1", "Class1.java", 1); + partialCallStack[2] = new StackTraceElement("class2", "method2", "Class2.java", 2); + partialCallStack[3] = new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$B", "configure", "Unknown Source", 200); + return moduleSource.createChild(new C(), partialCallStack); + } + + private static class A extends AbstractModule { + @Override + public void configure() { + install(new B()); + } + } + + private static class B implements Module { + @Override + public void configure(Binder binder) { + binder.install(new C()); + } + } + + @Retention(RUNTIME) + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @BindingAnnotation + @interface SampleAnnotation { } + + private static class C extends AbstractModule { + @Override + public void configure() { + bind(String.class).annotatedWith(SampleAnnotation.class).toInstance("the value"); + } + } +} diff --git a/src/test/java/com/google/inject/spi/ElementsTest.java b/src/test/java/com/google/inject/spi/ElementsTest.java new file mode 100644 index 0000000..3a106d8 --- /dev/null +++ b/src/test/java/com/google/inject/spi/ElementsTest.java @@ -0,0 +1,1308 @@ +/** + * Copyright (C) 2008 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.spi; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static com.google.inject.Asserts.isIncludeStackTraceComplete; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.BindingAnnotation; +import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.MembersInjector; +import com.google.inject.Module; +import com.google.inject.PrivateBinder; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.google.inject.binder.AnnotatedConstantBindingBuilder; +import com.google.inject.binder.ConstantBindingBuilder; +import com.google.inject.binder.ScopedBindingBuilder; +import com.google.inject.matcher.Matcher; +import com.google.inject.matcher.Matchers; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.util.Providers; +import org.junit.Assert; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ElementsTest extends Assert { + + // Binder fidelity tests + + public void testAddMessageErrorCommand() { + checkModule( + new AbstractModule() { + @Override protected void configure() { + addError("Message %s %d %s", "A", 5, "C"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("Message A 5 C", command.getMessage()); + assertNull(command.getCause()); + assertContains(command.getSources().toString(), + ElementsTest.class.getName(), + getDeclaringSourcePart(ElementsTest.class)); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testAddThrowableErrorCommand() { + checkModule( + new AbstractModule() { + protected void configure() { + addError(new Exception("A")); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("A", command.getCause().getMessage()); + assertEquals(command.getMessage(), + "An exception was caught and reported. Message: A"); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testErrorsAddedWhenExceptionsAreThrown() { + checkModule( + new AbstractModule() { + protected void configure() { + install(new AbstractModule() { + protected void configure() { + throw new RuntimeException("Throwing RuntimeException in AbstractModule.configure()."); + } + }); + + addError("Code after the exception still gets executed"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("Throwing RuntimeException in AbstractModule.configure().", + command.getCause().getMessage()); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("Code after the exception still gets executed", + command.getMessage()); + return null; + } + } + ); + } + + private T getInstance(Binding binding) { + return binding.acceptTargetVisitor(Elements.getInstanceVisitor()); + } + + public void testBindConstantAnnotations() { + checkModule( + new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(SampleAnnotation.class).to("A"); + bindConstant().annotatedWith(Names.named("Bee")).to("B"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey()); + assertEquals("A", getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(String.class, Names.named("Bee")), command.getKey()); + assertEquals("B", getInstance(command)); + return null; + } + } + ); + } + + public void testBindConstantTypes() { + checkModule( + new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(Names.named("String")).to("A"); + bindConstant().annotatedWith(Names.named("int")).to(2); + bindConstant().annotatedWith(Names.named("long")).to(3L); + bindConstant().annotatedWith(Names.named("boolean")).to(false); + bindConstant().annotatedWith(Names.named("double")).to(5.0d); + bindConstant().annotatedWith(Names.named("float")).to(6.0f); + bindConstant().annotatedWith(Names.named("short")).to((short) 7); + bindConstant().annotatedWith(Names.named("char")).to('h'); + bindConstant().annotatedWith(Names.named("byte")).to((byte) 8); + bindConstant().annotatedWith(Names.named("Class")).to(Iterator.class); + bindConstant().annotatedWith(Names.named("Enum")).to(CoinSide.TAILS); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(String.class, Names.named("String")), command.getKey()); + assertEquals("A", getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Integer.class, Names.named("int")), command.getKey()); + assertEquals(2, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Long.class, Names.named("long")), command.getKey()); + assertEquals(3L, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Boolean.class, Names.named("boolean")), command.getKey()); + assertEquals(false, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Double.class, Names.named("double")), command.getKey()); + assertEquals(5.0d, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Float.class, Names.named("float")), command.getKey()); + assertEquals(6.0f, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Short.class, Names.named("short")), command.getKey()); + assertEquals((short) 7, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Character.class, Names.named("char")), command.getKey()); + assertEquals('h', getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Byte.class, Names.named("byte")), command.getKey()); + assertEquals((byte) 8, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(Class.class, Names.named("Class")), command.getKey()); + assertEquals(Iterator.class, getInstance(command)); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(CoinSide.class, Names.named("Enum")), command.getKey()); + assertEquals(CoinSide.TAILS, getInstance(command)); + return null; + } + } + ); + } + + public void testBindKeysNoAnnotations() { + FailingElementVisitor keyChecker = new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class), command.getKey()); + return null; + } + }; + + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("A"); + bind(new TypeLiteral() {}).toInstance("B"); + bind(Key.get(String.class)).toInstance("C"); + } + }, + keyChecker, + keyChecker, + keyChecker + ); + } + + public void testBindKeysWithAnnotationType() { + FailingElementVisitor annotationChecker = new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey()); + return null; + } + }; + + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class).annotatedWith(SampleAnnotation.class).toInstance("A"); + bind(new TypeLiteral() {}).annotatedWith(SampleAnnotation.class).toInstance("B"); + } + }, + annotationChecker, + annotationChecker + ); + } + + public void testBindKeysWithAnnotationInstance() { + FailingElementVisitor annotationChecker = new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class, Names.named("a")), command.getKey()); + return null; + } + }; + + + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class).annotatedWith(Names.named("a")).toInstance("B"); + bind(new TypeLiteral() {}).annotatedWith(Names.named("a")).toInstance("C"); + } + }, + annotationChecker, + annotationChecker + ); + } + + public void testBindToProvider() { + final Provider aProvider = new Provider() { + public String get() { + return "A"; + } + }; + + final javax.inject.Provider intJavaxProvider = new javax.inject.Provider() { + public Integer get() { + return 42; + } + }; + + final javax.inject.Provider doubleJavaxProvider = new javax.inject.Provider() { + @javax.inject.Inject String string; + + public Double get() { + return 42.42; + } + }; + + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class).toProvider(aProvider); + bind(Integer.class).toProvider(intJavaxProvider); + bind(Double.class).toProvider(doubleJavaxProvider); + bind(List.class).toProvider(ListProvider.class); + bind(Collection.class).toProvider(Key.get(ListProvider.class)); + bind(Iterable.class).toProvider(new TypeLiteral>() {}); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof ProviderInstanceBinding); + assertEquals(Key.get(String.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit( + ProviderInstanceBinding binding) { + assertSame(aProvider, binding.getUserSuppliedProvider()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof ProviderInstanceBinding); + assertEquals(Key.get(Integer.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit( + ProviderInstanceBinding binding) { + assertSame(intJavaxProvider, binding.getUserSuppliedProvider()); + assertEquals(42, binding.getUserSuppliedProvider().get()); + // we don't wrap this w/ dependencies if there were none. + assertFalse(binding.getUserSuppliedProvider() instanceof HasDependencies); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof ProviderInstanceBinding); + assertEquals(Key.get(Double.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit( + ProviderInstanceBinding binding) { + assertSame(doubleJavaxProvider, binding.getUserSuppliedProvider()); + assertEquals(42.42, binding.getUserSuppliedProvider().get()); + // we do wrap it with dependencies if there were some. + //assertTrue(binding.getUserSuppliedProvider() instanceof HasDependencies); + Set> deps = + ((HasDependencies) binding.getUserSuppliedProvider()).getDependencies(); + assertEquals(1, deps.size()); + assertEquals(String.class, + deps.iterator().next().getKey().getTypeLiteral().getRawType()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof ProviderKeyBinding); + assertEquals(Key.get(List.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ProviderKeyBinding binding) { + assertEquals(Key.get(ListProvider.class), binding.getProviderKey()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof ProviderKeyBinding); + assertEquals(Key.get(Collection.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ProviderKeyBinding binding) { + assertEquals(Key.get(ListProvider.class), binding.getProviderKey()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof ProviderKeyBinding); + assertEquals(Key.get(Iterable.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ProviderKeyBinding binding) { + assertEquals(new Key>() {}, binding.getProviderKey()); + return null; + } + }); + return null; + } + } + ); + } + + public void testBindToLinkedBinding() { + checkModule( + new AbstractModule() { + protected void configure() { + bind(List.class).to(ArrayList.class); + bind(Map.class).to(new TypeLiteral>() {}); + bind(Set.class).to(Key.get(TreeSet.class, SampleAnnotation.class)); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof LinkedKeyBinding); + assertEquals(Key.get(List.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(LinkedKeyBinding binding) { + assertEquals(Key.get(ArrayList.class), binding.getLinkedKey()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof LinkedKeyBinding); + assertEquals(Key.get(Map.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(LinkedKeyBinding binding) { + assertEquals(Key.get(new TypeLiteral>() {}), + binding.getLinkedKey()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof LinkedKeyBinding); + assertEquals(Key.get(Set.class), command.getKey()); + command.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(LinkedKeyBinding binding) { + assertEquals(Key.get(TreeSet.class, SampleAnnotation.class), + binding.getLinkedKey()); + return null; + } + }); + return null; + } + } + ); + } + + public void testBindToInstance() { + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("A"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertTrue(command instanceof InstanceBinding); + assertEquals(Key.get(String.class), command.getKey()); + assertEquals("A", getInstance(command)); + return null; + } + } + ); + } + + public void testBindInScopes() { + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class); + bind(List.class).to(ArrayList.class).in(Scopes.SINGLETON); + bind(Map.class).to(HashMap.class).in(Singleton.class); + bind(Set.class).to(TreeSet.class).asEagerSingleton(); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitNoScoping() { + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(List.class), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitScope(Scope scope) { + assertEquals(Scopes.SINGLETON, scope); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(Map.class), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitScopeAnnotation(Class annotation) { + assertEquals(Singleton.class, annotation); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(Set.class), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + public Void visitEagerSingleton() { + return null; + } + }); + return null; + } + } + ); + } + + public void testBindToInstanceInScope() { + checkModule( + new AbstractModule() { + protected void configure() { + AnnotatedBindingBuilder b = bind(String.class); + b.toInstance("A"); + b.in(Singleton.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("Setting the scope is not permitted when binding to a single instance.", + command.getMessage()); + assertNull(command.getCause()); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testBindToInstanceScope() { + checkModule( + new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("A"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertEquals(Key.get(String.class), binding.getKey()); + binding.acceptScopingVisitor(new FailingBindingScopingVisitor() { + public Void visitEagerSingleton() { + return null; + } + }); + return null; + } + } + ); + } + + public void testBindScope() { + checkModule( + new AbstractModule() { + protected void configure() { + bindScope(SampleAnnotation.class, Scopes.NO_SCOPE); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(ScopeBinding command) { + assertSame(SampleAnnotation.class, command.getAnnotationType()); + assertSame(Scopes.NO_SCOPE, command.getScope()); + return null; + } + } + ); + } + + public void testBindListener() { + final Matcher typeMatcher = Matchers.only(TypeLiteral.get(String.class)); + final TypeListener listener = new TypeListener() { + public void hear(TypeLiteral type, TypeEncounter encounter) { + throw new UnsupportedOperationException(); + } + }; + + checkModule( + new AbstractModule() { + protected void configure() { + bindListener(typeMatcher, listener); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(TypeListenerBinding binding) { + assertSame(typeMatcher, binding.getTypeMatcher()); + assertSame(listener, binding.getListener()); + return null; + } + } + ); + } + + public void testConvertToTypes() { + final TypeConverter typeConverter = new TypeConverter() { + public Object convert(String value, TypeLiteral toType) { + return value; + } + }; + + checkModule( + new AbstractModule() { + protected void configure() { + convertToTypes(Matchers.any(), typeConverter); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(TypeConverterBinding command) { + assertSame(typeConverter, command.getTypeConverter()); + assertSame(Matchers.any(), command.getTypeMatcher()); + return null; + } + } + ); + } + + public void testGetProvider() { + checkModule( + new AbstractModule() { + protected void configure() { + Provider keyGetProvider + = getProvider(Key.get(String.class, SampleAnnotation.class)); + try { + keyGetProvider.get(); + } catch (IllegalStateException e) { + assertEquals("This Provider cannot be used until the Injector has been created.", + e.getMessage()); + } + + Provider typeGetProvider = getProvider(String.class); + try { + typeGetProvider.get(); + } catch (IllegalStateException e) { + assertEquals("This Provider cannot be used until the Injector has been created.", + e.getMessage()); + } + } + }, + + new FailingElementVisitor() { + @Override public Void visit(ProviderLookup command) { + assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey()); + assertNull(command.getDelegate()); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(ProviderLookup command) { + assertEquals(Key.get(String.class), command.getKey()); + assertNull(command.getDelegate()); + return null; + } + } + ); + } + + public void testElementInitialization() { + final AtomicReference> providerFromBinder + = new AtomicReference>(); + final AtomicReference> membersInjectorFromBinder + = new AtomicReference>(); + + final AtomicReference lastInjected = new AtomicReference(); + final MembersInjector stringInjector = new MembersInjector() { + public void injectMembers(String instance) { + lastInjected.set(instance); + } + }; + + checkModule( + new AbstractModule() { + protected void configure() { + providerFromBinder.set(getProvider(String.class)); + membersInjectorFromBinder.set(getMembersInjector(String.class)); + } + }, + + new FailingElementVisitor() { + public Void visit(ProviderLookup providerLookup) { + @SuppressWarnings("unchecked") // we know that T is a String here + ProviderLookup stringLookup = (ProviderLookup) providerLookup; + stringLookup.initializeDelegate(Providers.of("out")); + + assertEquals("out", providerFromBinder.get().get()); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(MembersInjectorLookup lookup) { + @SuppressWarnings("unchecked") // we know that T is a String here + MembersInjectorLookup stringLookup = (MembersInjectorLookup) lookup; + stringLookup.initializeDelegate(stringInjector); + + membersInjectorFromBinder.get().injectMembers("in"); + assertEquals("in", lastInjected.get()); + return null; + } + }); + } + + public void testGetMembersInjector() { + checkModule( + new AbstractModule() { + protected void configure() { + MembersInjector> typeMembersInjector + = getMembersInjector(new TypeLiteral>() {}); + try { + typeMembersInjector.injectMembers(new A()); + } catch (IllegalStateException e) { + assertEquals( + "This MembersInjector cannot be used until the Injector has been created.", + e.getMessage()); + } + + MembersInjector classMembersInjector = getMembersInjector(String.class); + try { + classMembersInjector.injectMembers("hello"); + } catch (IllegalStateException e) { + assertEquals( + "This MembersInjector cannot be used until the Injector has been created.", + e.getMessage()); + } + } + }, + + new FailingElementVisitor() { + @Override public Void visit(MembersInjectorLookup command) { + assertEquals(new TypeLiteral>() {}, command.getType()); + assertNull(command.getDelegate()); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(MembersInjectorLookup command) { + assertEquals(TypeLiteral.get(String.class), command.getType()); + assertNull(command.getDelegate()); + return null; + } + } + ); + } + + public void testRequestInjection() { + final Object firstObject = new Object(); + final Object secondObject = new Object(); + + checkModule( + new AbstractModule() { + protected void configure() { + requestInjection(firstObject); + requestInjection(secondObject); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(InjectionRequest command) { + assertEquals(firstObject, command.getInstance()); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(InjectionRequest command) { + assertEquals(secondObject, command.getInstance()); + return null; + } + } + ); + } + + public void testRequestStaticInjection() { + checkModule( + new AbstractModule() { + protected void configure() { + requestStaticInjection(ArrayList.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(StaticInjectionRequest command) { + assertEquals(ArrayList.class, command.getType()); + return null; + } + } + ); + } + + public void testNewPrivateBinder() { + final Key collection = Key.get(Collection.class, SampleAnnotation.class); + final Key arrayList = Key.get(ArrayList.class); + final ImmutableSet> collections = ImmutableSet.>of(arrayList, collection); + + final Key a = Key.get(String.class, Names.named("a")); + final Key b = Key.get(String.class, Names.named("b")); + final ImmutableSet> ab = ImmutableSet.of(a, b); + + checkModule( + new AbstractModule() { + protected void configure() { + PrivateBinder one = binder().newPrivateBinder(); + one.expose(ArrayList.class); + one.expose(Collection.class).annotatedWith(SampleAnnotation.class); + one.bind(List.class).to(ArrayList.class); + + PrivateBinder two = binder().withSource("1 FooBar") + .newPrivateBinder().withSource("2 FooBar"); + two.expose(String.class).annotatedWith(Names.named("a")); + two.expose(b); + two.bind(List.class).to(ArrayList.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(PrivateElements one) { + assertEquals(collections, one.getExposedKeys()); + checkElements(one.getElements(), + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertEquals(Key.get(List.class), binding.getKey()); + return null; + } + } + ); + return null; + } + }, + + new ExternalFailureVisitor() { + @Override public Void visit(PrivateElements two) { + assertEquals(ab, two.getExposedKeys()); + assertEquals("1 FooBar", two.getSource().toString()); + checkElements(two.getElements(), + new ExternalFailureVisitor() { + @Override public Void visit(Binding binding) { + assertEquals("2 FooBar", binding.getSource().toString()); + assertEquals(Key.get(List.class), binding.getKey()); + return null; + } + } + ); + return null; + } + } + ); + } + + public void testBindWithMultipleAnnotationsAddsError() { + checkModule( + new AbstractModule() { + protected void configure() { + AnnotatedBindingBuilder abb = bind(String.class); + abb.annotatedWith(SampleAnnotation.class); + abb.annotatedWith(Names.named("A")); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("More than one annotation is specified for this binding.", + command.getMessage()); + assertNull(command.getCause()); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testBindWithMultipleTargetsAddsError() { + checkModule( + new AbstractModule() { + protected void configure() { + AnnotatedBindingBuilder abb = bind(String.class); + abb.toInstance("A"); + abb.toInstance("B"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("Implementation is set more than once.", command.getMessage()); + assertNull(command.getCause()); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testBindWithMultipleScopesAddsError() { + checkModule( + new AbstractModule() { + protected void configure() { + ScopedBindingBuilder sbb = bind(List.class).to(ArrayList.class); + sbb.in(Scopes.NO_SCOPE); + sbb.asEagerSingleton(); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("Scope is set more than once.", command.getMessage()); + assertNull(command.getCause()); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testBindConstantWithMultipleAnnotationsAddsError() { + checkModule( + new AbstractModule() { + protected void configure() { + AnnotatedConstantBindingBuilder cbb = bindConstant(); + cbb.annotatedWith(SampleAnnotation.class).to("A"); + cbb.annotatedWith(Names.named("A")); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message command) { + assertEquals("More than one annotation is specified for this binding.", + command.getMessage()); + assertNull(command.getCause()); + assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testBindConstantWithMultipleTargetsAddsError() { + checkModule( + new AbstractModule() { + protected void configure() { + ConstantBindingBuilder cbb = bindConstant().annotatedWith(SampleAnnotation.class); + cbb.to("A"); + cbb.to("B"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Message message) { + assertEquals("Constant value is set more than once.", message.getMessage()); + assertNull(message.getCause()); + assertContains(message.getSource(), getDeclaringSourcePart(ElementsTest.class)); + return null; + } + } + ); + } + + public void testBindToConstructor() throws NoSuchMethodException, NoSuchFieldException { + final Constructor aConstructor = A.class.getDeclaredConstructor(); + final Constructor bConstructor = B.class.getDeclaredConstructor(Object.class); + final Field field = B.class.getDeclaredField("stage"); + + checkModule( + new AbstractModule() { + protected void configure() { + bind(A.class).toConstructor(aConstructor); + bind(B.class).toConstructor(bConstructor, new TypeLiteral>() {}) + .in(Singleton.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertEquals(new Key() {}, binding.getKey()); + + return binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ConstructorBinding constructorBinding) { + InjectionPoint injectionPoint = constructorBinding.getConstructor(); + assertEquals(aConstructor, injectionPoint.getMember()); + assertEquals(new TypeLiteral() {}, injectionPoint.getDeclaringType()); + return null; + } + }); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertEquals(new Key() {}, binding.getKey()); + binding.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitScopeAnnotation(Class annotation) { + assertEquals(Singleton.class, annotation); + return null; + } + }); + + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ConstructorBinding constructorBinding) { + assertEquals(bConstructor, constructorBinding.getConstructor().getMember()); + assertEquals(Key.get(Integer.class), + getOnlyElement(constructorBinding.getConstructor().getDependencies()).getKey()); + assertEquals(field, + getOnlyElement(constructorBinding.getInjectableMembers()).getMember()); + assertEquals(2, constructorBinding.getDependencies().size()); + return null; + } + }); + return null; + } + } + ); + } + + public void testBindToMalformedConstructor() throws NoSuchMethodException, NoSuchFieldException { + final Constructor constructor = C.class.getDeclaredConstructor(Integer.class); + + checkModule( + new AbstractModule() { + protected void configure() { + bind(C.class).toConstructor(constructor); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertEquals(Key.get(C.class), binding.getKey()); + assertTrue(binding instanceof UntargettedBinding); + return null; + } + }, + + new ExternalFailureVisitor() { + @Override public Void visit(Message message) { + assertContains(message.getMessage(), + C.class.getName() + ".a has more than one annotation ", + Named.class.getName(), SampleAnnotation.class.getName()); + return null; + } + }, + + new ExternalFailureVisitor() { + @Override public Void visit(Message message) { + assertContains(message.getMessage(), + C.class.getName() + ".() has more than one annotation ", + Named.class.getName(), SampleAnnotation.class.getName()); + return null; + } + } + ); + } + + // Business logic tests + + public void testModulesAreInstalledAtMostOnce() { + final AtomicInteger aConfigureCount = new AtomicInteger(0); + final Module a = new AbstractModule() { + public void configure() { + aConfigureCount.incrementAndGet(); + } + }; + + Elements.getElements(a, a); + assertEquals(1, aConfigureCount.get()); + + aConfigureCount.set(0); + Module b = new AbstractModule() { + protected void configure() { + install(a); + install(a); + } + }; + + Elements.getElements(b); + assertEquals(1, aConfigureCount.get()); + } + + /** + * Ensures the module performs the commands consistent with {@code visitors}. + */ + protected void checkModule(Module module, ElementVisitor... visitors) { + List elements = Elements.getElements(module); + assertEquals(elements.size(), visitors.length); + checkElements(elements, visitors); + } + + protected void checkElements(List elements, ElementVisitor... visitors) { + for (int i = 0; i < visitors.length; i++) { + ElementVisitor visitor = visitors[i]; + Element element = elements.get(i); + if (!(element instanceof Message)) { + ElementSource source = (ElementSource) element.getSource(); + assertFalse(source.getModuleClassNames().isEmpty()); + if (isIncludeStackTraceComplete()) { + assertTrue(source.getStackTrace().length > 0); + } else { + assertEquals(0, source.getStackTrace().length); + } + } + if (!(visitor instanceof ExternalFailureVisitor)) { + assertContains(element.getSource().toString(), getDeclaringSourcePart(ElementsTest.class)); + } + element.acceptVisitor(visitor); + } + } + + private static class ListProvider implements Provider { + public List get() { + return new ArrayList(); + } + } + + private static class TProvider implements Provider { + public T get() { + return null; + } + } + + /** + * By extending this interface rather than FailingElementVisitor, the source of the error doesn't + * need to contain the string {@code ElementsTest.java}. + */ + abstract class ExternalFailureVisitor extends FailingElementVisitor {} + + @Retention(RUNTIME) + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @BindingAnnotation + public @interface SampleAnnotation { } + + public enum CoinSide { HEADS, TAILS } + + static class A { + @Inject Stage stage; + } + + static class B { + @Inject Stage stage; + B(T t) {} + } + + static class C { + @Inject @Named("foo") @SampleAnnotation String a; + C(@Named("bar") @SampleAnnotation Integer b) {} + } +} diff --git a/src/test/java/com/google/inject/spi/FailingBindingScopingVisitor.java b/src/test/java/com/google/inject/spi/FailingBindingScopingVisitor.java new file mode 100644 index 0000000..bd08c4b --- /dev/null +++ b/src/test/java/com/google/inject/spi/FailingBindingScopingVisitor.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2008 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.spi; + +import com.google.inject.Scope; + +import junit.framework.AssertionFailedError; + +import java.lang.annotation.Annotation; + +public class FailingBindingScopingVisitor implements BindingScopingVisitor { + + public Void visitEagerSingleton() { + throw new AssertionFailedError(); + } + + public Void visitScope(Scope scope) { + throw new AssertionFailedError(); + } + + public Void visitScopeAnnotation(Class scopeAnnotation) { + throw new AssertionFailedError(); + } + + public Void visitNoScoping() { + throw new AssertionFailedError(); + } +} \ No newline at end of file diff --git a/src/test/java/com/google/inject/spi/FailingElementVisitor.java b/src/test/java/com/google/inject/spi/FailingElementVisitor.java new file mode 100644 index 0000000..d40d084 --- /dev/null +++ b/src/test/java/com/google/inject/spi/FailingElementVisitor.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2008 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.spi; + +import junit.framework.AssertionFailedError; + +class FailingElementVisitor extends DefaultElementVisitor { + @Override protected Void visitOther(Element element) { + throw new AssertionFailedError(); + } +} diff --git a/src/test/java/com/google/inject/spi/FailingTargetVisitor.java b/src/test/java/com/google/inject/spi/FailingTargetVisitor.java new file mode 100644 index 0000000..68d5f7e --- /dev/null +++ b/src/test/java/com/google/inject/spi/FailingTargetVisitor.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2008 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.spi; + +import com.google.inject.Binding; + +import junit.framework.AssertionFailedError; + +public class FailingTargetVisitor extends DefaultBindingTargetVisitor { + @Override protected Void visitOther(Binding binding) { + throw new AssertionFailedError(); + } +} diff --git a/src/test/java/com/google/inject/spi/HasDependenciesTest.java b/src/test/java/com/google/inject/spi/HasDependenciesTest.java new file mode 100644 index 0000000..670f2f4 --- /dev/null +++ b/src/test/java/com/google/inject/spi/HasDependenciesTest.java @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2008 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.spi; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; + +import junit.framework.TestCase; + +import java.util.Set; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class HasDependenciesTest extends TestCase { + + /** + * When an instance implements HasDependencies, the injected dependencies aren't used. + */ + public void testInstanceWithDependencies() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class).toInstance(new AWithDependencies()); + } + }); + + InstanceBinding binding = (InstanceBinding) injector.getBinding(A.class); + assertEquals(ImmutableSet.>of(Dependency.get(Key.get(Integer.class))), + binding.getDependencies()); + } + + public void testInstanceWithoutDependencies() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class).toInstance(new A()); + } + }); + + InstanceBinding binding = (InstanceBinding) injector.getBinding(A.class); + Dependency onlyDependency = Iterables.getOnlyElement(binding.getDependencies()); + assertEquals(Key.get(String.class), onlyDependency.getKey()); + } + + public void testProvider() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(A.class).toProvider(new ProviderOfA()); + } + }); + + ProviderInstanceBinding binding = (ProviderInstanceBinding) injector.getBinding(A.class); + Dependency onlyDependency = Iterables.getOnlyElement(binding.getDependencies()); + assertEquals(Key.get(String.class), onlyDependency.getKey()); + } + + static class A { + @Inject void injectUnusedDependencies(String unused) {} + } + + static class ProviderOfA implements Provider { + @Inject void injectUnusedDependencies(String unused) {} + + public A get() { + throw new UnsupportedOperationException(); + } + } + + static class AWithDependencies extends A implements HasDependencies { + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(Key.get(Integer.class))); + } + } + + static class ProviderOfAWithDependencies + extends ProviderOfA implements ProviderWithDependencies { + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(Key.get(Integer.class))); + } + } +} diff --git a/src/test/java/com/google/inject/spi/InjectionPointTest.java b/src/test/java/com/google/inject/spi/InjectionPointTest.java new file mode 100644 index 0000000..2ba4f5b --- /dev/null +++ b/src/test/java/com/google/inject/spi/InjectionPointTest.java @@ -0,0 +1,366 @@ +/** + * Copyright (C) 2008 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.spi; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.assertEqualsBothWays; +import static com.google.inject.Asserts.assertNotSerializable; +import static com.google.inject.name.Names.named; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.inject.ConfigurationException; +import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.ErrorsException; +import com.google.inject.name.Named; +import com.google.inject.spi.InjectionPoint.Signature; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class InjectionPointTest extends TestCase { + + public @Inject @Named("a") String foo; + public @Inject void bar(@Named("b") String param) {} + + public static class Constructable { + @Inject public Constructable(@Named("c") String param) {} + } + + public void testFieldInjectionPoint() throws NoSuchFieldException, IOException, ErrorsException { + TypeLiteral typeLiteral = TypeLiteral.get(getClass()); + Field fooField = getClass().getField("foo"); + + InjectionPoint injectionPoint = new InjectionPoint(typeLiteral, fooField, false); + assertSame(fooField, injectionPoint.getMember()); + assertFalse(injectionPoint.isOptional()); + assertEquals(getClass().getName() + ".foo", injectionPoint.toString()); + assertEqualsBothWays(injectionPoint, new InjectionPoint(typeLiteral, fooField, false)); + assertNotSerializable(injectionPoint); + + Dependency dependency = getOnlyElement(injectionPoint.getDependencies()); + assertEquals("Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=a)]@" + + getClass().getName() + ".foo", dependency.toString()); + assertEquals(fooField, dependency.getInjectionPoint().getMember()); + assertEquals(-1, dependency.getParameterIndex()); + assertEquals(Key.get(String.class, named("a")), dependency.getKey()); + assertFalse(dependency.isNullable()); + assertNotSerializable(dependency); + assertEqualsBothWays(dependency, + getOnlyElement(new InjectionPoint(typeLiteral, fooField, false).getDependencies())); + } + + public void testMethodInjectionPoint() throws Exception { + TypeLiteral typeLiteral = TypeLiteral.get(getClass()); + + Method barMethod = getClass().getMethod("bar", String.class); + InjectionPoint injectionPoint = new InjectionPoint(typeLiteral, barMethod, false); + assertSame(barMethod, injectionPoint.getMember()); + assertFalse(injectionPoint.isOptional()); + assertEquals(getClass().getName() + ".bar()", injectionPoint.toString()); + assertEqualsBothWays(injectionPoint, new InjectionPoint(typeLiteral, barMethod, false)); + assertNotSerializable(injectionPoint); + + Dependency dependency = getOnlyElement(injectionPoint.getDependencies()); + assertEquals("Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=b)]@" + + getClass().getName() + ".bar()[0]", dependency.toString()); + assertEquals(barMethod, dependency.getInjectionPoint().getMember()); + assertEquals(0, dependency.getParameterIndex()); + assertEquals(Key.get(String.class, named("b")), dependency.getKey()); + assertFalse(dependency.isNullable()); + assertNotSerializable(dependency); + assertEqualsBothWays(dependency, + getOnlyElement(new InjectionPoint(typeLiteral, barMethod, false).getDependencies())); + } + + public void testConstructorInjectionPoint() throws NoSuchMethodException, IOException, + ErrorsException { + TypeLiteral typeLiteral = TypeLiteral.get(Constructable.class); + + Constructor constructor = Constructable.class.getConstructor(String.class); + InjectionPoint injectionPoint = new InjectionPoint(typeLiteral, constructor); + assertSame(constructor, injectionPoint.getMember()); + assertFalse(injectionPoint.isOptional()); + assertEquals(Constructable.class.getName() + ".()", injectionPoint.toString()); + assertEqualsBothWays(injectionPoint, new InjectionPoint(typeLiteral, constructor)); + assertNotSerializable(injectionPoint); + + Dependency dependency = getOnlyElement(injectionPoint.getDependencies()); + assertEquals("Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=c)]@" + + Constructable.class.getName() + ".()[0]", dependency.toString()); + assertEquals(constructor, dependency.getInjectionPoint().getMember()); + assertEquals(0, dependency.getParameterIndex()); + assertEquals(Key.get(String.class, named("c")), dependency.getKey()); + assertFalse(dependency.isNullable()); + assertNotSerializable(dependency); + assertEqualsBothWays(dependency, + getOnlyElement(new InjectionPoint(typeLiteral, constructor).getDependencies())); + } + + public void testUnattachedDependency() throws IOException { + Dependency dependency = Dependency.get(Key.get(String.class, named("d"))); + assertEquals("Key[type=java.lang.String, annotation=@com.google.inject.name.Named(value=d)]", + dependency.toString()); + assertNull(dependency.getInjectionPoint()); + assertEquals(-1, dependency.getParameterIndex()); + assertEquals(Key.get(String.class, named("d")), dependency.getKey()); + assertTrue(dependency.isNullable()); + assertNotSerializable(dependency); + assertEqualsBothWays(dependency, Dependency.get(Key.get(String.class, named("d")))); + } + + public void testForConstructor() throws NoSuchMethodException { + Constructor constructor = HashSet.class.getConstructor(); + TypeLiteral> hashSet = new TypeLiteral>() {}; + + InjectionPoint injectionPoint = InjectionPoint.forConstructor(constructor, hashSet); + assertSame(constructor, injectionPoint.getMember()); + assertEquals(ImmutableList.of(), injectionPoint.getDependencies()); + assertFalse(injectionPoint.isOptional()); + + try { + InjectionPoint.forConstructor(constructor, new TypeLiteral>() {}); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), "java.util.LinkedHashSet", + " does not define java.util.HashSet.()", + " while locating java.util.LinkedHashSet"); + } + + try { + InjectionPoint.forConstructor((Constructor) constructor, new TypeLiteral>() {}); + } catch (ConfigurationException expected) { + assertContains(expected.getMessage(), "java.util.Set", + " does not define java.util.HashSet.()", + " while locating java.util.Set"); + } + } + + public void testForConstructorOf() { + InjectionPoint injectionPoint = InjectionPoint.forConstructorOf(Constructable.class); + assertEquals(Constructable.class.getName() + ".()", injectionPoint.toString()); + } + + public void testAddForInstanceMethodsAndFields() throws Exception { + Method instanceMethod = HasInjections.class.getMethod("instanceMethod", String.class); + Field instanceField = HasInjections.class.getField("instanceField"); + + TypeLiteral type = TypeLiteral.get(HasInjections.class); + assertEquals(ImmutableSet.of( + new InjectionPoint(type, instanceMethod, false), + new InjectionPoint(type, instanceField, false)), + InjectionPoint.forInstanceMethodsAndFields(HasInjections.class)); + } + + public void testAddForStaticMethodsAndFields() throws Exception { + Method staticMethod = HasInjections.class.getMethod("staticMethod", String.class); + Field staticField = HasInjections.class.getField("staticField"); + + Set injectionPoints = InjectionPoint.forStaticMethodsAndFields( + HasInjections.class); + assertEquals(ImmutableSet.of( + new InjectionPoint(TypeLiteral.get(HasInjections.class), staticMethod, false), + new InjectionPoint(TypeLiteral.get(HasInjections.class), staticField, false)), + injectionPoints); + } + + static class HasInjections { + @Inject public static void staticMethod(@Named("a") String a) {} + @Inject @Named("c") public static String staticField; + @Inject public void instanceMethod(@Named("d") String d) {} + @Inject @Named("f") public String instanceField; + } + + public void testAddForParameterizedInjections() { + TypeLiteral type = new TypeLiteral>() {}; + + InjectionPoint constructor = InjectionPoint.forConstructorOf(type); + assertEquals(new Key>() {}, + getOnlyElement(constructor.getDependencies()).getKey()); + + InjectionPoint field = getOnlyElement(InjectionPoint.forInstanceMethodsAndFields(type)); + assertEquals(new Key>() {}, getOnlyElement(field.getDependencies()).getKey()); + } + + static class ParameterizedInjections { + @Inject Set setOfTees; + @Inject public ParameterizedInjections(Map map) {} + } + + public void testSignature() throws Exception { + Signature fooA = new Signature(Foo.class.getDeclaredMethod( + "a", String.class, int.class)); + Signature fooB = new Signature(Foo.class.getDeclaredMethod("b")); + Signature barA = new Signature(Bar.class.getDeclaredMethod( + "a", String.class, int.class)); + Signature barB = new Signature(Bar.class.getDeclaredMethod("b")); + + assertEquals(fooA.hashCode(), barA.hashCode()); + assertEquals(fooB.hashCode(), barB.hashCode()); + assertEquals(fooA, barA); + assertEquals(fooB, barB); + } + + static class Foo { + void a(String s, int i) {} + int b() { + return 0; + } + } + static class Bar { + public void a(String s, int i) {} + void b() {} + } + + public void testOverrideBehavior() { + Set points; + + points = InjectionPoint.forInstanceMethodsAndFields(Super.class); + assertEquals(points.toString(), 6, points.size()); + assertPoints(points, Super.class, "atInject", "gInject", "privateAtAndPublicG", + "privateGAndPublicAt", "atFirstThenG", "gFirstThenAt"); + + points = InjectionPoint.forInstanceMethodsAndFields(Sub.class); + assertEquals(points.toString(), 7, points.size()); + // Superclass will always have is private members injected, + // and 'gInject' was last @Injected in Super, so that remains the owner + assertPoints(points, Super.class, "privateAtAndPublicG", "privateGAndPublicAt", "gInject"); + // Subclass also has the "private" methods, but they do not override + // the superclass' methods, and it now owns the inject2 methods. + assertPoints(points, Sub.class, "privateAtAndPublicG", "privateGAndPublicAt", + "atFirstThenG", "gFirstThenAt"); + + points = InjectionPoint.forInstanceMethodsAndFields(SubSub.class); + assertEquals(points.toString(), 6, points.size()); + // Superclass still has all the injection points it did before.. + assertPoints(points, Super.class, "privateAtAndPublicG", "privateGAndPublicAt", "gInject"); + // Subclass is missing the privateGAndPublicAt because it first became public with + // javax.inject.Inject and was overrode without an annotation, which means it + // disappears. (It was guice @Inject in Super, but it was private there, so it doesn't + // effect the annotations of the subclasses.) + assertPoints(points, Sub.class, "privateAtAndPublicG", "atFirstThenG", "gFirstThenAt"); + } + + /** + * This test serves two purposes: + * 1) It makes sure that the bridge methods javax generates don't stop + * us from injecting superclass methods in the case of javax.inject.Inject. + * This would happen prior to java8 (where javac didn't copy annotations + * from the superclass into the subclass method when it generated the + * bridge methods). + * + * 2) It makes sure that the methods we're going to inject have the correct + * generic types. Java8 copies the annotations from super to subclasses, + * but it doesn't copy the generic type information. Guice would naively + * consider the subclass an injectable method and eject the superclass + * from the 'overrideIndex', leaving only a class with improper generic types. + */ + public void testSyntheticBridgeMethodsInSubclasses() { + Set points; + + points = InjectionPoint.forInstanceMethodsAndFields(RestrictedSuper.class); + assertPointDependencies(points, new TypeLiteral>() {}); + assertEquals(points.toString(), 2, points.size()); + assertPoints(points, RestrictedSuper.class, "jInject", "gInject"); + + points = InjectionPoint.forInstanceMethodsAndFields(ExposedSub.class); + assertPointDependencies(points, new TypeLiteral>() {}); + assertEquals(points.toString(), 2, points.size()); + assertPoints(points, RestrictedSuper.class, "jInject", "gInject"); + } + + private void assertPoints(Iterable points, Class clazz, + String... methodNames) { + Set methods = new HashSet(); + for (InjectionPoint point : points) { + if (point.getDeclaringType().getRawType() == clazz) { + methods.add(point.getMember().getName()); + } + } + assertEquals(points.toString(), ImmutableSet.copyOf(methodNames), methods); + } + + /** Asserts that each injection point has the specified dependencies, in the given order. */ + private void assertPointDependencies(Iterable points, + TypeLiteral... literals) { + for (InjectionPoint point : points) { + assertEquals(literals.length, point.getDependencies().size()); + for (Dependency dep : point.getDependencies()) { + assertEquals(literals[dep.getParameterIndex()], dep.getKey().getTypeLiteral()); + } + } + } + + static class Super { + @javax.inject.Inject public void atInject() {} + @com.google.inject.Inject public void gInject() {} + + @javax.inject.Inject private void privateAtAndPublicG() {} + @com.google.inject.Inject private void privateGAndPublicAt() {} + + @javax.inject.Inject public void atFirstThenG() {} + @com.google.inject.Inject public void gFirstThenAt() {} + } + + static class Sub extends Super { + public void atInject() {} + public void gInject() {} + + @com.google.inject.Inject public void privateAtAndPublicG() {} + @javax.inject.Inject public void privateGAndPublicAt() {} + + @com.google.inject.Inject + @Override + public void atFirstThenG() {} + + @javax.inject.Inject + @Override + public void gFirstThenAt() {} + } + + static class SubSub extends Sub { + @Override public void privateAtAndPublicG() {} + @Override public void privateGAndPublicAt() {} + + @Override public void atFirstThenG() {} + @Override public void gFirstThenAt() {} + } + + static class RestrictedSuper { + @com.google.inject.Inject public void gInject(Provider p) {} + @javax.inject.Inject public void jInject(Provider p) {} + } + + public static class ExposedSub extends RestrictedSuper { + // The subclass may generate bridge/synthetic methods to increase the visibility + // of the superclass methods, since the superclass was package-private but this is public. + } +} diff --git a/src/test/java/com/google/inject/spi/InjectorSpiTest.java b/src/test/java/com/google/inject/spi/InjectorSpiTest.java new file mode 100644 index 0000000..a973fe9 --- /dev/null +++ b/src/test/java/com/google/inject/spi/InjectorSpiTest.java @@ -0,0 +1,74 @@ +package com.google.inject.spi; + +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; + +import junit.framework.TestCase; + +import java.util.Map; + +/** + * @author sberlin@gmail.com (Sam Berlin) + */ +public class InjectorSpiTest extends TestCase { + + public void testExistingBinding() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Foo.class); + bind(Baz.class); + } + }); + // Sanity check -- ensure we return the proper binding for all existing bindings. + for(Map.Entry, Binding> entry : injector.getAllBindings().entrySet()) { + assertSame(entry.getValue(), injector.getExistingBinding(entry.getKey())); + } + + // Now run through specifics... + Binding binding; + + // 1) non-Provider Foo.class + binding = injector.getExistingBinding(Key.get(Foo.class)); + assertNotNull(binding); + assertEquals(Foo.class, binding.getKey().getTypeLiteral().getRawType()); + + // 2) Provider class (should already exist, because Baz @Injects it). + // the assertTrue is a bit stricter than necessary, but makes sure this works for pre-existing Provider bindings + assertTrue(injector.getAllBindings().containsKey(Key.get(new TypeLiteral>() {}))); + binding = injector.getExistingBinding(Key.get(new TypeLiteral>() {})); + assertNotNull(binding); + assertEquals(Provider.class, binding.getKey().getTypeLiteral().getRawType()); + assertEquals(Foo.class, ((Provider)binding.getProvider().get()).get().getClass()); + + // 3) non-Provider Baz.class + binding = injector.getExistingBinding(Key.get(Baz.class)); + assertNotNull(binding); + assertEquals(Baz.class, binding.getKey().getTypeLiteral().getRawType()); + + // 4) Provider class (should not already exist, because nothing used it yet). + // the assertFalse is a bit stricter than necessary, but makes sure this works for non-pre-existing Provider bindings + assertFalse(injector.getAllBindings().containsKey(Key.get(new TypeLiteral>() {}))); + binding = injector.getExistingBinding(Key.get(new TypeLiteral>() {})); + assertNotNull(binding); + assertEquals(Provider.class, binding.getKey().getTypeLiteral().getRawType()); + assertEquals(Baz.class, ((Provider)binding.getProvider().get()).get().getClass()); + + // 5) non-Provider Bar, doesn't exist. + assertNull(injector.getExistingBinding(Key.get(Bar.class))); + + // 6) Provider Bar, doesn't exist. + assertNull(injector.getExistingBinding(Key.get(new TypeLiteral>() {}))); + } + + private static class Foo {} + private static class Bar {} + private static class Baz { @SuppressWarnings("unused") @Inject Provider fooP; } + +} diff --git a/src/test/java/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java b/src/test/java/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java new file mode 100644 index 0000000..6c797b1 --- /dev/null +++ b/src/test/java/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java @@ -0,0 +1,394 @@ +/** + * Copyright (C) 2015 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.spi; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.name.Names.named; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.CreationException; +import com.google.inject.Exposed; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.PrivateModule; +import com.google.inject.internal.util.StackTraceElements; +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Set; + +/** Tests for {@link ModuleAnnotatedMethodScanner} usage. */ +public class ModuleAnnotatedMethodScannerTest extends TestCase { + + public void testScanning() throws Exception { + Module module = new AbstractModule() { + @Override protected void configure() {} + + @TestProvides @Named("foo") String foo() { + return "foo"; + } + + @TestProvides @Named("foo2") String foo2() { + return "foo2"; + } + }; + Injector injector = Guice.createInjector(module, NamedMunger.module()); + + // assert no bindings named "foo" or "foo2" exist -- they were munged. + assertMungedBinding(injector, String.class, "foo", "foo"); + assertMungedBinding(injector, String.class, "foo2", "foo2"); + + Binding fooBinding = injector.getBinding(Key.get(String.class, named("foo-munged"))); + Binding foo2Binding = injector.getBinding(Key.get(String.class, named("foo2-munged"))); + // Validate the provider has a sane toString + assertEquals(methodName(TestProvides.class, "foo", module), + fooBinding.getProvider().toString()); + assertEquals(methodName(TestProvides.class, "foo2", module), + foo2Binding.getProvider().toString()); + } + + public void testSkipSources() throws Exception { + Module module = new AbstractModule() { + @Override protected void configure() { + binder().skipSources(getClass()).install(new AbstractModule() { + @Override protected void configure() {} + + @TestProvides @Named("foo") String foo() { return "foo"; } + }); + } + }; + Injector injector = Guice.createInjector(module, NamedMunger.module()); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testWithSource() throws Exception { + Module module = new AbstractModule() { + @Override protected void configure() { + binder().withSource("source").install(new AbstractModule() { + @Override protected void configure() {} + + @TestProvides @Named("foo") String foo() { return "foo"; } + }); + } + }; + Injector injector = Guice.createInjector(module, NamedMunger.module()); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testMoreThanOneClaimedAnnotationFails() throws Exception { + Module module = new AbstractModule() { + @Override protected void configure() {} + + @TestProvides @TestProvides2 String foo() { + return "foo"; + } + }; + try { + Guice.createInjector(module, NamedMunger.module()); + fail(); + } catch(CreationException expected) { + assertEquals(1, expected.getErrorMessages().size()); + assertContains(expected.getMessage(), + "More than one annotation claimed by NamedMunger on method " + + module.getClass().getName() + ".foo(). Methods can only have " + + "one annotation claimed per scanner."); + } + } + + private String methodName(Class annotation, String method, Object container) + throws Exception { + return "@" + annotation.getName() + " " + + StackTraceElements.forMember(container.getClass().getDeclaredMethod(method)); + } + + @Documented @Target(METHOD) @Retention(RUNTIME) + private @interface TestProvides {} + + @Documented @Target(METHOD) @Retention(RUNTIME) + private @interface TestProvides2 {} + + private static class NamedMunger extends ModuleAnnotatedMethodScanner { + static Module module() { + return new AbstractModule() { + @Override protected void configure() { + binder().scanModulesForAnnotatedMethods(new NamedMunger()); + } + }; + } + + @Override + public String toString() { + return "NamedMunger"; + } + + @Override + public Set> annotationClasses() { + return ImmutableSet.of(TestProvides.class, TestProvides2.class); + } + + @Override + public Key prepareMethod(Binder binder, Annotation annotation, Key key, + InjectionPoint injectionPoint) { + return Key.get(key.getTypeLiteral(), + Names.named(((Named) key.getAnnotation()).value() + "-munged")); + } + } + + private void assertMungedBinding(Injector injector, Class clazz, String originalName, + Object expectedValue) { + assertNull(injector.getExistingBinding(Key.get(clazz, named(originalName)))); + Binding fooBinding = injector.getBinding(Key.get(clazz, named(originalName + "-munged"))); + assertEquals(expectedValue, fooBinding.getProvider().get()); + } + + public void testFailingScanner() { + try { + Guice.createInjector(new SomeModule(), FailingScanner.module()); + fail(); + } catch (CreationException expected) { + Message m = Iterables.getOnlyElement(expected.getErrorMessages()); + assertEquals( + "An exception was caught and reported. Message: Failing in the scanner.", + m.getMessage()); + assertEquals(IllegalStateException.class, m.getCause().getClass()); + ElementSource source = (ElementSource) Iterables.getOnlyElement(m.getSources()); + assertEquals(SomeModule.class.getName(), + Iterables.getOnlyElement(source.getModuleClassNames())); + assertEquals(String.class.getName() + " " + SomeModule.class.getName() + ".aString()", + source.toString()); + } + } + + public static class FailingScanner extends ModuleAnnotatedMethodScanner { + static Module module() { + return new AbstractModule() { + @Override protected void configure() { + binder().scanModulesForAnnotatedMethods(new FailingScanner()); + } + }; + } + + @Override public Set> annotationClasses() { + return ImmutableSet.of(TestProvides.class); + } + + @Override public Key prepareMethod( + Binder binder, Annotation rawAnnotation, Key key, InjectionPoint injectionPoint) { + throw new IllegalStateException("Failing in the scanner."); + } + } + + static class SomeModule extends AbstractModule { + @TestProvides String aString() { + return "Foo"; + } + + @Override protected void configure() {} + } + + public void testChildInjectorInheritsScanner() { + Injector parent = Guice.createInjector(NamedMunger.module()); + Injector child = parent.createChildInjector(new AbstractModule() { + @Override protected void configure() {} + + @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + assertMungedBinding(child, String.class, "foo", "foo"); + } + + public void testChildInjectorScannersDontImpactSiblings() { + Module module = new AbstractModule() { + @Override + protected void configure() {} + + @TestProvides @Named("foo") String foo() { + return "foo"; + } + }; + Injector parent = Guice.createInjector(); + Injector child = parent.createChildInjector(NamedMunger.module(), module); + assertMungedBinding(child, String.class, "foo", "foo"); + + // no foo nor foo-munged in sibling, since scanner never saw it. + Injector sibling = parent.createChildInjector(module); + assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo")))); + assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo-munged")))); + } + + public void testPrivateModuleInheritScanner_usingPrivateModule() { + Injector injector = Guice.createInjector(NamedMunger.module(), new PrivateModule() { + @Override protected void configure() {} + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModule_skipSourcesWithinPrivateModule() { + Injector injector = Guice.createInjector(NamedMunger.module(), new PrivateModule() { + @Override protected void configure() { + binder().skipSources(getClass()).install(new AbstractModule() { + @Override protected void configure() {} + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModule_skipSourcesForPrivateModule() { + Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() { + @Override protected void configure() { + binder().skipSources(getClass()).install(new PrivateModule() { + @Override protected void configure() {} + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + }}); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModuleInheritScanner_usingPrivateBinder() { + Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() { + @Override protected void configure() { + binder().newPrivateBinder().install(new AbstractModule() { + @Override protected void configure() {} + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder() { + Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() { + @Override protected void configure() { + binder().newPrivateBinder().skipSources(getClass()).install(new AbstractModule() { + @Override protected void configure() {} + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder2() { + Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() { + @Override protected void configure() { + binder().skipSources(getClass()).newPrivateBinder().install(new AbstractModule() { + @Override protected void configure() {} + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModuleScannersDontImpactSiblings_usingPrivateModule() { + Injector injector = Guice.createInjector(new PrivateModule() { + @Override protected void configure() { + install(NamedMunger.module()); + } + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }, new PrivateModule() { + @Override protected void configure() {} + + // ignored! (because the scanner doesn't run over this module) + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModuleScannersDontImpactSiblings_usingPrivateBinder() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + binder().newPrivateBinder().install(new AbstractModule() { + @Override protected void configure() { + install(NamedMunger.module()); + } + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + } + }, new AbstractModule() { + @Override protected void configure() { + binder().newPrivateBinder().install(new AbstractModule() { + @Override protected void configure() {} + + // ignored! (because the scanner doesn't run over this module) + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + }}); + assertMungedBinding(injector, String.class, "foo", "foo"); + } + + public void testPrivateModuleWithinPrivateModule() { + Injector injector = Guice.createInjector(NamedMunger.module(), new PrivateModule() { + @Override protected void configure() { + expose(Key.get(String.class, named("foo-munged"))); + install(new PrivateModule() { + @Override protected void configure() {} + + @Exposed @TestProvides @Named("foo") String foo() { + return "foo"; + } + }); + } + }); + assertMungedBinding(injector, String.class, "foo", "foo"); + } +} diff --git a/src/test/java/com/google/inject/spi/ModuleRewriterTest.java b/src/test/java/com/google/inject/spi/ModuleRewriterTest.java new file mode 100644 index 0000000..06581da --- /dev/null +++ b/src/test/java/com/google/inject/spi/ModuleRewriterTest.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2008 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.spi; + +import com.google.common.collect.Lists; +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.ConfigurationException; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.name.Names; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ModuleRewriterTest extends TestCase { + + public void testRewriteBindings() { + // create a module the binds String.class and CharSequence.class + Module module = new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("Pizza"); + bind(CharSequence.class).toInstance("Wine"); + } + }; + + // record the elements from that module + List elements = Elements.getElements(module); + + // create a rewriter that rewrites the binding to 'Wine' with a binding to 'Beer' + List rewritten = Lists.newArrayList(); + for (Element element : elements) { + element = element.acceptVisitor(new DefaultElementVisitor() { + @Override public Element visit(Binding binding) { + T target = binding.acceptTargetVisitor(Elements.getInstanceVisitor()); + if ("Wine".equals(target)) { + return null; + } + else { + return binding; + } + } + }); + if (element != null) { + rewritten.add(element); + } + } + + // create a module from the original list of elements and the rewriter + Module rewrittenModule = Elements.getModule(rewritten); + + // the wine binding is dropped + Injector injector = Guice.createInjector(rewrittenModule); + try { + injector.getInstance(CharSequence.class); + fail(); + } catch (ConfigurationException expected) { + } + } + + public void testGetProviderAvailableAtInjectMembersTime() { + Module module = new AbstractModule() { + public void configure() { + final Provider stringProvider = getProvider(String.class); + + bind(String.class).annotatedWith(Names.named("2")).toProvider(new Provider() { + private String value; + + @Inject void initialize() { + value = stringProvider.get(); + } + + public String get() { + return value; + } + }); + + bind(String.class).toInstance("A"); + } + }; + + // the module works fine normally + Injector injector = Guice.createInjector(module); + assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("2")))); + + // and it should also work fine if we rewrite it + List elements = Elements.getElements(module); + Module replayed = Elements.getModule(elements); + Injector replayedInjector = Guice.createInjector(replayed); + assertEquals("A", replayedInjector.getInstance(Key.get(String.class, Names.named("2")))); + } +} diff --git a/src/test/java/com/google/inject/spi/ModuleSourceTest.java b/src/test/java/com/google/inject/spi/ModuleSourceTest.java new file mode 100644 index 0000000..b72d976 --- /dev/null +++ b/src/test/java/com/google/inject/spi/ModuleSourceTest.java @@ -0,0 +1,125 @@ +package com.google.inject.spi; + +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Module; + +import junit.framework.TestCase; + +/** + * Tests for {@link ModuleSource}. + */ +public class ModuleSourceTest extends TestCase { + + private static final StackTraceElement BINDER_INSTALL = + new StackTraceElement("com.google.inject.spi.Elements$RecordingBinder", "install", + "Unknown Source", 235 /* line number*/); + + public void testOneModule() { + ModuleSource moduleSource = createWithSizeOne(); + checkSizeOne(moduleSource); + } + + public void testTwoModules() { + ModuleSource moduleSource = createWithSizeTwo(); + checkSizeTwo(moduleSource); + moduleSource = moduleSource.getParent(); + checkSizeOne(moduleSource); + } + + public void testThreeModules() { + ModuleSource moduleSource = createWithSizeThree(); + checkSizeThree(moduleSource); + moduleSource = moduleSource.getParent(); + checkSizeTwo(moduleSource); + moduleSource = moduleSource.getParent(); + checkSizeOne(moduleSource); + } + + private void checkSizeOne(ModuleSource moduleSource) { + assertEquals(1, moduleSource.size()); + assertEquals(1, moduleSource.getStackTraceSize()); + // Check call stack + StackTraceElement[] callStack = moduleSource.getStackTrace(); + assertEquals(BINDER_INSTALL, callStack[0]); + } + + private void checkSizeTwo(ModuleSource moduleSource) { + assertEquals(2, moduleSource.size()); + assertEquals(3, moduleSource.getStackTraceSize()); + // Check call stack + StackTraceElement[] callStack = moduleSource.getStackTrace(); + assertEquals(BINDER_INSTALL, callStack[0]); + assertEquals( + new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$A", "configure", "Unknown Source", 100), + callStack[1]); + assertEquals(BINDER_INSTALL, callStack[2]); + } + + private void checkSizeThree(ModuleSource moduleSource) { + assertEquals(3, moduleSource.size()); + assertEquals(7, moduleSource.getStackTraceSize()); + // Check call stack + StackTraceElement[] callStack = moduleSource.getStackTrace(); + assertEquals(BINDER_INSTALL, callStack[0]); + assertEquals(new StackTraceElement("class1", "method1", "Unknown Source", 1), callStack[1]); + assertEquals(new StackTraceElement("class2", "method2", "Unknown Source", 2), callStack[2]); + assertEquals( + new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$B", "configure", "Unknown Source", 200), + callStack[3]); + assertEquals(BINDER_INSTALL, callStack[4]); + + assertEquals( + new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$A", "configure", "Unknown Source", 100), + callStack[5]); + assertEquals(BINDER_INSTALL, callStack[6]); + } + + private ModuleSource createWithSizeOne() { + StackTraceElement[] partialCallStack = new StackTraceElement[1]; + partialCallStack[0] = BINDER_INSTALL; + return new ModuleSource(new A(), partialCallStack); + } + + private ModuleSource createWithSizeTwo() { + ModuleSource moduleSource = createWithSizeOne(); + StackTraceElement[] partialCallStack = new StackTraceElement[2]; + partialCallStack[0] = BINDER_INSTALL; + partialCallStack[1] = new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$A", "configure", "moduleSourceTest.java", 100); + return moduleSource.createChild(new B(), partialCallStack); + } + + private ModuleSource createWithSizeThree() { + ModuleSource moduleSource = createWithSizeTwo(); + StackTraceElement[] partialCallStack = new StackTraceElement[4]; + partialCallStack[0] = BINDER_INSTALL; + partialCallStack[1] = new StackTraceElement("class1", "method1", "Class1.java", 1); + partialCallStack[2] = new StackTraceElement("class2", "method2", "Class2.java", 2); + partialCallStack[3] = new StackTraceElement( + "com.google.inject.spi.moduleSourceTest$B", "configure", "moduleSourceTest.java", 200); + return moduleSource.createChild(new C(), partialCallStack); + } + + private static class A extends AbstractModule { + @Override + public void configure() { + install(new B()); + } + } + + private static class B implements Module { + @Override + public void configure(Binder binder) { + binder.install(new C()); + } + } + + private static class C extends AbstractModule { + @Override + public void configure() {} + } +} diff --git a/src/test/java/com/google/inject/spi/ProviderMethodsTest.java b/src/test/java/com/google/inject/spi/ProviderMethodsTest.java new file mode 100644 index 0000000..1265990 --- /dev/null +++ b/src/test/java/com/google/inject/spi/ProviderMethodsTest.java @@ -0,0 +1,905 @@ +/** + * Copyright (C) 2007 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.spi; + +import static com.google.inject.Asserts.assertContains; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.BindingAnnotation; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provides; +import com.google.inject.ProvisionException; +import com.google.inject.Singleton; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.Errors; +import com.google.inject.internal.InternalFlags; +import com.google.inject.internal.ProviderMethod; +import com.google.inject.internal.ProviderMethodsModule; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.util.Providers; +import com.google.inject.util.Types; + +import junit.framework.TestCase; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public class ProviderMethodsTest extends TestCase implements Module { + + @SuppressWarnings("unchecked") + public void testProviderMethods() { + Injector injector = Guice.createInjector(this); + + Bob bob = injector.getInstance(Bob.class); + assertEquals("A Bob", bob.getName()); + + Bob clone = injector.getInstance(Bob.class); + assertEquals("A Bob", clone.getName()); + + assertNotSame(bob, clone); + assertSame(bob.getDaughter(), clone.getDaughter()); + + Key soleBobKey = Key.get(Bob.class, Sole.class); + assertSame( + injector.getInstance(soleBobKey), + injector.getInstance(soleBobKey) + ); + } + + public void configure(Binder binder) {} + + interface Bob { + String getName(); + Dagny getDaughter(); + } + + interface Dagny { + int getAge(); + } + + @Provides + Bob provideBob(final Dagny dagny) { + return new Bob() { + public String getName() { + return "A Bob"; + } + + public Dagny getDaughter() { + return dagny; + } + }; + } + + @Provides + @Singleton + @Sole + Bob provideSoleBob(final Dagny dagny) { + return new Bob() { + public String getName() { + return "Only Bob"; + } + + public Dagny getDaughter() { + return dagny; + } + }; + } + + @Provides + @Singleton + Dagny provideDagny() { + return new Dagny() { + public int getAge() { + return 1; + } + }; + } + + @Retention(RUNTIME) + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @BindingAnnotation + @interface Sole {} + + + +// We'll have to make getProvider() support circular dependencies before this +// will work. +// +// public void testCircularDependency() { +// Injector injector = Guice.createInjector(new Module() { +// public void configure(Binder binder) { +// binder.install(ProviderMethods.from(ProviderMethodsTest.this)); +// } +// }); +// +// Foo foo = injector.getInstance(Foo.class); +// assertEquals(5, foo.getI()); +// assertEquals(10, foo.getBar().getI()); +// assertEquals(5, foo.getBar().getFoo().getI()); +// } +// +// interface Foo { +// Bar getBar(); +// int getI(); +// } +// +// interface Bar { +// Foo getFoo(); +// int getI(); +// } +// +// @Provides Foo newFoo(final Bar bar) { +// return new Foo() { +// +// public Bar getBar() { +// return bar; +// } +// +// public int getI() { +// return 5; +// } +// }; +// } +// +// @Provides Bar newBar(final Foo foo) { +// return new Bar() { +// +// public Foo getFoo() { +// return foo; +// } +// +// public int getI() { +// return 10; +// } +// }; +// } + + + public void testMultipleBindingAnnotations() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() {} + + @Provides @Named("A") @Blue + public String provideString() { + return "a"; + } + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "more than one annotation annotated with @BindingAnnotation:", + "Named", + "Blue", + "at " + getClass().getName(), + ".provideString("); + } + + } + + @Retention(RUNTIME) + @BindingAnnotation @interface Blue {} + + public void testGenericProviderMethods() { + Injector injector = Guice.createInjector( + new ProvideTs("A", "B") {}, new ProvideTs(1, 2) {}); + + assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First")))); + assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second")))); + assertEquals(ImmutableSet.of("A", "B"), + injector.getInstance(Key.get(Types.setOf(String.class)))); + + assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("First"))).intValue()); + assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("Second"))).intValue()); + assertEquals(ImmutableSet.of(1, 2), + injector.getInstance(Key.get(Types.setOf(Integer.class)))); + } + + abstract class ProvideTs extends AbstractModule { + final T first; + final T second; + + protected ProvideTs(T first, T second) { + this.first = first; + this.second = second; + } + + @Override protected void configure() {} + + @Named("First") @Provides T provideFirst() { + return first; + } + + @Named("Second") @Provides T provideSecond() { + return second; + } + + @Provides Set provideBoth(@Named("First") T first, @Named("Second") T second) { + return ImmutableSet.of(first, second); + } + } + + public void testAutomaticProviderMethods() { + Injector injector = Guice.createInjector((Module) new AbstractModule() { + @Override protected void configure() { } + private int next = 1; + + @Provides @Named("count") + public Integer provideCount() { + return next++; + } + }); + + assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); + assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); + assertEquals(3, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); + } + + /** + * If the user installs provider methods for the module manually, that shouldn't cause a double + * binding of the provider methods' types. + */ + public void testAutomaticProviderMethodsDoNotCauseDoubleBinding() { + Module installsSelf = new AbstractModule() { + @Override protected void configure() { + install(this); + bind(Integer.class).toInstance(5); + } + @Provides public String provideString(Integer count) { + return "A" + count; + } + }; + + Injector injector = Guice.createInjector(installsSelf); + assertEquals("A5", injector.getInstance(String.class)); + } + + public void testWildcardProviderMethods() { + final List strings = ImmutableList.of("A", "B", "C"); + final List numbers = ImmutableList.of(1, 2, 3); + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override protected void configure() { + @SuppressWarnings("unchecked") + Key> listOfSupertypesOfInteger = (Key>) + Key.get(Types.listOf(Types.supertypeOf(Integer.class))); + bind(listOfSupertypesOfInteger).toInstance(numbers); + } + @Provides public List provideCharSequences() { + return strings; + } + @Provides public Class provideType() { + return Float.class; + } + }); + + assertSame(strings, injector.getInstance(HasWildcardInjection.class).charSequences); + assertSame(numbers, injector.getInstance(HasWildcardInjection.class).numbers); + assertSame(Float.class, injector.getInstance(HasWildcardInjection.class).type); + } + + static class HasWildcardInjection { + @Inject List charSequences; + @Inject List numbers; + @Inject Class type; + } + + public void testProviderMethodDependenciesAreExposed() throws Exception { + Module module = new AbstractModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(50); + bindConstant().annotatedWith(Names.named("units")).to("Kg"); + } + @Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) { + return count + units; + } + }; + Injector injector = Guice.createInjector(module); + + ProviderInstanceBinding binding = (ProviderInstanceBinding) injector.getBinding( + Key.get(String.class, Names.named("weight"))); + Method method = + module.getClass().getDeclaredMethod("provideWeight", Integer.class, String.class); + InjectionPoint point = new InjectionPoint(TypeLiteral.get(module.getClass()), method, false); + assertEquals(ImmutableSet.>of( + new Dependency(point, Key.get(Integer.class), false, 0), + new Dependency(point, Key.get(String.class, Names.named("units")), false, 1)), + binding.getDependencies()); + } + + public void testNonModuleProviderMethods() { + final Object methodsObject = new Object() { + @Provides @Named("foo") String provideFoo() { + return "foo-value"; + } + }; + + Module module = new AbstractModule() { + @Override protected void configure() { + install(ProviderMethodsModule.forObject(methodsObject)); + } + }; + + Injector injector = Guice.createInjector(module); + + Key key = Key.get(String.class, Names.named("foo")); + assertEquals("foo-value", injector.getInstance(key)); + + // Test the provider method object itself. This makes sure getInstance works, since GIN uses it + List elements = Elements.getElements(module); + assertEquals(1, elements.size()); + + Element element = elements.get(0); + assertTrue(element + " instanceof ProviderInstanceBinding", + element instanceof ProviderInstanceBinding); + + ProviderInstanceBinding binding = (ProviderInstanceBinding) element; + javax.inject.Provider provider = binding.getUserSuppliedProvider(); + assertTrue(provider instanceof ProviderMethod); + assertEquals(methodsObject, ((ProviderMethod) provider).getInstance()); + } + + public void testVoidProviderMethods() { + try { + Guice.createInjector(new AbstractModule() { + @Override protected void configure() {} + + @Provides void provideFoo() {} + }); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "1) Provider methods must return a value. Do not return void.", + getClass().getName(), + ".provideFoo("); + } + } + + /*public void testInjectsJustOneLogger() { + AtomicReference loggerRef = new AtomicReference(); + Injector injector = Guice.createInjector(new FooModule(loggerRef)); + + assertNull(loggerRef.get()); + injector.getInstance(Integer.class); + Logger lastLogger = loggerRef.getAndSet(null); + assertNotNull(lastLogger); + injector.getInstance(Integer.class); + assertSame(lastLogger, loggerRef.get()); + + assertEquals(FooModule.class.getName(), lastLogger.getName()); + } + + private static class FooModule extends AbstractModule { + private final AtomicReference loggerRef; + + public FooModule(AtomicReference loggerRef) { + this.loggerRef = loggerRef; + } + + @Override protected void configure() {} + + @SuppressWarnings("unused") + @Provides Integer foo(Logger logger) { + loggerRef.set(logger); + return 42; + } + }*/ + + public void testSpi() throws Exception { + Module m1 = new AbstractModule() { + @Override protected void configure() {} + @Provides @Named("foo") String provideFoo(Integer dep) { return "foo"; } + }; + Module m2 = new AbstractModule() { + @Override protected void configure() {} + @Provides Integer provideInt(@Named("foo") String dep) { return 42; } + }; + Injector injector = Guice.createInjector(m1, m2); + + Binding stringBinding = + injector.getBinding(Key.get(String.class, Names.named("foo"))); + ProvidesMethodBinding stringMethod = + stringBinding.acceptTargetVisitor(new BindingCapturer()); + assertEquals(m1, stringMethod.getEnclosingInstance()); + assertEquals(m1.getClass().getDeclaredMethod("provideFoo", Integer.class), + stringMethod.getMethod()); + assertEquals(((HasDependencies) stringBinding).getDependencies(), + stringMethod.getDependencies()); + assertEquals(Key.get(String.class, Names.named("foo")), stringMethod.getKey()); + + Binding intBinding = injector.getBinding(Integer.class); + ProvidesMethodBinding intMethod = + intBinding.acceptTargetVisitor(new BindingCapturer()); + assertEquals(m2, intMethod.getEnclosingInstance()); + assertEquals(m2.getClass().getDeclaredMethod("provideInt", String.class), + intMethod.getMethod()); + assertEquals(((HasDependencies) intBinding).getDependencies(), + intMethod.getDependencies()); + assertEquals(Key.get(Integer.class), intMethod.getKey()); + + } + + private static class BindingCapturer extends DefaultBindingTargetVisitor> + implements ProvidesMethodTargetVisitor> { + + @SuppressWarnings("unchecked") + public ProvidesMethodBinding visit( + ProvidesMethodBinding providesMethodBinding) { + return (ProvidesMethodBinding)providesMethodBinding; + } + + @Override protected ProvidesMethodBinding visitOther(Binding binding) { + throw new IllegalStateException("unexpected visit of: " + binding); + } + } + + public void testProvidesMethodVisibility() { + Injector injector = Guice.createInjector(new VisibilityModule()); + + assertEquals(42, injector.getInstance(Integer.class).intValue()); + assertEquals(42L, injector.getInstance(Long.class).longValue()); + assertEquals(42D, injector.getInstance(Double.class).doubleValue()); + assertEquals(42F, injector.getInstance(Float.class).floatValue()); + } + + private static class VisibilityModule extends AbstractModule { + @Override protected void configure() {} + + @SuppressWarnings("unused") + @Provides Integer foo() { + return 42; + } + + @SuppressWarnings("unused") + @Provides private Long bar() { + return 42L; + } + + @SuppressWarnings("unused") + @Provides protected Double baz() { + return 42D; + } + + @SuppressWarnings("unused") + @Provides public Float quux() { + return 42F; + } + } + + public void testProvidesMethodInheritenceHierarchy() { + try { + Guice.createInjector(new Sub1Module(), new Sub2Module()); + fail("Expected injector creation failure"); + } catch (CreationException expected) { + // both of our super class bindings cause errors + assertContains(expected.getMessage(), + "A binding to java.lang.Long was already configured", + "A binding to java.lang.Integer was already configured"); + } + } + + public void testProvidesMethodsDefinedInSuperClass() { + Injector injector = Guice.createInjector(new Sub1Module()); + assertEquals(42, injector.getInstance(Integer.class).intValue()); + assertEquals(42L, injector.getInstance(Long.class).longValue()); + assertEquals(42D, injector.getInstance(Double.class).doubleValue()); + } + + private static class BaseModule extends AbstractModule { + @Override protected void configure() {} + + @Provides Integer foo() { + return 42; + } + + @Provides Long bar() { + return 42L; + } + } + + private static class Sub1Module extends BaseModule { + @Provides Double baz() { + return 42D; + } + } + + private static class Sub2Module extends BaseModule { + @Provides Float quux() { + return 42F; + } + } + + static class SuperClassModule extends AbstractModule { + @Override protected void configure() {} + @Provides Number providerMethod() { + return 1D; + } + @Provides @Named("rawlist") List rawProvider(@Named("list") List f) { + return f; + } + + @Provides @Named("unrawlist") List rawParameterProvider(@Named("rawlist") List f) { + return f; + } + + @Provides @Named("list") List annotatedGenericProviderMethod() { + return new ArrayList(); + } + @Provides @Named("collection") Collection annotatedGenericParameterProviderMethod( + @Named("list") List foo) { + return foo; + } + @Provides private String privateProviderMethod() { + return "hello"; + } + } + + public void testOverrideProviderMethod_overrideHasProvides() { + class SubClassModule extends SuperClassModule { + @Override @Provides Number providerMethod() { + return 2D; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()", + "overridden by: " + SubClassModule.class.getName() + ".providerMethod()"); + } + } + + public void testOverrideProviderMethod_overrideHasProvides_withNewAnnotation() { + class SubClassModule extends SuperClassModule { + @Override @Provides @Named("foo") Number providerMethod() { + return 2D; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()", + "overridden by: " + SubClassModule.class.getName() + ".providerMethod()"); + } + } + + public void testOverrideProviderMethod_overrideDoesntHaveProvides() { + class SubClassModule extends SuperClassModule { + @Override Number providerMethod() { + return 2D; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()", + "overridden by: " + SubClassModule.class.getName() + ".providerMethod()"); + } + } + public void testOverrideProviderMethod_overrideDoesntHaveProvides_withNewAnnotation() { + class SubClassModule extends SuperClassModule { + @Override @Named("foo") Number providerMethod() { + return 2D; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()", + "overridden by: " + SubClassModule.class.getName() + ".providerMethod()"); + } + } + + + public void testOverrideProviderMethod_covariantOverrideDoesntHaveProvides() { + class SubClassModule extends SuperClassModule { + @Override Double providerMethod() { + return 2D; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()", + "overridden by: " + SubClassModule.class.getName() + ".providerMethod()"); + } + } + + public void testOverrideProviderMethod_covariantOverrideHasProvides() { + class SubClassModule extends SuperClassModule { + @Override @Provides Double providerMethod() { + return 2D; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()", + "overridden by: " + SubClassModule.class.getName() + ".providerMethod()"); + } + } + + public void testOverrideProviderMethod_fakeOverridePrivateMethod() { + class SubClassModule extends SuperClassModule { + // not actually an override, just looks like it + String privateProviderMethod() { + return "sub"; + } + } + assertEquals("hello", Guice.createInjector(new SubClassModule()).getInstance(String.class)); + } + + public void testOverrideProviderMethod_subclassRawTypes_returnType() { + class SubClassModule extends SuperClassModule { + @Override List annotatedGenericProviderMethod() { + return super.annotatedGenericProviderMethod(); + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + + ".annotatedGenericProviderMethod()", + "overridden by: " + SubClassModule.class.getName() + ".annotatedGenericProviderMethod()"); + } + } + + public void testOverrideProviderMethod_subclassRawTypes_parameterType() { + class SubClassModule extends SuperClassModule { + @Override Collection annotatedGenericParameterProviderMethod(List foo) { + return super.annotatedGenericParameterProviderMethod(foo); + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + + ".annotatedGenericParameterProviderMethod()", + "overridden by: " + SubClassModule.class.getName() + + ".annotatedGenericParameterProviderMethod()"); + } + } + + public void testOverrideProviderMethod_superclassRawTypes_returnType() { + class SubClassModule extends SuperClassModule { + // remove the rawtype from the override + @Override List rawProvider(List f) { + return f; + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + SuperClassModule.class.getName() + ".rawProvider()", + "overridden by: " + SubClassModule.class.getName() + ".rawProvider()"); + } + } + + abstract static class GenericSuperModule extends AbstractModule { + @Provides String provide(T thing) { + return thing.toString(); + } + } + + // This is a tricky case where signatures don't match, but it is an override (facilitated via a + // bridge method) + public void testOverrideProviderMethod_erasureBasedOverrides() { + class SubClassModule extends GenericSuperModule { + @Override String provide(Integer thing) { + return thing.toString(); + } + + @Override protected void configure() { + bind(Integer.class).toInstance(3); + } + } + try { + Guice.createInjector(new SubClassModule()); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), + "Overriding @Provides methods is not allowed.", + "@Provides method: " + GenericSuperModule.class.getName() + ".provide()", + "overridden by: " + SubClassModule.class.getName() + ".provide()"); + } + } + + class RestrictedSuper extends AbstractModule { + @Provides public String provideFoo() { return "foo"; } + @Override protected void configure() {} + } + + public class ExposedSub extends RestrictedSuper {} + + public void testOverrideProviderMethod_increasedVisibility() { + // ensure we don't detect the synthetic provideFoo method in ExposedSub as an override (it is, + // but since it is synthetic it would be annoying to throw an error on it). + assertEquals("foo", Guice.createInjector(new ExposedSub()).getInstance(String.class)); + } + + interface ProviderInterface { + T getT(); + } + + static class ModuleImpl extends AbstractModule implements ProviderInterface { + @Override protected void configure() {} + @Provides public String getT() { + return "string"; + } + @Provides public Object getObject() { + return new Object(); + } + /* javac will synthesize a bridge method for getT with the types erased, equivalent to: + * @Provides public Object getT() { ... } + */ + } + + public void testIgnoreSyntheticBridgeMethods() { + Guice.createInjector(new ModuleImpl()); + } + + public void testNullability() throws Exception { + Module module = new AbstractModule() { + @Override + protected void configure() { + bind(String.class).toProvider(Providers.of(null)); + } + + @SuppressWarnings("unused") + @Provides + Integer fail(String foo) { + return 1; + } + + @SuppressWarnings("unused") + @Provides + Long succeed(@Nullable String foo) { + return 2L; + } + }; + Injector injector = Guice.createInjector(module); + InjectionPoint fooPoint = InjectionPoint.forMethod( + module.getClass().getDeclaredMethod("fail", String.class), + TypeLiteral.get(module.getClass())); + Dependency fooDependency = Iterables.getOnlyElement(fooPoint.getDependencies()); + + runNullableTest(injector, fooDependency, module); + + injector.getInstance(Long.class); + } + + private void runNullableTest(Injector injector, Dependency dependency, Module module) { + switch (InternalFlags.getNullableProvidesOption()) { + case ERROR: + validateNullableFails(injector, module); + break; + case IGNORE: + validateNullableIgnored(injector); + break; + case WARN: + validateNullableWarns(injector, dependency); + break; + } + } + + private void validateNullableFails(Injector injector, Module module) { + try { + injector.getInstance(Integer.class); + fail(); + } catch (ProvisionException expected) { + assertContains(expected.getMessage(), + "1) null returned by binding at " + module.getClass().getName() + ".configure(", + "but parameter 0 of " + module.getClass().getName() + ".fail() is not @Nullable", + "while locating java.lang.String", + "for parameter 0 at " + module.getClass().getName() + ".fail(", + "while locating java.lang.Integer"); + + assertEquals(1, expected.getErrorMessages().size()); + } + } + + private void validateNullableIgnored(Injector injector) { + injector.getInstance(Integer.class); // no exception + } + + private void validateNullableWarns(Injector injector, Dependency dependency) { + /*final List logRecords = Lists.newArrayList(); + final Handler fakeHandler = new Handler() { + @Override + public void publish(LogRecord logRecord) { + logRecords.add(logRecord); + } + @Override + public void flush() {} + @Override + public void close() throws SecurityException {} + }; + //Logger.getLogger(Guice.class.getName()).addHandler(fakeHandler); + try { + injector.getInstance(Integer.class); // no exception, but assert it does log. + LogRecord record = Iterables.getOnlyElement(logRecords); + assertEquals( + "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable." + + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" + + " error.", + record.getMessage()); + assertEquals(dependency.getParameterIndex(), record.getParameters()[0]); + assertEquals(Errors.convert(dependency.getInjectionPoint().getMember()), + record.getParameters()[1]); + assertEquals(Errors.convert(dependency.getKey()), record.getParameters()[2]); + } finally { + //Logger.getLogger(Guice.class.getName()).removeHandler(fakeHandler); + } + */ + } + + @Retention(RetentionPolicy.RUNTIME) + @interface Nullable {} +} diff --git a/src/test/java/com/google/inject/spi/SpiBindingsTest.java b/src/test/java/com/google/inject/spi/SpiBindingsTest.java new file mode 100644 index 0000000..82e969e --- /dev/null +++ b/src/test/java/com/google/inject/spi/SpiBindingsTest.java @@ -0,0 +1,463 @@ +/** + * Copyright (C) 2008 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.spi; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.getDeclaringSourcePart; +import static com.google.inject.Asserts.isIncludeStackTraceComplete; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.Stage; +import com.google.inject.name.Names; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import java.lang.reflect.Constructor; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class SpiBindingsTest extends TestCase { + + public void testBindConstant() { + checkInjector( + new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(Names.named("one")).to(1); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof InstanceBinding); + assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey()); + return null; + } + } + ); + } + + public void testToInstanceBinding() { + checkInjector( + new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("A"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof InstanceBinding); + checkBindingSource(binding); + assertEquals(Key.get(String.class), binding.getKey()); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(InstanceBinding binding) { + assertEquals("A", binding.getInstance()); + return null; + } + }); + binding.acceptScopingVisitor(new FailingBindingScopingVisitor() { + public Void visitEagerSingleton() { + return null; + } + }); + return null; + } + } + ); + } + + public void testToProviderBinding() { + final Provider stringProvider = new StringProvider(); + + checkInjector( + new AbstractModule() { + protected void configure() { + bind(String.class).toProvider(stringProvider); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof ProviderInstanceBinding); + checkBindingSource(binding); + assertEquals(Key.get(String.class), binding.getKey()); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit( + ProviderInstanceBinding binding) { + assertSame(stringProvider, binding.getUserSuppliedProvider()); + return null; + } + }); + return null; + } + } + ); + } + + public void testToProviderKeyBinding() { + checkInjector( + new AbstractModule() { + protected void configure() { + bind(String.class).toProvider(StringProvider.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof ProviderKeyBinding); + checkBindingSource(binding); + assertEquals(Key.get(String.class), binding.getKey()); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ProviderKeyBinding binding) { + assertEquals(Key.get(StringProvider.class), binding.getProviderKey()); + return null; + } + }); + return null; + } + } + ); + } + + public void testToKeyBinding() { + final Key aKey = Key.get(String.class, Names.named("a")); + final Key bKey = Key.get(String.class, Names.named("b")); + + checkInjector( + new AbstractModule() { + protected void configure() { + bind(aKey).to(bKey); + bind(bKey).toInstance("B"); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof LinkedKeyBinding); + checkBindingSource(binding); + assertEquals(aKey, binding.getKey()); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(LinkedKeyBinding binding) { + assertEquals(bKey, binding.getLinkedKey()); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertEquals(bKey, binding.getKey()); + return null; + } + } + ); + } + + public void testToConstructorBinding() { + checkInjector( + new AbstractModule() { + protected void configure() { + bind(D.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof ConstructorBinding); + checkBindingSource(binding); + assertEquals(Key.get(D.class), binding.getKey()); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(ConstructorBinding binding) { + Constructor expected = D.class.getDeclaredConstructors()[0]; + assertEquals(expected, binding.getConstructor().getMember()); + assertEquals(ImmutableSet.of(), binding.getInjectableMembers()); + return null; + } + }); + return null; + } + } + ); + } + + public void testConstantBinding() { + checkInjector( + new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(Names.named("one")).to(1); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding binding) { + assertTrue(binding instanceof InstanceBinding); + checkBindingSource(binding); + assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey()); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit(InstanceBinding binding) { + assertEquals(1, binding.getInstance()); + return null; + } + }); + return null; + } + } + ); + } + + public void testConvertedConstantBinding() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bindConstant().annotatedWith(Names.named("one")).to("1"); + } + }); + + Binding binding = injector.getBinding(Key.get(Integer.class, Names.named("one"))); + assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey()); + checkBindingSource(binding); + assertTrue(binding instanceof ConvertedConstantBinding); + binding.acceptTargetVisitor(new FailingTargetVisitor() { + @Override public Void visit( + ConvertedConstantBinding binding) { + assertEquals((Integer) 1, binding.getValue()); + assertEquals(Key.get(String.class, Names.named("one")), binding.getSourceKey()); + return null; + } + }); + } + + public void testProviderBinding() { + Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toInstance("A"); + } + }); + + Key> providerOfStringKey = new Key>() {}; + Binding> binding = injector.getBinding(providerOfStringKey); + assertEquals(providerOfStringKey, binding.getKey()); + checkBindingSource(binding); + assertTrue(binding instanceof ProviderBinding); + binding.acceptTargetVisitor(new FailingTargetVisitor>() { + @Override public Void visit( + ProviderBinding> binding) { + assertEquals(Key.get(String.class), binding.getProvidedKey()); + return null; + } + }); + } + + public void testScopes() { + checkInjector( + new AbstractModule() { + protected void configure() { + bind(String.class).annotatedWith(Names.named("a")) + .toProvider(StringProvider.class).in(Singleton.class); + bind(String.class).annotatedWith(Names.named("b")) + .toProvider(StringProvider.class).in(Scopes.SINGLETON); + bind(String.class).annotatedWith(Names.named("c")) + .toProvider(StringProvider.class).asEagerSingleton(); + bind(String.class).annotatedWith(Names.named("d")) + .toProvider(StringProvider.class); + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class, Names.named("a")), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitScope(Scope scope) { + // even though we bound with an annotation, the injector always uses instances + assertSame(Scopes.SINGLETON, scope); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class, Names.named("b")), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitScope(Scope scope) { + assertSame(Scopes.SINGLETON, scope); + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class, Names.named("c")), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitEagerSingleton() { + return null; + } + }); + return null; + } + }, + + new FailingElementVisitor() { + @Override public Void visit(Binding command) { + assertEquals(Key.get(String.class, Names.named("d")), command.getKey()); + command.acceptScopingVisitor(new FailingBindingScopingVisitor() { + @Override public Void visitNoScoping() { + return null; + } + }); + return null; + } + } + ); + } + + public void testExtensionSpi() { + final AtomicBoolean visiting = new AtomicBoolean(false); + + final Injector injector = Guice.createInjector(new AbstractModule() { + protected void configure() { + bind(String.class).toProvider(new ProviderWithExtensionVisitor() { + public V acceptExtensionVisitor(BindingTargetVisitor visitor, + ProviderInstanceBinding binding) { + assertSame(this, binding.getUserSuppliedProvider()); + // We can't always check for FailingSpiTargetVisitor, + // because constructing the injector visits here, and we need + // to process the binding as normal + if(visiting.get()) { + assertTrue("visitor: " + visitor, visitor instanceof FailingSpiTargetVisitor); + return (V)"visited"; + } else { + return visitor.visit(binding); + } + } + + public String get() { + return "FooBar"; + } + }); + } + }); + + visiting.set(true); + + // Check for Provider binding -- that is still a ProviderBinding. + Key> providerOfStringKey = new Key>() {}; + Binding> providerBinding = injector.getBinding(providerOfStringKey); + assertEquals(providerOfStringKey, providerBinding.getKey()); + checkBindingSource(providerBinding); + assertTrue("binding: " + providerBinding, providerBinding instanceof ProviderBinding); + providerBinding.acceptTargetVisitor(new FailingTargetVisitor>() { + @Override public Void visit(ProviderBinding> binding) { + assertEquals(Key.get(String.class), binding.getProvidedKey()); + return null; + } + }); + + // Check for String binding -- that one is ProviderInstanceBinding, and gets hooked + Binding binding = injector.getBinding(String.class); + assertEquals(Key.get(String.class), binding.getKey()); + checkBindingSource(binding); + assertTrue(binding instanceof ProviderInstanceBinding); + assertEquals("visited", binding.acceptTargetVisitor(new FailingSpiTargetVisitor())); + } + + private static class FailingSpiTargetVisitor extends DefaultBindingTargetVisitor { + @Override + protected String visitOther(Binding binding) { + throw new AssertionFailedError(); + } + } + + public void checkBindingSource(Binding binding) { + assertContains(binding.getSource().toString(), getDeclaringSourcePart(getClass())); + ElementSource source = (ElementSource) binding.getSource(); + assertFalse(source.getModuleClassNames().isEmpty()); + if (isIncludeStackTraceComplete()) { + assertTrue(source.getStackTrace().length > 0); + } else { + assertEquals(0, source.getStackTrace().length); + } + } + + public void checkInjector(Module module, ElementVisitor... visitors) { + Injector injector = Guice.createInjector(module); + + List> bindings = Lists.newArrayList(injector.getBindings().values()); + for (Iterator> i = bindings.iterator(); i.hasNext(); ) { + if (BUILT_IN_BINDINGS.contains(i.next().getKey())) { + i.remove(); + } + } + + Collections.sort(bindings, orderByKey); + + assertEquals(bindings.size(), visitors.length); + + for (int i = 0; i < visitors.length; i++) { + ElementVisitor visitor = visitors[i]; + Binding binding = bindings.get(i); + binding.acceptVisitor(visitor); + } + } + + private final ImmutableSet> BUILT_IN_BINDINGS = ImmutableSet.of( + Key.get(Injector.class), Key.get(Stage.class), Key.get(Logger.class)); + + private final Comparator> orderByKey = new Comparator>() { + public int compare(Binding a, Binding b) { + return a.getKey().toString().compareTo(b.getKey().toString()); + } + }; + + private static class StringProvider implements Provider { + public String get() { + return "A"; + } + } + + private static class C { } + + private static class D extends C { + @Inject public D(Injector unused) { } + } +} diff --git a/src/test/java/com/google/inject/spi/ToolStageInjectorTest.java b/src/test/java/com/google/inject/spi/ToolStageInjectorTest.java new file mode 100644 index 0000000..7f63f1c --- /dev/null +++ b/src/test/java/com/google/inject/spi/ToolStageInjectorTest.java @@ -0,0 +1,165 @@ +package com.google.inject.spi; + +import com.google.inject.AbstractModule; +import com.google.inject.Asserts; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.Stage; +import com.google.inject.spi.Toolable; + +import junit.framework.TestCase; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ToolStageInjectorTest extends TestCase { + + @Override + protected void setUp() throws Exception { + Foo.s = null; + Foo.sm = null; + } + + public void testToolStageInjectorRestrictions() { + Injector injector = Guice.createInjector(Stage.TOOL); + try { + injector.injectMembers(new Object()); + fail("Non-SPI Injector methods must throw an exception in the TOOL stage."); + } catch (UnsupportedOperationException expected) { + } + + try { + injector.getInstance(Injector.class); + fail("Non-SPI Injector methods must throw an exception in the TOOL stage."); + } catch (UnsupportedOperationException expected) { + } + + try { + injector.getInstance(Key.get(Injector.class)); + fail("Non-SPI Injector methods must throw an exception in the TOOL stage."); + } catch (UnsupportedOperationException expected) { + } + + try { + injector.getProvider(Injector.class); + fail("Non-SPI Injector methods must throw an exception in the TOOL stage."); + } catch (UnsupportedOperationException expected) { + } + + try { + injector.getProvider(Key.get(Injector.class)); + fail("Non-SPI Injector methods must throw an exception in the TOOL stage."); + } catch (UnsupportedOperationException expected) { + } + } + + public void testToolStageDoesntInjectInstances() { + final Foo foo = new Foo(); + Guice.createInjector(Stage.TOOL, new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(Foo.class); + requestInjection(foo); + } + }); + assertNull(Foo.s); + assertNull(Foo.sm); + assertNull(foo.f); + assertNull(foo.m); + } + + public void testToolStageDoesntInjectProviders() { + final Foo foo = new Foo(); + Guice.createInjector(Stage.TOOL, new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(Foo.class); + bind(Object.class).toProvider(foo); + } + }); + assertNull(Foo.s); + assertNull(Foo.sm); + assertNull(foo.f); + assertNull(foo.m); + } + + public void testToolStageWarnsOfMissingObjectGraph() { + final Bar bar = new Bar(); + try { + Guice.createInjector(Stage.TOOL, new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(Bar.class); + requestInjection(bar); + } + }); + fail("expected exception"); + } catch(CreationException expected) { + Asserts.assertContains(expected.toString(), "No implementation for java.util.Collection was bound.", + "No implementation for java.util.Map was bound.", + "No implementation for java.util.List was bound.", + "No implementation for java.util.Set was bound."); + } + } + + public void testToolStageInjectsTooledMethods() { + final Tooled tooled = new Tooled(); + Guice.createInjector(Stage.TOOL, new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(Tooled.class); + bind(Object.class).toProvider(tooled); + } + }); + assertNull(Tooled.s); + assertNotNull(Tooled.sm); + assertNull(tooled.f); + assertNotNull(tooled.m); + } + + @SuppressWarnings("unchecked") + private static class Bar { + @SuppressWarnings("unused") @Inject private static List list; + @SuppressWarnings("unused") @Inject private Set set; + @SuppressWarnings("unused") @Inject void method(Collection c) {} + @SuppressWarnings("unused") @Inject static void staticMethod(Map map) {} + } + + private static class Foo implements Provider { + @Inject private static S s; + @Inject private F f; + private M m; + @SuppressWarnings("unused") @Inject void method(M m) { this.m = m; } + private static SM sm; + @SuppressWarnings("unused") @Inject static void staticMethod(SM sm) { Tooled.sm = sm; } + + public Object get() { + return null; + } + } + + private static class Tooled implements Provider { + @Inject private static S s; + @Inject private F f; + private M m; + @Toolable @SuppressWarnings("unused") @Inject void method(M m) { this.m = m; } + private static SM sm; + @Toolable @SuppressWarnings("unused") @Inject static void staticMethod(SM sm) { Tooled.sm = sm; } + + public Object get() { + return null; + } + } + + private static class S {} + private static class F {} + private static class M {} + private static class SM {} + +} diff --git a/src/test/java/com/google/inject/util/NoopOverrideTest.java b/src/test/java/com/google/inject/util/NoopOverrideTest.java new file mode 100644 index 0000000..9f65bf4 --- /dev/null +++ b/src/test/java/com/google/inject/util/NoopOverrideTest.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2008 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.util; + +import com.google.inject.Module; +import com.google.inject.spi.ElementVisitor; +import com.google.inject.spi.ElementsTest; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class NoopOverrideTest extends ElementsTest { + + protected void checkModule(Module module, ElementVisitor... visitors) { + Module overridden = Modules.override(module).with(Modules.EMPTY_MODULE); + super.checkModule(overridden, visitors); + } +} \ No newline at end of file diff --git a/src/test/java/com/google/inject/util/OverrideModuleTest.java b/src/test/java/com/google/inject/util/OverrideModuleTest.java new file mode 100644 index 0000000..16d7a2c --- /dev/null +++ b/src/test/java/com/google/inject/util/OverrideModuleTest.java @@ -0,0 +1,731 @@ +/** + * Copyright (C) 2008 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.util; + +import static com.google.inject.Asserts.asModuleChain; +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Guice.createInjector; +import static com.google.inject.name.Names.named; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.CreationException; +import com.google.inject.Exposed; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.PrivateModule; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.Scope; +import com.google.inject.ScopeAnnotation; +import com.google.inject.Stage; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Date; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author sberlin@gmail.com (Sam Berlin) + */ +public class OverrideModuleTest extends TestCase { + + private static final Key key2 = Key.get(String.class, named("2")); + private static final Key key3 = Key.get(String.class, named("3")); + + private static final Module EMPTY_MODULE = new Module() { + public void configure(Binder binder) {} + }; + + public void testOverride() { + Injector injector = createInjector(Modules.override(newModule("A")).with(newModule("B"))); + assertEquals("B", injector.getInstance(String.class)); + } + + public void testOverrideMultiple() { + Module module = Modules.override(newModule("A"), newModule(1), newModule(0.5f)) + .with(newModule("B"), newModule(2), newModule(1.5d)); + Injector injector = createInjector(module); + assertEquals("B", injector.getInstance(String.class)); + assertEquals(2, injector.getInstance(Integer.class).intValue()); + assertEquals(0.5f, injector.getInstance(Float.class)); + assertEquals(1.5d, injector.getInstance(Double.class)); + } + + public void testOverrideUnmatchedTolerated() { + Injector injector = createInjector(Modules.override(EMPTY_MODULE).with(newModule("B"))); + assertEquals("B", injector.getInstance(String.class)); + } + + public void testOverrideConstant() { + Module original = new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(named("Test")).to("A"); + } + }; + + Module replacements = new AbstractModule() { + @Override protected void configure() { + bindConstant().annotatedWith(named("Test")).to("B"); + } + }; + + Injector injector = createInjector(Modules.override(original).with(replacements)); + assertEquals("B", injector.getInstance(Key.get(String.class, named("Test")))); + } + + public void testGetProviderInModule() { + Module original = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("A"); + bind(key2).toProvider(getProvider(String.class)); + } + }; + + Injector injector = createInjector(Modules.override(original).with(EMPTY_MODULE)); + assertEquals("A", injector.getInstance(String.class)); + assertEquals("A", injector.getInstance(key2)); + } + + public void testOverrideWhatGetProviderProvided() { + Module original = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("A"); + bind(key2).toProvider(getProvider(String.class)); + } + }; + + Module replacements = newModule("B"); + + Injector injector = createInjector(Modules.override(original).with(replacements)); + assertEquals("B", injector.getInstance(String.class)); + assertEquals("B", injector.getInstance(key2)); + } + + public void testOverrideUsingOriginalsGetProvider() { + Module original = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("A"); + bind(key2).toInstance("B"); + } + }; + + Module replacements = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toProvider(getProvider(key2)); + } + }; + + Injector injector = createInjector(Modules.override(original).with(replacements)); + assertEquals("B", injector.getInstance(String.class)); + assertEquals("B", injector.getInstance(key2)); + } + + public void testOverrideOfOverride() { + Module original = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("A1"); + bind(key2).toInstance("A2"); + bind(key3).toInstance("A3"); + } + }; + + Module replacements1 = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("B1"); + bind(key2).toInstance("B2"); + } + }; + + Module overrides = Modules.override(original).with(replacements1); + + Module replacements2 = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("C1"); + bind(key3).toInstance("C3"); + } + }; + + Injector injector = createInjector(Modules.override(overrides).with(replacements2)); + assertEquals("C1", injector.getInstance(String.class)); + assertEquals("B2", injector.getInstance(key2)); + assertEquals("C3", injector.getInstance(key3)); + } + + static class OuterReplacementsModule extends AbstractModule { + @Override protected void configure() { + install(new InnerReplacementsModule()); + } + } + static class InnerReplacementsModule extends AbstractModule { + @Override protected void configure() { + bind(String.class).toInstance("B"); + bind(String.class).toInstance("C"); + } + } + public void testOverridesTwiceFails() { + Module original = newModule("A"); + Module replacements = new OuterReplacementsModule(); + Module module = Modules.override(original).with(replacements); + try { + createInjector(module); + fail(); + } catch (CreationException expected) { + assertContains(expected.getMessage(), + "A binding to java.lang.String was already configured at " + + InnerReplacementsModule.class.getName(), + asModuleChain(Modules.OverrideModule.class, + OuterReplacementsModule.class, InnerReplacementsModule.class), + "at " + InnerReplacementsModule.class.getName(), + asModuleChain(Modules.OverrideModule.class, + OuterReplacementsModule.class, InnerReplacementsModule.class)); + } + } + + public void testOverridesDoesntFixTwiceBoundInOriginal() { + Module original = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("A"); + bind(String.class).toInstance("B"); + } + }; + + Module replacements = new AbstractModule() { + @Override protected void configure() { + bind(String.class).toInstance("C"); + } + }; + + Module module = Modules.override(original).with(replacements); + try { + createInjector(module); + fail(); + } catch (CreationException expected) { + // The replacement comes first because we replace A with C, + // then we encounter B and freak out. + assertContains(expected.getMessage(), + "1) A binding to java.lang.String was already configured at " + + replacements.getClass().getName(), + asModuleChain(Modules.OverrideModule.class, replacements.getClass()), + "at " + original.getClass().getName(), + asModuleChain(Modules.OverrideModule.class, original.getClass())); + } + } + + public void testStandardScopeAnnotation() { + final SingleUseScope scope = new SingleUseScope(); + + Module module = new AbstractModule() { + @Override protected void configure() { + bindScope(TestScopeAnnotation.class, scope); + bind(String.class).in(TestScopeAnnotation.class); + } + }; + assertFalse(scope.used); + + Guice.createInjector(module); + assertTrue(scope.used); + } + + public void testOverrideUntargettedBinding() { + Module original = new AbstractModule() { + @Override protected void configure() { + bind(Date.class); + } + }; + + Module replacements = new AbstractModule() { + @Override protected void configure() { + bind(Date.class).toInstance(new Date(0)); + } + }; + + Injector injector = createInjector(Modules.override(original).with(replacements)); + assertEquals(0, injector.getInstance(Date.class).getTime()); + } + + public void testOverrideScopeAnnotation() { + final Scope scope = new Scope() { + public Provider scope(Key key, Provider unscoped) { + throw new AssertionError("Should not be called"); + } + }; + + final SingleUseScope replacementScope = new SingleUseScope(); + + Module original = new AbstractModule() { + @Override protected void configure() { + bindScope(TestScopeAnnotation.class, scope); + bind(Date.class).in(TestScopeAnnotation.class); + } + }; + + Module replacements = new AbstractModule() { + @Override protected void configure() { + bindScope(TestScopeAnnotation.class, replacementScope); + } + }; + + Injector injector = createInjector(Modules.override(original).with(replacements)); + injector.getInstance(Date.class); + assertTrue(replacementScope.used); + } + + public void testFailsIfOverridenScopeInstanceHasBeenUsed() { + final Scope scope = new Scope() { + public Provider scope(Key key, Provider unscoped) { + return unscoped; + } + + @Override public String toString() { + return "ORIGINAL SCOPE"; + } + }; + + final Module original = new AbstractModule() { + @Override protected void configure() { + bindScope(TestScopeAnnotation.class, scope); + bind(Date.class).in(scope); + bind(String.class).in(scope); + } + }; + Module originalWrapper = new AbstractModule() { + @Override protected void configure() { + install(original); + } + }; + + Module replacements = new AbstractModule() { + @Override protected void configure() { + bindScope(TestScopeAnnotation.class, new SingleUseScope()); + } + }; + + try { + createInjector(Modules.override(originalWrapper).with(replacements)); + fail("Exception expected"); + } catch (CreationException e) { + assertContains(e.getMessage(), + "1) The scope for @TestScopeAnnotation is bound directly and cannot be overridden.", + "original binding at " + original.getClass().getName() + ".configure(", + asModuleChain(originalWrapper.getClass(), original.getClass()), + "bound directly at " + original.getClass().getName() + ".configure(", + asModuleChain(originalWrapper.getClass(), original.getClass()), + "bound directly at " + original.getClass().getName() + ".configure(", + asModuleChain(originalWrapper.getClass(), original.getClass()), + "at ", replacements.getClass().getName() + ".configure(", + asModuleChain(Modules.OverrideModule.class, replacements.getClass())); + } + } + + public void testOverrideIsLazy() { + final AtomicReference value = new AtomicReference("A"); + Module overridden = Modules.override(new AbstractModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(named("original")).toInstance(value.get()); + } + }).with(new AbstractModule() { + @Override protected void configure() { + bind(String.class).annotatedWith(named("override")).toInstance(value.get()); + } + }); + + // the value.get() call should be deferred until Guice.createInjector + value.set("B"); + Injector injector = Guice.createInjector(overridden); + assertEquals("B", injector.getInstance(Key.get(String.class, named("original")))); + assertEquals("B", injector.getInstance(Key.get(String.class, named("override")))); + } + + public void testOverridePrivateModuleOverPrivateModule() { + Module exposes5and6 = new AbstractModule() { + @Override protected void configure() { + install(new PrivateModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(5); + expose(Integer.class); + + bind(Character.class).toInstance('E'); + } + }); + + install(new PrivateModule() { + @Override protected void configure() { + bind(Long.class).toInstance(6L); + expose(Long.class); + + bind(Character.class).toInstance('F'); + } + }); + } + }; + + AbstractModule exposes15 = new AbstractModule() { + @Override protected void configure() { + install(new PrivateModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(15); + expose(Integer.class); + + bind(Character.class).toInstance('G'); + } + }); + + install(new PrivateModule() { + @Override protected void configure() { + bind(Character.class).toInstance('H'); + } + }); + } + }; + + // override forwards + Injector injector = Guice.createInjector(Modules.override(exposes5and6).with(exposes15)); + assertEquals(15, injector.getInstance(Integer.class).intValue()); + assertEquals(6L, injector.getInstance(Long.class).longValue()); + + // and in reverse order + Injector reverse = Guice.createInjector(Modules.override(exposes15).with(exposes5and6)); + assertEquals(5, reverse.getInstance(Integer.class).intValue()); + assertEquals(6L, reverse.getInstance(Long.class).longValue()); + } + + public void testOverrideModuleAndPrivateModule() { + Module exposes5 = new PrivateModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(5); + expose(Integer.class); + } + }; + + Module binds15 = new AbstractModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(15); + } + }; + + Injector injector = Guice.createInjector(Modules.override(exposes5).with(binds15)); + assertEquals(15, injector.getInstance(Integer.class).intValue()); + + Injector reverse = Guice.createInjector(Modules.override(binds15).with(exposes5)); + assertEquals(5, reverse.getInstance(Integer.class).intValue()); + } + + public void testOverrideDeepExpose() { + final AtomicReference> charAProvider + = new AtomicReference>(); + + Module exposes5 = new PrivateModule() { + @Override protected void configure() { + install(new PrivateModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(5); + expose(Integer.class); + charAProvider.set(getProvider(Character.class)); + bind(Character.class).toInstance('A'); + } + }); + expose(Integer.class); + } + }; + + Injector injector = Guice.createInjector(Modules.override(exposes5).with(EMPTY_MODULE)); + assertEquals(5, injector.getInstance(Integer.class).intValue()); + assertEquals('A', charAProvider.getAndSet(null).get().charValue()); + + injector = Guice.createInjector(Modules.override(EMPTY_MODULE).with(exposes5)); + assertEquals(5, injector.getInstance(Integer.class).intValue()); + assertEquals('A', charAProvider.getAndSet(null).get().charValue()); + + final AtomicReference> charBProvider + = new AtomicReference>(); + + Module binds15 = new AbstractModule() { + @Override protected void configure() { + bind(Integer.class).toInstance(15); + + install(new PrivateModule() { + @Override protected void configure() { + charBProvider.set(getProvider(Character.class)); + bind(Character.class).toInstance('B'); + } + }); + } + }; + + injector = Guice.createInjector(Modules.override(binds15).with(exposes5)); + assertEquals(5, injector.getInstance(Integer.class).intValue()); + assertEquals('A', charAProvider.getAndSet(null).get().charValue()); + assertEquals('B', charBProvider.getAndSet(null).get().charValue()); + + injector = Guice.createInjector(Modules.override(exposes5).with(binds15)); + assertEquals(15, injector.getInstance(Integer.class).intValue()); + assertEquals('A', charAProvider.getAndSet(null).get().charValue()); + assertEquals('B', charBProvider.getAndSet(null).get().charValue()); + } + + @Retention(RUNTIME) + @Target(TYPE) + @ScopeAnnotation + private static @interface TestScopeAnnotation {} + + private static class SingleUseScope implements Scope { + boolean used = false; + public Provider scope(Key key, Provider unscoped) { + assertFalse(used); + used = true; + return unscoped; + } + } + + static class NewModule extends AbstractModule { + private final T bound; + NewModule(T bound) { + this.bound = bound; + } + @Override protected void configure() { + @SuppressWarnings("unchecked") + Class type = (Class)bound.getClass(); + bind(type).toInstance(bound); + } + } + + private static Module newModule(final T bound) { + return new NewModule(bound); + } + + private static final String RESULT = "RESULT"; + private static final String PRIVATE_INPUT = "PRIVATE_INPUT"; + private static final String OVERRIDDEN_INPUT = "FOO"; + private static final String OVERRIDDEN_RESULT = "Size: 3"; + private static final Key RESULT_KEY = Key.get(String.class, named(RESULT)); + private static final Key INPUT_KEY = Key.get(String.class, named(PRIVATE_INPUT)); + + public void testExposedBindingOverride() throws Exception { + Injector inj = Guice.createInjector( + Modules.override(new ExampleModule()).with( + new AbstractModule() { + @Override protected void configure() { + bind(RESULT_KEY).toInstance(OVERRIDDEN_RESULT); + } + })); + assertEquals(inj.getInstance(RESULT_KEY), OVERRIDDEN_RESULT); + } + + public void testPrivateBindingOverride() throws Exception { + Injector inj = Guice.createInjector( + Modules.override(new ExampleModule()).with( + new AbstractModule() { + @Override protected void configure() { + bind(INPUT_KEY).toInstance(OVERRIDDEN_INPUT); + } + })); + assertEquals(inj.getInstance(RESULT_KEY), OVERRIDDEN_RESULT); + } + + public static class ExampleModule extends PrivateModule { + @Provides @Exposed @Named(RESULT) + public String provideResult(@Named(PRIVATE_INPUT) String input) { + return "Size: " + input.length(); + } + + @Provides @Named(PRIVATE_INPUT) + public String provideInput() { + return "Hello World"; + } + + @Override protected void configure() { + } + } + + public void testEqualsNotCalledByDefaultOnInstance() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnEquals = true; + Guice.createInjector(Modules.override(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(HashEqualsTester.class).toInstance(a); + } + }).with()); + } + + public void testEqualsNotCalledByDefaultOnProvider() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnEquals = true; + Guice.createInjector(Modules.override(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(Object.class).toProvider(a); + } + }).with()); + } + + public void testHashcodeNeverCalledOnInstance() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnHashcode = true; + a.equality = "test"; + + final HashEqualsTester b = new HashEqualsTester(); + b.throwOnHashcode = true; + b.equality = "test"; + Guice.createInjector(Modules.override(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(HashEqualsTester.class).toInstance(a); + bind(HashEqualsTester.class).toInstance(b); + } + }).with()); + } + + public void testHashcodeNeverCalledOnProviderInstance() { + final HashEqualsTester a = new HashEqualsTester(); + a.throwOnHashcode = true; + a.equality = "test"; + + final HashEqualsTester b = new HashEqualsTester(); + b.throwOnHashcode = true; + b.equality = "test"; + Guice.createInjector(Modules.override(new AbstractModule() { + @Override + protected void configure() { + bind(String.class); + bind(Object.class).toProvider(a); + bind(Object.class).toProvider(b); + } + }).with()); + } + + private static class HashEqualsTester implements Provider { + private String equality; + private boolean throwOnEquals; + private boolean throwOnHashcode; + + @Override + public boolean equals(Object obj) { + if (throwOnEquals) { + throw new RuntimeException(); + } else if (obj instanceof HashEqualsTester) { + HashEqualsTester o = (HashEqualsTester)obj; + if(o.throwOnEquals) { + throw new RuntimeException(); + } + if(equality == null && o.equality == null) { + return this == o; + } else { + return Objects.equal(equality, o.equality); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + if(throwOnHashcode) { + throw new RuntimeException(); + } else { + return super.hashCode(); + } + } + + public Object get() { + return new Object(); + } + } + + public void testCorrectStage() { + final Stage stage = Stage.PRODUCTION; + Module module = Modules.override(new AbstractModule() { + @Override + protected void configure() { + if (currentStage() != Stage.PRODUCTION) { + addError("Wronge stage in overridden module:" + currentStage()); + } + } + }).with(new AbstractModule() { + @Override + protected void configure() { + if (currentStage() != Stage.PRODUCTION) { + addError("Wronge stage in overriding module:" + currentStage()); + } + } + }); + Guice.createInjector(stage, module); + } + + public void testOverridesApplyOriginalScanners() { + Injector injector = + Guice.createInjector(Modules.override(NamedMunger.module()).with(new AbstractModule() { + @Override protected void configure() {} + @TestProvides @Named("test") String provideString() { return "foo"; } + })); + + assertNull(injector.getExistingBinding(Key.get(String.class, named("test")))); + Binding binding = injector.getBinding(Key.get(String.class, named("test-munged"))); + assertEquals("foo", binding.getProvider().get()); + } + + @Documented @Target(METHOD) @Retention(RUNTIME) + private @interface TestProvides {} + + private static class NamedMunger extends ModuleAnnotatedMethodScanner { + static Module module() { + return new AbstractModule() { + @Override protected void configure() { + binder().scanModulesForAnnotatedMethods(new NamedMunger()); + } + }; + } + + @Override + public String toString() { + return "NamedMunger"; + } + + @Override + public Set> annotationClasses() { + return ImmutableSet.of(TestProvides.class); + } + + @Override + public Key prepareMethod(Binder binder, Annotation annotation, Key key, + InjectionPoint injectionPoint) { + return Key.get(key.getTypeLiteral(), + Names.named(((Named) key.getAnnotation()).value() + "-munged")); + } + } +} diff --git a/src/test/java/com/google/inject/util/ProvidersTest.java b/src/test/java/com/google/inject/util/ProvidersTest.java new file mode 100644 index 0000000..6891e57 --- /dev/null +++ b/src/test/java/com/google/inject/util/ProvidersTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 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.util; + +import com.google.common.base.Objects; +import com.google.common.testing.EqualsTester; +import com.google.inject.Provider; + +import junit.framework.TestCase; + +import javax.inject.Inject; + +/** + * Unit tests for {@link Providers}. + * + * @author Kevin Bourrillion (kevinb9n@gmail.com) + */ +public class ProvidersTest extends TestCase { + + public void testOfInstance() { + String foo = "foo"; + Provider p = Providers.of(foo); + assertSame(foo, p.get()); + assertSame(foo, p.get()); + } + + public void testOfNull() { + Provider p = Providers.of(null); + assertNull(p.get()); + } + + public void testOfEquality() { + new EqualsTester() + .addEqualityGroup( + Providers.of(null), + Providers.of(null)) + .addEqualityGroup( + Providers.of("Hello"), + Providers.of("Hello")) + .testEquals(); + } + + private static class JavaxProvider implements javax.inject.Provider { + private final int value; + + public JavaxProvider(int value) { + this.value = value; + } + + public Integer get() { + return value; + } + + @Override public int hashCode() { + return Objects.hashCode(value); + } + + @Override public boolean equals(Object obj) { + return (obj instanceof JavaxProvider) && (value == ((JavaxProvider) obj).value); + } + } + + private static class JavaxProviderWithDependencies implements javax.inject.Provider { + private int value; + + @Inject void setValue(int value) { + this.value = value; + } + + public Integer get() { + return value; + } + + @Override public int hashCode() { + return 42; + } + + @Override public boolean equals(Object obj) { + return (obj instanceof JavaxProviderWithDependencies); + } + } +} diff --git a/src/test/java/com/google/inject/util/TypesTest.java b/src/test/java/com/google/inject/util/TypesTest.java new file mode 100644 index 0000000..db015de --- /dev/null +++ b/src/test/java/com/google/inject/util/TypesTest.java @@ -0,0 +1,215 @@ +/** + * Copyright (C) 2008 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.util; + +import static com.google.inject.Asserts.assertContains; +import static com.google.inject.Asserts.assertEqualWhenReserialized; +import static com.google.inject.Asserts.assertEqualsBothWays; +import static com.google.inject.util.Types.subtypeOf; +import static com.google.inject.util.Types.supertypeOf; + +import com.google.inject.TypeLiteral; +import com.google.inject.internal.MoreTypes; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class TypesTest extends TestCase { + + // generic types for comparison + Map a; + Inner b; + List[][]> c; + List d; + Set e; + Outer.Inner f; + + private ParameterizedType mapStringInteger; + private ParameterizedType innerFloatDouble; + private ParameterizedType listSetStringArray; + private ParameterizedType listString; + private ParameterizedType setString; + private ParameterizedType outerInner; + private GenericArrayType setStringArray; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mapStringInteger = (ParameterizedType) getClass().getDeclaredField("a").getGenericType(); + innerFloatDouble = (ParameterizedType) getClass().getDeclaredField("b").getGenericType(); + listSetStringArray = (ParameterizedType) getClass().getDeclaredField("c").getGenericType(); + listString = (ParameterizedType) getClass().getDeclaredField("d").getGenericType(); + setString = (ParameterizedType) getClass().getDeclaredField("e").getGenericType(); + outerInner = (ParameterizedType) getClass().getDeclaredField("f").getGenericType(); + setStringArray = (GenericArrayType) listSetStringArray.getActualTypeArguments()[0]; + } + + public void testListSetMap() { + assertEqualsBothWays(mapStringInteger, Types.mapOf(String.class, Integer.class)); + assertEqualsBothWays(listString, Types.listOf(String.class)); + assertEqualsBothWays(setString, Types.setOf(String.class)); + } + + public void testDefensiveCopies() { + Type[] arguments = new Type[] { String.class, Integer.class }; + ParameterizedType parameterizedType = Types.newParameterizedType(Map.class, arguments); + arguments[0] = null; + assertEquals(String.class, parameterizedType.getActualTypeArguments()[0]); + parameterizedType.getActualTypeArguments()[1] = null; + assertEquals(Integer.class, parameterizedType.getActualTypeArguments()[1]); + } + + public void testTypeWithOwnerType() { + ParameterizedType actual = Types.newParameterizedTypeWithOwner( + TypesTest.class, Inner.class, Float.class, Double.class); + assertEquals(TypesTest.class, actual.getOwnerType()); + assertEqualsBothWays(innerFloatDouble, actual); + // The JDK prints this out as: + // com.google.inject.util.TypesTest.com.google.inject.util.TypesTest$Inner + // and we think that's wrong, so the assertEquals comparison is worthless. :-( +// assertEquals(innerFloatDouble.toString(), actual.toString()); + + // We think the correct comparison is: + assertEquals("com.google.inject.util.TypesTest$Inner", actual.toString()); + } + + public void testTypeParametersMustNotBePrimitives() { + try { + Types.newParameterizedType(Map.class, String.class, int.class); + fail(); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), + "Primitive types are not allowed in type parameters: int"); + } + } + + public List wildcardExtends; + public List wildcardSuper; + public List wildcardObject; + + public void testWildcardTypes() throws NoSuchFieldException, IOException { + assertEqualsBothWays(getWildcard("wildcardSuper"), supertypeOf(CharSequence.class)); + assertEqualsBothWays(getWildcard("wildcardExtends"), subtypeOf(CharSequence.class)); + assertEqualsBothWays(getWildcard("wildcardObject"), subtypeOf(Object.class)); + + assertEquals("? super java.lang.CharSequence", supertypeOf(CharSequence.class).toString()); + assertEquals("? extends java.lang.CharSequence", subtypeOf(CharSequence.class).toString()); + assertEquals("?", subtypeOf(Object.class).toString()); + + assertEqualWhenReserialized(supertypeOf(CharSequence.class)); + assertEqualWhenReserialized(subtypeOf(CharSequence.class)); + } + + public void testWildcardBoundsMustNotBePrimitives() { + try { + supertypeOf(int.class); + fail(); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), + "Primitive types are not allowed in wildcard bounds: int"); + } + + try { + subtypeOf(int.class); + fail(); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), + "Primitive types are not allowed in wildcard bounds: int"); + } + } + + private WildcardType getWildcard(String fieldName) throws NoSuchFieldException { + ParameterizedType type = (ParameterizedType) getClass().getField(fieldName).getGenericType(); + return (WildcardType) type.getActualTypeArguments()[0]; + } + + public void testEqualsAndHashcode() { + ParameterizedType parameterizedType + = Types.newParameterizedType(Map.class, String.class, Integer.class); + assertEqualsBothWays(mapStringInteger, parameterizedType); + assertEquals(mapStringInteger.toString(), parameterizedType.toString()); + + GenericArrayType genericArrayType = Types.arrayOf(Types.arrayOf( + Types.newParameterizedType(Set.class, String.class))); + assertEqualsBothWays(setStringArray, genericArrayType); + assertEquals(setStringArray.toString(), genericArrayType.toString()); + } + + public void testToString() { + Assert.assertEquals("java.lang.String", MoreTypes.typeToString(String.class)); + assertEquals("java.util.Set[][]", MoreTypes.typeToString(setStringArray)); + assertEquals("java.util.Map", + MoreTypes.typeToString(mapStringInteger)); + assertEquals("java.util.List[][]>", + MoreTypes.typeToString(listSetStringArray)); + assertEquals(innerFloatDouble.toString(), + MoreTypes.typeToString(innerFloatDouble)); + } + + static class Owning {} + + /** + * Ensure that owning types are required when necessary, and forbidden + * otherwise. + */ + public void testCanonicalizeRequiresOwnerTypes() { + try { + Types.newParameterizedType(Owning.class, String.class); + fail(); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), + "No owner type for enclosed " + Owning.class); + } + + try { + Types.newParameterizedTypeWithOwner(Object.class, Set.class, String.class); + } catch (IllegalArgumentException expected) { + assertContains(expected.getMessage(), + "Owner type for unenclosed " + Set.class); + } + } + + @SuppressWarnings("UnusedDeclaration") + class Inner {} + + public void testInnerParameterizedEvenWithZeroArgs() { + TypeLiteral.Inner> type = new TypeLiteral.Inner>() {}; + assertEqualsBothWays(outerInner, type.getType()); + + ParameterizedType parameterizedType = (ParameterizedType) type.getType(); + assertEquals(0, parameterizedType.getActualTypeArguments().length); + assertEquals(new TypeLiteral>() {}.getType(), parameterizedType.getOwnerType()); + assertEquals(Outer.Inner.class, parameterizedType.getRawType()); + } + + static class Outer { + class Inner {} + } +}