├── winter ├── .gitignore ├── gradle.properties ├── src │ ├── test │ │ ├── resources │ │ │ └── mockito-extensions │ │ │ │ └── org.mockito.plugins.MockMaker │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ ├── evaluator │ │ │ ├── DirectServiceEvaluatorTest.kt │ │ │ ├── CyclicDependenciesCheckingDirectServiceEvaluatorTest.kt │ │ │ ├── BoundTestService.kt │ │ │ ├── LifecycleServiceEvaluatorTest.kt │ │ │ ├── CreateServiceEvaluatorTest.kt │ │ │ ├── AbstractCyclicServiceEvaluatorTest.kt │ │ │ └── AbstractServiceEvaluatorTest.kt │ │ │ ├── adapter │ │ │ └── ApplicationGraphOnlyInjectionAdapterTest.kt │ │ │ ├── assertions.kt │ │ │ └── testClasses.kt │ └── main │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ ├── inject │ │ ├── Prototype.kt │ │ ├── EagerSingleton.kt │ │ ├── MembersInjector.kt │ │ ├── ApplicationScope.kt │ │ ├── FactoryType.kt │ │ ├── Factory.kt │ │ └── InjectConstructor.kt │ │ ├── Scope.kt │ │ ├── evaluator │ │ ├── ServiceEvaluator.kt │ │ ├── DirectServiceEvaluator.kt │ │ ├── createServiceEvaluator.kt │ │ ├── CyclicDependenciesCheckingDirectServiceEvaluator.kt │ │ └── checkForCyclicDependencies.kt │ │ ├── plugin │ │ ├── SimplePlugin.kt │ │ ├── Plugins.kt │ │ └── Plugin.kt │ │ ├── adapter │ │ └── ApplicationGraphOnlyInjectionAdapter.kt │ │ ├── Winter.kt │ │ ├── delegate │ │ └── DelegateNotifier.kt │ │ ├── WinterException.kt │ │ ├── TypeKey.kt │ │ └── Types.kt ├── dokka-packages.md └── build.gradle ├── winter-java ├── .gitignore ├── gradle.properties ├── src │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── java │ │ │ ├── di.kt │ │ │ └── JWinterTest.java │ └── main │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── java │ │ └── JWinter.kt └── build.gradle ├── winter-androidx ├── .gitignore ├── gradle.properties ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── androidx │ │ │ ├── inject │ │ │ ├── ActivityScope.kt │ │ │ └── PresentationScope.kt │ │ │ ├── viewmodel │ │ │ ├── WinterViewModelFactory.kt │ │ │ ├── WinterViewModel.kt │ │ │ └── ComponentBuilderExt.kt │ │ │ ├── LifecycleAutoClose.kt │ │ │ ├── DependencyGraphContextWrapper.kt │ │ │ └── AndroidPresentationScopeInjectionAdapter.kt │ └── test │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── androidx │ │ └── LifecycleAutoCloseTest.kt ├── proguard-consumer-rules.pro └── build.gradle ├── winter-compiler ├── .gitignore ├── gradle.properties ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── javax.annotation.processing.Processor │ │ └── java │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── compiler │ │ │ ├── date.kt │ │ │ ├── SourceWriter.kt │ │ │ ├── Logger.kt │ │ │ ├── ProcessorConfiguration.kt │ │ │ ├── model │ │ │ └── InjectorModel.kt │ │ │ ├── KotlinMetadataExt.kt │ │ │ ├── DI.kt │ │ │ ├── generator │ │ │ └── ComponentGenerator.kt │ │ │ ├── TypeUtils.kt │ │ │ └── WinterProcessor.kt │ └── test │ │ ├── java │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── compiler │ │ │ ├── CustomScope.kt │ │ │ ├── ComponentTest.kt │ │ │ ├── TypeMappingTest.kt │ │ │ ├── BaseProcessorTest.kt │ │ │ ├── MembersInjectorTest.kt │ │ │ └── InjectConstructorAnnotationTest.kt │ │ └── resources │ │ ├── WithInjectedFieldExtended.java │ │ ├── WithInjectedField.java │ │ ├── OneArgumentInjectConstructor.java │ │ ├── EagerSingletonAnnotation.java │ │ ├── NoArgumentInjectConstructor.java │ │ ├── InjectConstructorAnnotationWithType.java │ │ ├── PrivateNoArgumentInjectConstructor.java │ │ ├── InjectConstructorAnnotation.java │ │ ├── WithCustomScope.java │ │ ├── NamedSingletonInjectConstructor.java │ │ ├── WithInjectedGenericFields.java │ │ ├── FactoryTypeAnnotation.java │ │ ├── PrototypeAnnotation.java │ │ ├── OneNamedArgumentInjectConstructor.java │ │ ├── InjectConstructorAnnotationWithInjectConstructor.java │ │ ├── FactoryTypeAnnotationAndTypeInInjectConstructorAnnotation.java │ │ ├── PrototypeAndEagerSingletonAnnotation.java │ │ ├── InjectConstructorAnnotationWithTwoConstructors.java │ │ ├── WithInjectedField_WinterMembersInjector.kt │ │ ├── InjectConstructorWithProviderAndLazyArguments.java │ │ ├── WithInjectedProviderAndLazyFields.java │ │ ├── WithInjectedFieldExtended_WinterMembersInjector.kt │ │ ├── WithInjectedGenericFields_WinterMembersInjector.kt │ │ ├── FactoryTypeAnnotation_WinterFactory.kt │ │ ├── EagerSingletonAnnotation_WinterFactory.kt │ │ ├── InjectConstructorAnnotationWithType_WinterFactory.kt │ │ ├── generatedComponent.kt │ │ ├── InjectConstructorAnnotation_WinterFactory.kt │ │ ├── OneArgumentInjectConstructor_WinterFactory.kt │ │ ├── WithInjectedField_WinterFactory.kt │ │ ├── PrototypeAnnotation_WinterFactory.kt │ │ ├── OneNamedArgumentInjectConstructor_WinterFactory.kt │ │ ├── NoArgumentInjectConstructor_WinterFactory.kt │ │ ├── NamedSingletonInjectConstructor_WinterFactory.kt │ │ ├── WithInjectedProviderAndLazyFields_WinterMembersInjector.kt │ │ └── InjectConstructorWithProviderAndLazyArguments_WinterFactory.kt └── build.gradle ├── winter-junit4 ├── .gitignore ├── gradle.properties ├── src │ ├── test │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── junit4 │ │ │ └── WinterRuleTest.kt │ └── main │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── junit4 │ │ └── WinterRule.kt └── build.gradle ├── winter-junit5 ├── .gitignore ├── gradle.properties ├── src │ ├── test │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── junit5 │ │ │ ├── Mock.kt │ │ │ ├── WinterAllExtensionStaticTest.kt │ │ │ ├── WinterAllExtensionTest.kt │ │ │ └── WinterEachExtensionTest.kt │ └── main │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── junit5 │ │ ├── WInject.kt │ │ ├── WinterAllExtension.kt │ │ ├── WinterEachExtension.kt │ │ └── AbstractWinterExtension.kt └── build.gradle ├── winter-rxjava2 ├── .gitignore ├── gradle.properties ├── src │ ├── main │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── rxjava2 │ │ │ └── WinterDisposablePlugin.kt │ └── test │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── rxjava2 │ │ └── WinterDisposablePluginTest.kt └── build.gradle ├── winter-testing ├── .gitignore ├── gradle.properties ├── src │ ├── test │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── testing │ │ │ ├── Mock.kt │ │ │ ├── ComponentBuilderExtTest.kt │ │ │ ├── PropertyServiceTest.kt │ │ │ └── GraphExtTest.kt │ └── main │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── testing │ │ ├── PropertyService.kt │ │ ├── ComponentBuilderExt.kt │ │ ├── GraphExt.kt │ │ └── ReflectExt.kt └── build.gradle ├── android-sample-app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── quote_item_view.xml │ │ │ │ └── activity_quotes.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── jentz │ │ │ │ └── winter │ │ │ │ └── android │ │ │ │ └── sample │ │ │ │ ├── model │ │ │ │ ├── Quote.kt │ │ │ │ └── QuoteRepository.kt │ │ │ │ ├── viewmodel │ │ │ │ ├── ViewModel.kt │ │ │ │ └── TestViewModel.kt │ │ │ │ ├── quotes │ │ │ │ ├── QuotesViewState.kt │ │ │ │ ├── QuoteFormatter.kt │ │ │ │ ├── QuoteItemView.kt │ │ │ │ ├── QuotesAdapter.kt │ │ │ │ ├── QuotesActivity.kt │ │ │ │ └── QuotesViewModel.kt │ │ │ │ └── IntegrationTestApp.kt │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── android │ │ └── sample │ │ └── EspressoExt.kt ├── proguard-rules.pro └── build.gradle ├── winter-androidx-fragment ├── consumer-rules.pro ├── .gitignore ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── androidx │ │ └── fragment │ │ ├── exportAndroidTypes.kt │ │ └── WinterFragmentFactory.kt ├── proguard-rules.pro └── build.gradle ├── winter-compiler-test ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── compiler │ │ │ └── test │ │ │ └── KotlinProperties.kt │ └── test │ │ └── java │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── compiler │ │ └── test │ │ └── KotlinPropertiesTest.kt └── build.gradle ├── winter-coroutines ├── .gitignore ├── gradle.properties ├── src │ ├── main │ │ └── kotlin │ │ │ └── io │ │ │ └── jentz │ │ │ └── winter │ │ │ └── coroutines │ │ │ └── ComponentBuilderExt.kt │ └── test │ │ └── kotlin │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── coroutines │ │ └── ComponentBuilderExtTest.kt └── build.gradle ├── winter-androidx-integration-test ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── jentz │ │ │ │ └── winter │ │ │ │ └── androidx │ │ │ │ └── integration │ │ │ │ └── test │ │ │ │ ├── TestFragment.kt │ │ │ │ ├── TestViewModel.kt │ │ │ │ ├── TestSavedstateViewModel.kt │ │ │ │ ├── TestActivity.kt │ │ │ │ └── TestApp.kt │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── androidx │ │ └── integration │ │ └── test │ │ └── DependencyGraphContextWrapperTest.kt ├── proguard-rules.pro └── build.gradle ├── winter-androidx-viewmodel-savedstate ├── .gitignore ├── consumer-rules.pro ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── io │ │ └── jentz │ │ └── winter │ │ └── androidx │ │ └── viewmodel │ │ └── savedstate │ │ ├── WinterSavedStateViewModelFactory.kt │ │ └── ComponentBuilderExt.kt ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── gradle.properties ├── .gitignore ├── .travis.yml ├── deploy_website.rb └── gradlew.bat /winter/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-java/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-androidx/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-compiler/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-junit4/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-junit5/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-rxjava2/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-testing/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android-sample-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-androidx-fragment/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /winter-compiler-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-coroutines/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-androidx-fragment/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /winter/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-java/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter Java 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-junit4/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter JUnit 4 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-junit5/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter JUnit 5 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-rxjava2/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter RxJava2 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-testing/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter Testing 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-androidx/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter AndroidX 2 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /winter-compiler/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter Compiler 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter-coroutines/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter Coroutines 2 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /winter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /winter-androidx-fragment/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter AndroidX Fragment 2 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /winter-androidx/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Winter AndroidX ViewModel Savedstate 2 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /winter-compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | io.jentz.winter.compiler.WinterProcessor -------------------------------------------------------------------------------- /android-sample-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Winter Android Sample 3 | 4 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Winter AndroidX Integration Test 3 | 4 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /winter-androidx-fragment/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beyama/winter/HEAD/android-sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /winter-compiler/src/test/java/io/jentz/winter/compiler/CustomScope.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class CustomScope -------------------------------------------------------------------------------- /winter-junit5/src/test/kotlin/io/jentz/winter/junit5/Mock.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | @Retention 4 | @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) 5 | annotation class Mock -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /winter-testing/src/test/kotlin/io/jentz/winter/testing/Mock.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | @Retention 4 | @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) 5 | annotation class Mock 6 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/model/Quote.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.model 2 | 3 | data class Quote( 4 | val originator: String, 5 | val quote: String 6 | ) -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedFieldExtended.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | public class WithInjectedFieldExtended extends WithInjectedField { 6 | @Inject String field1; 7 | } -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/java/io/jentz/winter/androidx/integration/test/TestFragment.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.integration.test 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | class TestFragment : Fragment() 6 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/viewmodel/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.viewmodel 2 | 3 | import io.reactivex.Flowable 4 | 5 | interface ViewModel { 6 | fun toFlowable(): Flowable 7 | } -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/DirectServiceEvaluatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | class DirectServiceEvaluatorTest : AbstractServiceEvaluatorTest() { 4 | override val evaluator = DirectServiceEvaluator() 5 | } 6 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedField.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | public class WithInjectedField { 6 | 7 | @Inject 8 | public WithInjectedField() {} 9 | 10 | @Inject String field0; 11 | } -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/date.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import java.util.* 4 | 5 | // Used to set a fixed date for testing 6 | internal var currentDateFixed: Date? = null 7 | 8 | fun now(): Date = currentDateFixed ?: Date() 9 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/OneArgumentInjectConstructor.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | public class OneArgumentInjectConstructor { 6 | @Inject 7 | public OneArgumentInjectConstructor(String arg) { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 28 10:34:20 CEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/Prototype.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | /** 4 | * Marks a class as prototype scoped. 5 | */ 6 | @Target(AnnotationTarget.CLASS) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @MustBeDocumented 9 | annotation class Prototype 10 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/EagerSingletonAnnotation.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.jentz.winter.inject.EagerSingleton; 4 | import io.jentz.winter.inject.InjectConstructor; 5 | 6 | @EagerSingleton 7 | @InjectConstructor 8 | public class EagerSingletonAnnotation { 9 | } 10 | 11 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/NoArgumentInjectConstructor.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | @Singleton 7 | public class NoArgumentInjectConstructor { 8 | @Inject 9 | public NoArgumentInjectConstructor() { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/EagerSingleton.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | /** 4 | * Marks a class as eager singleton scoped. 5 | */ 6 | @Target(AnnotationTarget.CLASS) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @MustBeDocumented 9 | annotation class EagerSingleton 10 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/quotes/QuotesViewState.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.quotes 2 | 3 | import io.jentz.winter.android.sample.model.Quote 4 | 5 | data class QuotesViewState( 6 | val isLoading: Boolean = false, 7 | val quotes: List = emptyList() 8 | ) -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorAnnotationWithType.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | 5 | import io.jentz.winter.inject.InjectConstructor; 6 | 7 | @InjectConstructor(AtomicBoolean.class) 8 | class InjectConstructorAnnotationWithType extends AtomicBoolean { 9 | } -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/MembersInjector.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | import io.jentz.winter.Graph 4 | 5 | /** 6 | * Interface for members injectors generated by Winter compiler. 7 | */ 8 | interface MembersInjector { 9 | fun inject(graph: Graph, target: T) 10 | } 11 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/PrivateNoArgumentInjectConstructor.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | @Singleton 7 | public class PrivateNoArgumentInjectConstructor { 8 | @Inject 9 | private PrivateNoArgumentInjectConstructor() { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorAnnotation.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.jentz.winter.inject.InjectConstructor; 6 | 7 | @InjectConstructor 8 | public class InjectConstructorAnnotation { 9 | public InjectConstructorAnnotation(String value) { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/CyclicDependenciesCheckingDirectServiceEvaluatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | class CyclicDependenciesCheckingDirectServiceEvaluatorTest : AbstractCyclicServiceEvaluatorTest() { 4 | override val evaluator = CyclicDependenciesCheckingDirectServiceEvaluator() 5 | } 6 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithCustomScope.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.jentz.winter.compiler.CustomScope; 6 | import io.jentz.winter.inject.ApplicationScope; 7 | 8 | @CustomScope 9 | public class WithCustomScope { 10 | @Inject 11 | public WithCustomScope() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /winter-java/src/test/java/io/jentz/winter/java/di.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Di") 2 | 3 | package io.jentz.winter.java 4 | 5 | import io.jentz.winter.component 6 | 7 | val testComponent = component { 8 | prototype { "prototype" } 9 | prototype("a") { "prototype a" } 10 | prototype("b") { "prototype b" } 11 | prototype("c") { "prototype c" } 12 | } 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':winter', ':winter-compiler', ':android-sample-app', ':winter-rxjava2', ':winter-junit4', 2 | ':winter-androidx', ':winter-junit5', ':winter-testing', ':winter-java', ':winter-androidx-integration-test', 3 | ':winter-compiler-test', ':winter-androidx-viewmodel-savedstate', ':winter-androidx-fragment', 4 | ':winter-coroutines' 5 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/java/io/jentz/winter/androidx/integration/test/TestViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.integration.test 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.ViewModel 5 | 6 | class SomeViewModelDependency 7 | 8 | class TestViewModel(val application: Application, dependency: SomeViewModelDependency) : ViewModel() -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/inject/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.inject 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Scope annotation for dependencies with [android.app.Activity] lifetime. 7 | */ 8 | @Scope 9 | @MustBeDocumented 10 | @Retention(AnnotationRetention.RUNTIME) 11 | annotation class ActivityScope 12 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/NamedSingletonInjectConstructor.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Named; 5 | import javax.inject.Singleton; 6 | 7 | @Singleton 8 | @Named("variant1") 9 | public class NamedSingletonInjectConstructor { 10 | @Inject 11 | public NamedSingletonInjectConstructor() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/Scope.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | data class Scope(val name: String) { 4 | companion object { 5 | val Prototype = Scope("prototype") 6 | val Singleton = Scope("singleton") 7 | val SoftSingleton = Scope("softSingleton") 8 | val WeakSingleton = Scope("weakSingleton") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedGenericFields.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class WithInjectedGenericFields { 11 | @Inject @NotNull Map field0; 12 | @Inject List field1; 13 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/FactoryTypeAnnotation.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | 5 | import javax.inject.Inject; 6 | 7 | import io.jentz.winter.inject.FactoryType; 8 | 9 | @FactoryType(AtomicBoolean.class) 10 | class FactoryTypeAnnotation extends AtomicBoolean { 11 | @Inject public FactoryTypeAnnotation() {} 12 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/PrototypeAnnotation.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.jentz.winter.inject.ApplicationScope; 6 | import io.jentz.winter.inject.InjectConstructor; 7 | import io.jentz.winter.inject.Prototype; 8 | 9 | @Prototype 10 | @ApplicationScope 11 | @InjectConstructor 12 | public class PrototypeAnnotation { 13 | } 14 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/java/io/jentz/winter/androidx/integration/test/TestSavedstateViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.integration.test 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.SavedStateHandle 5 | import androidx.lifecycle.ViewModel 6 | 7 | class TestSavedstateViewModel(val application: Application, handle: SavedStateHandle) : ViewModel() -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/OneNamedArgumentInjectConstructor.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Named; 7 | 8 | public class OneNamedArgumentInjectConstructor { 9 | @Inject 10 | public OneNamedArgumentInjectConstructor(@Named("a name") @NotNull String arg) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorAnnotationWithInjectConstructor.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.jentz.winter.inject.InjectConstructor; 6 | 7 | @InjectConstructor 8 | public class InjectConstructorAnnotationWithInjectConstructor { 9 | @Inject 10 | public InjectConstructorAnnotationWithInjectConstructor() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/ApplicationScope.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Scope annotation for application wide available dependencies and default qualifier for root 7 | * [components][io.jentz.winter.Component]. 8 | */ 9 | @Scope 10 | @Retention(AnnotationRetention.RUNTIME) 11 | @MustBeDocumented 12 | annotation class ApplicationScope 13 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/inject/PresentationScope.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.inject 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Scope annotation for dependencies that outlive Activity recreations and are destroyed when an 7 | * [android.app.Activity] finishes. 8 | */ 9 | @Scope 10 | @MustBeDocumented 11 | @Retention(AnnotationRetention.RUNTIME) 12 | annotation class PresentationScope 13 | -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/SourceWriter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.FileSpec 4 | import javax.annotation.processing.Filer 5 | 6 | interface SourceWriter { 7 | fun write(fileSpec: FileSpec) 8 | } 9 | 10 | class FilerSourceWriter(private val filer: Filer): SourceWriter { 11 | override fun write(fileSpec: FileSpec) { 12 | fileSpec.writeTo(filer) 13 | } 14 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/FactoryTypeAnnotationAndTypeInInjectConstructorAnnotation.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | 5 | import io.jentz.winter.inject.FactoryType; 6 | import io.jentz.winter.inject.InjectConstructor; 7 | 8 | @FactoryType(AtomicBoolean.class) 9 | @InjectConstructor(AtomicBoolean.class) 10 | class FactoryTypeAnnotationAndTypeInInjectConstructorAnnotation extends AtomicBoolean { 11 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/PrototypeAndEagerSingletonAnnotation.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.jentz.winter.inject.ApplicationScope; 6 | import io.jentz.winter.inject.EagerSingleton; 7 | import io.jentz.winter.inject.InjectConstructor; 8 | import io.jentz.winter.inject.Prototype; 9 | 10 | @Prototype 11 | @EagerSingleton 12 | @InjectConstructor 13 | public class PrototypeAndEagerSingletonAnnotation { 14 | } 15 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/FactoryType.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * Change the factory type to one of the super types of the annotated classes. 7 | * 8 | * @param value Register the annotated class with one of its super types. 9 | */ 10 | @Target(AnnotationTarget.CLASS) 11 | @Retention(AnnotationRetention.RUNTIME) 12 | @MustBeDocumented 13 | annotation class FactoryType(val value: KClass<*>) 14 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/java/io/jentz/winter/androidx/integration/test/TestActivity.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.integration.test 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.FragmentActivity 5 | import io.jentz.winter.Winter 6 | 7 | class TestActivity : FragmentActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | Winter.inject(this) 11 | super.onCreate(savedInstanceState) 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorAnnotationWithTwoConstructors.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | import io.jentz.winter.inject.InjectConstructor; 7 | 8 | @InjectConstructor 9 | public class InjectConstructorAnnotationWithTwoConstructors { 10 | public InjectConstructorAnnotationWithTwoConstructors() { 11 | } 12 | public InjectConstructorAnnotationWithTwoConstructors(String arg) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/Factory.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | 7 | /** 8 | * Interface implemented by factories generated by winter-compiler. 9 | */ 10 | interface Factory : (Graph) -> R { 11 | 12 | /** 13 | * Register the factory on the given [builder] instance. 14 | */ 15 | fun register(builder: Component.Builder, override: Boolean): TypeKey 16 | 17 | } 18 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/layout/quote_item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedField_WinterMembersInjector.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.inject.MembersInjector 5 | import javax.annotation.Generated 6 | import kotlin.String 7 | 8 | @Generated( 9 | value = ["io.jentz.winter.compiler.WinterProcessor"], 10 | date = "2019-02-10T14:52Z" 11 | ) 12 | class WithInjectedField_WinterMembersInjector : MembersInjector { 13 | override fun inject(graph: Graph, target: WithInjectedField) { 14 | target.field0 = graph.instanceOrNull() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/evaluator/ServiceEvaluator.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.BoundService 4 | 5 | /** 6 | * Service evaluators are used to call [BoundService.newInstance] to retrieve a new instance from 7 | * a service. 8 | * 9 | * They are responsible for handling exceptions that may be thrown by the [BoundService], 10 | * calling plugin and service lifecycle methods and maybe performing cyclic dependency checks. 11 | */ 12 | internal interface ServiceEvaluator { 13 | fun evaluate(service: BoundService): R 14 | } 15 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorWithProviderAndLazyArguments.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.List; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Named; 9 | import javax.inject.Provider; 10 | 11 | import kotlin.Lazy; 12 | 13 | public class InjectConstructorWithProviderAndLazyArguments { 14 | @Inject 15 | public InjectConstructorWithProviderAndLazyArguments( 16 | @NotNull @Named("string") Provider arg0, 17 | @Named("stringList") Lazy> arg1 18 | ) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/viewmodel/WinterViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import io.jentz.winter.GFactory 6 | import io.jentz.winter.Graph 7 | 8 | @PublishedApi 9 | internal class WinterViewModelFactory( 10 | private val graph: Graph, 11 | private val factory: GFactory 12 | ) : ViewModelProvider.Factory { 13 | 14 | @Suppress("UNCHECKED_CAST") 15 | override fun create(modelClass: Class): T = factory.invoke(graph) as T 16 | 17 | } 18 | -------------------------------------------------------------------------------- /winter/dokka-packages.md: -------------------------------------------------------------------------------- 1 | # Package io.jentz.winter 2 | 3 | ### Winter API 4 | 5 | The building blocks of Winter are the `components` and the `graphs`. 6 | 7 | #### Components 8 | 9 | Components store the dependency provider; basically the dependency definitions provided to Winter - they are 10 | the blueprints for the `graph`. 11 | 12 | * they are immutable 13 | * they can be extended (by using derive) 14 | * they can be mixed (by using include in the builder) 15 | 16 | #### Graphs 17 | 18 | Graphs are used to retrieve and instantiate dependencies defined in `components`. 19 | 20 | Think of Graphs as instances of `components`. 21 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/inject/InjectConstructor.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.inject 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * Annotation stolen from Toothpick that tells the winter-compiler that the first and only 7 | * constructor of an annotated class should be treated like it were annotated with 8 | * [javax.inject.Inject]. 9 | * 10 | * @param value Allows to register the annotated class with one of its super types. 11 | */ 12 | @Target(AnnotationTarget.CLASS) 13 | @Retention(AnnotationRetention.RUNTIME) 14 | @MustBeDocumented 15 | annotation class InjectConstructor(val value: KClass<*> = Nothing::class) 16 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/plugin/SimplePlugin.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.plugin 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.Scope 6 | 7 | /** 8 | * Empty implementation of [Plugin]. 9 | */ 10 | open class SimplePlugin : Plugin { 11 | 12 | override fun graphInitializing(parentGraph: Graph?, builder: Component.Builder) { 13 | } 14 | 15 | override fun graphInitialized(graph: Graph) { 16 | } 17 | 18 | override fun graphClose(graph: Graph) { 19 | } 20 | 21 | override fun postConstruct(graph: Graph, scope: Scope, instance: Any) { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/viewmodel/WinterViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import io.jentz.winter.Graph 5 | 6 | /** 7 | * A ViewModel that can hold a [Graph] instance and closes it [onCleared]. 8 | * 9 | * This is intended to be used inside an [io.jentz.winter.WinterApplication.InjectionAdapter] to 10 | * hold graphs that outlive configuration changes. 11 | */ 12 | class WinterViewModel : ViewModel() { 13 | 14 | var graph: Graph? = null 15 | 16 | override fun onCleared() { 17 | graph?.close() 18 | graph = null 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedProviderAndLazyFields.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Named; 7 | import javax.inject.Provider; 8 | 9 | import kotlin.Lazy; 10 | import kotlin.jvm.functions.Function0; 11 | 12 | public class WithInjectedProviderAndLazyFields { 13 | @Inject 14 | Object field0; 15 | 16 | @Inject 17 | @Named("stringList") 18 | Provider> field1; 19 | 20 | @Inject 21 | @Named("stringList") 22 | Function0> field2; 23 | 24 | @Inject 25 | @Named("stringList") 26 | Lazy> field3; 27 | } -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/main/java/io/jentz/winter/androidx/integration/test/TestApp.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.integration.test 2 | 3 | import android.app.Application 4 | import io.jentz.winter.Winter 5 | import io.jentz.winter.adapter.useApplicationGraphOnlyAdapter 6 | import io.jentz.winter.emptyComponent 7 | 8 | @Suppress("unused") 9 | class TestApp : Application() { 10 | 11 | override fun onCreate() { 12 | super.onCreate() 13 | 14 | Winter.useApplicationGraphOnlyAdapter() 15 | Winter.component = emptyComponent() 16 | Winter.openGraph { 17 | constant(this@TestApp) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedFieldExtended_WinterMembersInjector.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.inject.MembersInjector 5 | import javax.annotation.Generated 6 | import kotlin.String 7 | 8 | @Generated( 9 | value = ["io.jentz.winter.compiler.WinterProcessor"], 10 | date = "2019-02-10T14:52Z" 11 | ) 12 | class WithInjectedFieldExtended_WinterMembersInjector : MembersInjector { 13 | override fun inject(graph: Graph, target: WithInjectedFieldExtended) { 14 | WithInjectedField_WinterMembersInjector().inject(graph, target) 15 | target.field1 = graph.instanceOrNull() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /winter-junit5/src/test/kotlin/io/jentz/winter/junit5/WinterAllExtensionStaticTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import io.jentz.winter.WinterApplication 4 | import io.kotlintest.shouldBe 5 | import org.junit.jupiter.api.Test 6 | import org.junit.jupiter.api.extension.RegisterExtension 7 | 8 | class WinterAllExtensionStaticTest { 9 | 10 | companion object { 11 | 12 | val app = WinterApplication() 13 | 14 | @JvmField 15 | @RegisterExtension 16 | val extension = WinterAllExtension { 17 | application = app 18 | } 19 | } 20 | 21 | @Test 22 | fun `session plugin should be registered`() { 23 | app.plugins.size.shouldBe(1) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=0.9.0 2 | GROUP=io.jentz.winter 3 | 4 | POM_DESCRIPTION=Kotlin Dependency Injection 5 | POM_URL=https://github.com/beyama/winter 6 | POM_SCM_URL=https://github.com/beyama/winter 7 | POM_SCM_CONNECTION=scm:git:https://github.com/beyama/winter.git 8 | POM_SCM_DEV_CONNECTION=scm:git:https://github.com/beyama/winter.git 9 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 10 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 11 | POM_ISSUE_MANAGEMENT_SYSTEM=GitHub Issues 12 | POM_ISSUE_MANAGEMENT_URL=https://github.com/beyama/winter/issues 13 | POM_LICENCE_DIST=repo 14 | POM_DEVELOPER_ID=ajentz 15 | POM_DEVELOPER_NAME=Alexander Jentz 16 | android.useAndroidX=true 17 | android.enableJetifier=true -------------------------------------------------------------------------------- /android-sample-app/src/androidTest/java/io/jentz/winter/android/sample/EspressoExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample 2 | 3 | import androidx.test.espresso.DataInteraction 4 | import androidx.test.espresso.ViewInteraction 5 | import androidx.test.espresso.action.ViewActions 6 | import androidx.test.espresso.assertion.ViewAssertions 7 | import androidx.test.espresso.matcher.ViewMatchers 8 | import org.hamcrest.Matchers 9 | 10 | fun ViewInteraction.isNotDisplayed() = check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed()))) 11 | fun ViewInteraction.isDisplayed() = check(ViewAssertions.matches(ViewMatchers.isDisplayed())) 12 | fun ViewInteraction.click() = perform(ViewActions.click()) 13 | fun DataInteraction.click() = perform(ViewActions.click()) -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/evaluator/DirectServiceEvaluator.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.BoundService 4 | 5 | /** 6 | * The simplest [ServiceEvaluator] which only calls [BoundService.newInstance] and handles 7 | * exceptions. 8 | * 9 | * This is used when no plugin is registered and no service requires lifecycle callbacks and cyclic 10 | * dependency checks are disabled. 11 | */ 12 | internal class DirectServiceEvaluator : ServiceEvaluator { 13 | 14 | override fun evaluate(service: BoundService): R { 15 | return try { 16 | service.newInstance() 17 | } catch (t: Throwable) { 18 | handleException(service.key, t) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/evaluator/createServiceEvaluator.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.plugin.Plugins 6 | 7 | internal fun createServiceEvaluator( 8 | graph: Graph, 9 | component: Component, 10 | plugins: Plugins, 11 | checkForCyclicDependencies: Boolean 12 | ): ServiceEvaluator = when { 13 | component.requiresLifecycleCallbacks || plugins.isNotEmpty() -> { 14 | LifecycleServiceEvaluator(graph, plugins, checkForCyclicDependencies) 15 | } 16 | checkForCyclicDependencies -> { 17 | CyclicDependenciesCheckingDirectServiceEvaluator() 18 | } 19 | else -> { 20 | DirectServiceEvaluator() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/viewmodel/TestViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.viewmodel 2 | 3 | import io.reactivex.BackpressureStrategy 4 | import io.reactivex.Flowable 5 | import io.reactivex.disposables.Disposable 6 | import io.reactivex.subjects.BehaviorSubject 7 | import io.reactivex.subjects.Subject 8 | 9 | class TestViewModel : ViewModel, Disposable { 10 | 11 | private var isDisposed = false 12 | 13 | val downstream: Subject = BehaviorSubject.create() 14 | 15 | override fun toFlowable(): Flowable = downstream.toFlowable(BackpressureStrategy.LATEST) 16 | 17 | override fun isDisposed(): Boolean = isDisposed 18 | 19 | override fun dispose() { 20 | isDisposed = true 21 | } 22 | } -------------------------------------------------------------------------------- /android-sample-app/src/main/res/layout/activity_quotes.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea 40 | 41 | # Keystore files 42 | *.jks 43 | 44 | # External native build folder generated in Android Studio 2.2 and later 45 | .externalNativeBuild 46 | 47 | tmp_clone 48 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedGenericFields_WinterMembersInjector.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.inject.MembersInjector 5 | import javax.annotation.Generated 6 | import kotlin.Int 7 | import kotlin.String 8 | import kotlin.collections.List 9 | import kotlin.collections.Map 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class WithInjectedGenericFields_WinterMembersInjector : MembersInjector { 16 | override fun inject(graph: Graph, target: WithInjectedGenericFields) { 17 | target.field0 = graph.instance>(generics = true) 18 | target.field1 = graph.instanceOrNull>(generics = true) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/adapter/ApplicationGraphOnlyInjectionAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.adapter 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.WinterApplication 5 | 6 | /** 7 | * Simple adapter for application with only one dependency graph. 8 | */ 9 | open class ApplicationGraphOnlyInjectionAdapter( 10 | protected val app: WinterApplication 11 | ) : WinterApplication.InjectionAdapter { 12 | 13 | override fun get(instance: Any): Graph? = app.getOrOpenGraph() 14 | 15 | } 16 | 17 | /** 18 | * Register an [ApplicationGraphOnlyInjectionAdapter] on this [WinterApplication] instance. 19 | */ 20 | fun WinterApplication.useApplicationGraphOnlyAdapter() { 21 | injectionAdapter = ApplicationGraphOnlyInjectionAdapter(this) 22 | } 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/FactoryTypeAnnotation_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | import javax.annotation.Generated 9 | import kotlin.Boolean 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class FactoryTypeAnnotation_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): AtomicBoolean = FactoryTypeAnnotation() 17 | 18 | override fun register(builder: Component.Builder, override: Boolean): TypeKey = 19 | builder.prototype(override = override, factory = this) 20 | } 21 | -------------------------------------------------------------------------------- /winter-junit5/src/main/kotlin/io/jentz/winter/junit5/WInject.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import kotlin.annotation.AnnotationRetention.RUNTIME 4 | import kotlin.annotation.AnnotationTarget.* 5 | 6 | /** 7 | * This annotation can be used to inject values into JUnit5 methods and constructors by using 8 | * the test graph to resolve them. 9 | * 10 | * The problem with Javax Inject is, that it does not allow value parameter targets. 11 | * 12 | * Example in a test using one of the Winter JUnit5 extensions: 13 | * ``` 14 | * @Test fun myTest(@WInject service: Service) { 15 | * // do something with service 16 | * } 17 | * 18 | * ``` 19 | * 20 | */ 21 | @Target(VALUE_PARAMETER) 22 | @Retention(RUNTIME) 23 | @MustBeDocumented 24 | annotation class WInject(val qualifier: String = "") 25 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/EagerSingletonAnnotation_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import javax.annotation.Generated 8 | import kotlin.Boolean 9 | 10 | @Generated( 11 | value = ["io.jentz.winter.compiler.WinterProcessor"], 12 | date = "2019-02-10T14:52Z" 13 | ) 14 | class EagerSingletonAnnotation_WinterFactory : Factory { 15 | override fun invoke(graph: Graph): EagerSingletonAnnotation = EagerSingletonAnnotation() 16 | 17 | override fun register(builder: Component.Builder, override: Boolean): 18 | TypeKey = builder.eagerSingleton(override = override, factory = 19 | this) 20 | } 21 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/quotes/QuoteFormatter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.quotes 2 | 3 | import android.text.Spannable 4 | import android.text.SpannableStringBuilder 5 | import androidx.core.text.bold 6 | import io.jentz.winter.android.sample.model.Quote 7 | import io.jentz.winter.androidx.inject.ActivityScope 8 | import io.jentz.winter.inject.InjectConstructor 9 | 10 | @ActivityScope 11 | @InjectConstructor 12 | class QuoteFormatter { 13 | 14 | fun format(quote: Quote): Spannable = SpannableStringBuilder().let { 15 | it.bold { append("\"") } 16 | it.append(quote.quote) 17 | it.bold { append("\"") } 18 | it.append("\n") 19 | it.bold { 20 | append("- ") 21 | append(quote.originator) 22 | } 23 | } 24 | 25 | 26 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorAnnotationWithType_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | import javax.annotation.Generated 9 | import kotlin.Boolean 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class InjectConstructorAnnotationWithType_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): AtomicBoolean = InjectConstructorAnnotationWithType() 17 | 18 | override fun register(builder: Component.Builder, override: Boolean): TypeKey = 19 | builder.prototype(override = override, factory = this) 20 | } 21 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/Winter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | /** 4 | * The default [WinterApplication] object. 5 | * 6 | * It is recommended for applications to use this directly and for libraries it is recommended to 7 | * create a library specific object based on [WinterApplication]. 8 | * 9 | * Example: 10 | * 11 | * ``` 12 | * // configure application component 13 | * Winter.component { 14 | * // ... dependency declaration 15 | * } 16 | * // install RxJava 2 disposable plugin 17 | * Winter.installDisposablePlugin() 18 | * // configure injection adapter. 19 | * Winter.useAndroidPresentationScopeInjectionAdapter() 20 | * // open the application dependency graph 21 | * Winter.openGraph() 22 | * ``` 23 | * 24 | * @see WinterApplication for more details. 25 | */ 26 | object Winter : WinterApplication() 27 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/quotes/QuoteItemView.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.quotes 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.FrameLayout 6 | import io.jentz.winter.Winter 7 | import io.jentz.winter.android.sample.model.Quote 8 | import kotlinx.android.synthetic.main.quote_item_view.view.* 9 | import javax.inject.Inject 10 | 11 | class QuoteItemView @JvmOverloads constructor( 12 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 13 | ) : FrameLayout(context, attrs, defStyleAttr) { 14 | 15 | @Inject lateinit var quoteFormatter: QuoteFormatter 16 | 17 | init { 18 | Winter.inject(this) 19 | } 20 | 21 | fun render(quote: Quote) { 22 | textView.text = quoteFormatter.format(quote) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/generatedComponent.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.compiler.CustomScope 5 | import io.jentz.winter.component 6 | import io.jentz.winter.inject.ApplicationScope 7 | import javax.annotation.Generated 8 | 9 | @Generated( 10 | value = ["io.jentz.winter.compiler.WinterProcessor"], 11 | date = "2019-02-10T14:52Z" 12 | ) 13 | val generatedComponent: Component = component("generated") { 14 | subcomponent(ApplicationScope::class) { 15 | InjectConstructorAnnotation_WinterFactory().register(this, false) 16 | PrototypeAnnotation_WinterFactory().register(this, false) 17 | NamedSingletonInjectConstructor_WinterFactory().register(this, false) 18 | } 19 | subcomponent(CustomScope::class) { 20 | WithCustomScope_WinterFactory().register(this, false) 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /android-sample-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /winter-androidx-fragment/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /winter-compiler-test/src/main/java/io/jentz/winter/compiler/test/KotlinProperties.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler.test 2 | 3 | import io.jentz.winter.inject.ApplicationScope 4 | import io.jentz.winter.inject.InjectConstructor 5 | import javax.inject.Inject 6 | import javax.inject.Named 7 | 8 | @ApplicationScope 9 | @InjectConstructor 10 | class KotlinProperties( 11 | val constructorInjectedString: String, 12 | @Named("someInt") val constructorInjectedPrimitive: Int 13 | ) { 14 | 15 | @Inject var primitiveProperty: Int = 0 16 | 17 | @set:Inject var primitiveSetter: Int = 0 18 | 19 | @Inject @Named("someInt") var namedPrimitiveProperty: Int = 0 20 | 21 | @set:[Inject Named("someInt")] var namedPrimitiveSetter: Int = 0 22 | 23 | @Inject lateinit var someList: List 24 | 25 | @Inject lateinit var stringProvider: () -> String 26 | } 27 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorAnnotation_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import javax.annotation.Generated 8 | import kotlin.Boolean 9 | import kotlin.String 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class InjectConstructorAnnotation_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): InjectConstructorAnnotation = 17 | InjectConstructorAnnotation(graph.instanceOrNull()) 18 | 19 | override fun register(builder: Component.Builder, override: Boolean): 20 | TypeKey = builder.prototype(override = override, factory = this) 21 | } 22 | -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/OneArgumentInjectConstructor_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import javax.annotation.Generated 8 | import kotlin.Boolean 9 | import kotlin.String 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class OneArgumentInjectConstructor_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): OneArgumentInjectConstructor = 17 | OneArgumentInjectConstructor(graph.instanceOrNull()) 18 | 19 | override fun register(builder: Component.Builder, override: Boolean): 20 | TypeKey = builder.prototype(override = override, factory = this) 21 | } 22 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedField_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import javax.annotation.Generated 8 | import kotlin.Boolean 9 | 10 | @Generated( 11 | value = ["io.jentz.winter.compiler.WinterProcessor"], 12 | date = "2019-02-10T14:52Z" 13 | ) 14 | class WithInjectedField_WinterFactory : Factory { 15 | override fun invoke(graph: Graph): WithInjectedField { 16 | val instance = WithInjectedField() 17 | WithInjectedField_WinterMembersInjector().inject(graph, instance) 18 | return instance 19 | } 20 | 21 | override fun register(builder: Component.Builder, override: Boolean): TypeKey = 22 | builder.prototype(override = override, factory = this) 23 | } 24 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/PrototypeAnnotation_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.ApplicationScope 7 | import io.jentz.winter.inject.Factory 8 | import javax.annotation.Generated 9 | import kotlin.Boolean 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class PrototypeAnnotation_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): PrototypeAnnotation = PrototypeAnnotation() 17 | 18 | override fun register(builder: Component.Builder, override: Boolean): 19 | TypeKey { 20 | builder.checkComponentQualifier(ApplicationScope::class) 21 | return builder.prototype(override = override, factory = this) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /winter-coroutines/src/main/kotlin/io/jentz/winter/coroutines/ComponentBuilderExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.coroutines 2 | 3 | import io.jentz.winter.Component 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Job 6 | import kotlinx.coroutines.SupervisorJob 7 | import kotlin.coroutines.CoroutineContext 8 | import kotlin.coroutines.EmptyCoroutineContext 9 | 10 | /** 11 | * Register a [CoroutineScope] on this component. 12 | * 13 | * If the give [context] doesn't contain a [Job] a [SupervisorJob] is added to it. 14 | * The [contexts][context] job gets canceled when the graph gets closed. 15 | */ 16 | fun Component.Builder.coroutineScope(context: CoroutineContext = EmptyCoroutineContext) { 17 | 18 | singleton(onClose = { it.coroutineContext[Job]?.cancel() }) { 19 | CoroutineScope(if (context[Job] != null) context else context + SupervisorJob()) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/delegate/DelegateNotifier.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.delegate 2 | 3 | import io.jentz.winter.Graph 4 | import java.util.* 5 | 6 | /** 7 | * Holds a map of instances to a list of [injected properties][InjectedProperty] until 8 | * [Graph.inject] with the instance is called which will trigger [notify]. 9 | * 10 | * Inspired by Toothpick KTP. 11 | */ 12 | internal object DelegateNotifier { 13 | 14 | private val delegates = 15 | Collections.synchronizedMap(WeakHashMap>>()) 16 | 17 | fun register(owner: Any, property: InjectedProperty<*>) { 18 | delegates 19 | .getOrPut(owner) { mutableListOf() } 20 | .add(property) 21 | } 22 | 23 | fun notify(owner: Any, graph: Graph): Boolean { 24 | return delegates.remove(owner)?.onEach { it.inject(graph) } != null 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/OneNamedArgumentInjectConstructor_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import javax.annotation.Generated 8 | import kotlin.Boolean 9 | import kotlin.String 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class OneNamedArgumentInjectConstructor_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): OneNamedArgumentInjectConstructor = 17 | OneNamedArgumentInjectConstructor(graph.instance(qualifier = "a name")) 18 | 19 | override fun register(builder: Component.Builder, override: Boolean): 20 | TypeKey = builder.prototype(override = override, factory = 21 | this) 22 | } 23 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/NoArgumentInjectConstructor_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.ApplicationScope 7 | import io.jentz.winter.inject.Factory 8 | import javax.annotation.Generated 9 | import kotlin.Boolean 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class NoArgumentInjectConstructor_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): NoArgumentInjectConstructor = NoArgumentInjectConstructor() 17 | 18 | override fun register(builder: Component.Builder, override: Boolean): 19 | TypeKey { 20 | builder.checkComponentQualifier(ApplicationScope::class) 21 | return builder.singleton(override = override, factory = this) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android-sample-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /winter-junit4/src/test/kotlin/io/jentz/winter/junit4/WinterRuleTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit4 2 | 3 | import io.jentz.winter.WinterApplication 4 | import io.kotlintest.matchers.boolean.shouldBeTrue 5 | import io.kotlintest.shouldBe 6 | import org.junit.Rule 7 | import org.junit.Test 8 | import org.junit.runner.JUnitCore 9 | 10 | class WinterRuleTest { 11 | 12 | private object TestApp : WinterApplication(block = {}) 13 | 14 | class EachRunnerTest { 15 | 16 | @get:Rule val rule = WinterRule { 17 | application = TestApp 18 | } 19 | 20 | @Test 21 | fun `session plugin should be registered`() { 22 | TestApp.plugins.size.shouldBe(1) 23 | } 24 | 25 | } 26 | 27 | @Test 28 | fun `should unregister plugin after test`() { 29 | JUnitCore.runClasses(EachRunnerTest::class.java).wasSuccessful().shouldBeTrue() 30 | TestApp.plugins.isEmpty().shouldBeTrue() 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/NamedSingletonInjectConstructor_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.ApplicationScope 7 | import io.jentz.winter.inject.Factory 8 | import javax.annotation.Generated 9 | import kotlin.Boolean 10 | 11 | @Generated( 12 | value = ["io.jentz.winter.compiler.WinterProcessor"], 13 | date = "2019-02-10T14:52Z" 14 | ) 15 | class NamedSingletonInjectConstructor_WinterFactory : Factory { 16 | override fun invoke(graph: Graph): NamedSingletonInjectConstructor = 17 | NamedSingletonInjectConstructor() 18 | 19 | override fun register(builder: Component.Builder, override: Boolean): 20 | TypeKey { 21 | builder.checkComponentQualifier(ApplicationScope::class) 22 | return builder.singleton(qualifier = "variant1", override = override, factory = this) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/WinterException.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | /** 4 | * Base exception class of all exception thrown by Winter. 5 | */ 6 | open class WinterException(message: String?, cause: Throwable? = null) : Exception(message, cause) 7 | 8 | /** 9 | * Exception that is thrown when a component entry or graph was not found but was requested as 10 | * non-optional. 11 | */ 12 | class EntryNotFoundException(val key: TypeKey<*>, message: String) : WinterException(message) 13 | 14 | /** 15 | * Exception that is thrown when an error occurs during dependency resolution. 16 | */ 17 | class DependencyResolutionException( 18 | val key: TypeKey<*>, 19 | message: String, 20 | cause: Throwable? = null 21 | ) : WinterException(message, cause) 22 | 23 | /** 24 | * Exception that is thrown when a cyclic dependency was detected. 25 | */ 26 | class CyclicDependencyException( 27 | val key: TypeKey<*>, 28 | message: String, 29 | cause: Throwable? = null 30 | ) : WinterException(message, cause) 31 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/viewmodel/ComponentBuilderExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.ViewModelStore 6 | import io.jentz.winter.Component 7 | import io.jentz.winter.GFactory 8 | 9 | /** 10 | * Register a [ViewModel] factory on this component. 11 | * 12 | * Be careful, view models outlive activities and therefore shouldn't depend on anything that has 13 | * the same lifetime as an activity. Add this to your applications 14 | * presentation scope or if you do not maintain one add it to your activity scope and be extra 15 | * careful with your dependencies. 16 | */ 17 | inline fun Component.Builder.viewModel(noinline factory: GFactory) { 18 | 19 | prototype { 20 | ViewModelProvider( 21 | instance(), 22 | WinterViewModelFactory(this, factory) 23 | ).get(R::class.java) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/WithInjectedProviderAndLazyFields_WinterMembersInjector.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.inject.MembersInjector 5 | import javax.annotation.Generated 6 | import kotlin.Any 7 | import kotlin.String 8 | import kotlin.collections.List 9 | 10 | @Generated( 11 | value = ["io.jentz.winter.compiler.WinterProcessor"], 12 | date = "2019-02-10T14:52Z" 13 | ) 14 | class WithInjectedProviderAndLazyFields_WinterMembersInjector : 15 | MembersInjector { 16 | override fun inject(graph: Graph, target: WithInjectedProviderAndLazyFields) { 17 | target.field0 = graph.instanceOrNull() 18 | target.field1 = { graph.instanceOrNull>(qualifier = "stringList", generics = true) 19 | } 20 | target.field2 = graph.providerOrNull>(qualifier = "stringList", generics = true) 21 | target.field3 = lazy { graph.instanceOrNull>(qualifier = "stringList", generics = 22 | true) } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /winter-compiler/src/test/java/io/jentz/winter/compiler/ComponentTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.google.testing.compile.Compiler 4 | import com.google.testing.compile.JavaFileObjects 5 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 6 | import org.junit.jupiter.api.Test 7 | 8 | @KotlinPoetMetadataPreview 9 | class ComponentTest : BaseProcessorTest() { 10 | 11 | @Test 12 | fun `should generate component in configured package`() { 13 | Compiler.javac() 14 | .withProcessors(WinterProcessor()) 15 | .withOptions("-A$OPTION_GENERATED_COMPONENT_PACKAGE=test") 16 | .compile( 17 | JavaFileObjects.forResource("InjectConstructorAnnotation.java"), 18 | JavaFileObjects.forResource("NamedSingletonInjectConstructor.java"), 19 | JavaFileObjects.forResource("WithCustomScope.java"), 20 | JavaFileObjects.forResource("PrototypeAnnotation.java") 21 | ) 22 | 23 | generatesSource("generatedComponent") 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/Logger.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import javax.annotation.processing.Messager 4 | import javax.lang.model.element.Element 5 | import javax.tools.Diagnostic 6 | 7 | class Logger(private val messager: Messager) { 8 | 9 | fun info(message: String) { 10 | messager.printMessage(Diagnostic.Kind.NOTE, message) 11 | } 12 | 13 | fun info(element: Element, message: String) { 14 | messager.printMessage(Diagnostic.Kind.NOTE, message, element) 15 | } 16 | 17 | fun warn(message: String) { 18 | messager.printMessage(Diagnostic.Kind.WARNING, message) 19 | } 20 | 21 | fun error(element: Element, throwable: Throwable) { 22 | error(element, throwable.message ?: "Unknown error") 23 | } 24 | 25 | fun error(element: Element, message: String) { 26 | messager.printMessage(Diagnostic.Kind.ERROR, message, element) 27 | } 28 | 29 | fun error(message: String) { 30 | messager.printMessage(Diagnostic.Kind.ERROR, message) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /winter-compiler/src/test/resources/InjectConstructorWithProviderAndLazyArguments_WinterFactory.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.inject.Factory 7 | import javax.annotation.Generated 8 | import kotlin.Boolean 9 | import kotlin.String 10 | import kotlin.collections.List 11 | 12 | @Generated( 13 | value = ["io.jentz.winter.compiler.WinterProcessor"], 14 | date = "2019-02-10T14:52Z" 15 | ) 16 | class InjectConstructorWithProviderAndLazyArguments_WinterFactory : 17 | Factory { 18 | override fun invoke(graph: Graph): InjectConstructorWithProviderAndLazyArguments = 19 | InjectConstructorWithProviderAndLazyArguments( 20 | { graph.instance(qualifier = "string") }, 21 | lazy { graph.instanceOrNull>(qualifier = "stringList", generics = true) } 22 | ) 23 | 24 | override fun register(builder: Component.Builder, override: Boolean): 25 | TypeKey = builder.prototype(override = 26 | override, factory = this) 27 | } 28 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/BoundTestService.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.BoundService 4 | import io.jentz.winter.Scope 5 | import io.jentz.winter.TypeKey 6 | import io.jentz.winter.typeKey 7 | 8 | internal class BoundTestService( 9 | private val evaluator: ServiceEvaluator, 10 | override val key: TypeKey = typeKey(), 11 | var dependency: BoundService? = null, 12 | var throwOnNewInstance: (() -> Throwable)? = null, 13 | var instance: () -> String = { "" } 14 | ) : BoundService { 15 | 16 | var postConstructCalled = mutableListOf() 17 | 18 | override val scope: Scope get() = Scope.Prototype 19 | 20 | override fun instance(): String = throw Error() 21 | 22 | override fun newInstance(): String { 23 | dependency?.let { evaluator.evaluate(it) } 24 | throwOnNewInstance?.let { throw it() } 25 | return this.instance.invoke() 26 | } 27 | 28 | override fun onPostConstruct(instance: String) { 29 | postConstructCalled.add(instance) 30 | } 31 | 32 | override fun onClose() { 33 | throw Error() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /winter-compiler-test/src/test/java/io/jentz/winter/compiler/test/KotlinPropertiesTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler.test 2 | 3 | import io.jentz.winter.component 4 | import io.kotlintest.shouldBe 5 | import org.junit.jupiter.api.Test 6 | 7 | class KotlinPropertiesTest { 8 | 9 | @Test 10 | fun `should inject Kotlin properties`() { 11 | val graph = component { 12 | constant("foo") 13 | constant(21) 14 | constant(42, qualifier = "someInt") 15 | constant(listOf("a", "b", "c"), generics = true) 16 | generated() 17 | }.createGraph() 18 | 19 | val instance: KotlinProperties = graph.instance() 20 | 21 | instance.constructorInjectedString.shouldBe("foo") 22 | instance.constructorInjectedPrimitive.shouldBe(42) 23 | 24 | instance.primitiveProperty.shouldBe(21) 25 | instance.primitiveSetter.shouldBe(21) 26 | 27 | instance.namedPrimitiveProperty.shouldBe(42) 28 | instance.namedPrimitiveSetter.shouldBe(42) 29 | 30 | instance.someList.shouldBe(listOf("a", "b", "c")) 31 | 32 | instance.stringProvider().shouldBe("foo") 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /winter-androidx/proguard-consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Do not obfuscate classes with Inject annotated constructors 2 | -keepclasseswithmembernames class * { @javax.inject.Inject (...); } 3 | 4 | # Do not obfuscate names of classes with InjectConstructor annotation 5 | -keepnames @io.jentz.winter.inject.InjectConstructor class * 6 | 7 | # Do not obfuscate classes with Injected Fields 8 | -keepclasseswithmembernames class * { @javax.inject.Inject ; } 9 | 10 | # Do not obfuscate classes with Injected Methods 11 | -keepclasseswithmembernames class * { @javax.inject.Inject ; } 12 | 13 | # Do not obfuscate classes with inject delegates 14 | -keepclasseswithmembernames class * { io.jentz.winter.delegate.* *; } 15 | 16 | # Do not remove constructors, methods and fields annotated with Inject 17 | -keepclassmembers class * { 18 | @javax.inject.Inject (...); 19 | @javax.inject.Inject (); 20 | @javax.inject.Inject ; 21 | public (...); 22 | io.jentz.winter.delegate.* *; 23 | } 24 | 25 | # Keep Winter generated factories and members injectors 26 | -keep class * implements io.jentz.winter.inject.Factory { *; } 27 | -keep class * implements io.jentz.winter.inject.MembersInjector { *; } -------------------------------------------------------------------------------- /winter-testing/src/main/kotlin/io/jentz/winter/testing/PropertyService.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.* 4 | import kotlin.reflect.KProperty1 5 | import kotlin.reflect.jvm.isAccessible 6 | 7 | /** 8 | * Service that gets its instances from a Kotlin property. 9 | */ 10 | internal class PropertyService( 11 | override val key: TypeKey, 12 | private val source: Any, 13 | private val property: KProperty1 14 | ) : UnboundService, BoundService { 15 | 16 | override val scope: Scope get() = Scope.Prototype 17 | 18 | override val requiresLifecycleCallbacks: Boolean get() = false 19 | 20 | init { 21 | property.isAccessible = true 22 | } 23 | 24 | override fun bind(graph: Graph): BoundService = this 25 | 26 | override fun instance(): Any = property.get(source) ?: throw WinterException( 27 | "Property `${source.javaClass.name}::${property.name} returned null`." 28 | ) 29 | 30 | override fun newInstance(): Any { 31 | throw IllegalStateException("BUG: Should never been called.") 32 | } 33 | 34 | override fun onPostConstruct(instance: Any) { 35 | } 36 | 37 | override fun onClose() { 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/evaluator/CyclicDependenciesCheckingDirectServiceEvaluator.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.BoundService 4 | import io.jentz.winter.TypeKey 5 | 6 | /** 7 | * Like [DirectServiceEvaluator] but it also checks for cyclic dependencies. 8 | * 9 | * This is used when no plugin is registered and no service requires lifecycle callbacks and cyclic 10 | * dependency checks are enabled. 11 | */ 12 | internal class CyclicDependenciesCheckingDirectServiceEvaluator : ServiceEvaluator { 13 | 14 | private var stack = mutableListOf>() 15 | 16 | override fun evaluate(service: BoundService): R { 17 | val key = service.key 18 | 19 | checkForCyclicDependencies(key, { stack.contains(key) }, { stack }) 20 | 21 | stack.push(key) 22 | 23 | return try { 24 | service.newInstance() 25 | } catch (t: Throwable) { 26 | handleException(key, t) 27 | } finally { 28 | stack.pop() 29 | } 30 | 31 | } 32 | 33 | private fun MutableList>.push(key: TypeKey<*>) = add(key) 34 | 35 | private fun MutableList>.pop() = removeAt(lastIndex) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/LifecycleAutoClose.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx 2 | 3 | import androidx.lifecycle.Lifecycle.Event 4 | import androidx.lifecycle.LifecycleEventObserver 5 | import androidx.lifecycle.LifecycleOwner 6 | 7 | /** 8 | * Abstract LifecycleObserver that calls [close] and unregisters itself from 9 | * [source][LifecycleOwner] once the [closeEvent] was emitted. 10 | * 11 | * @param closeEvent The event that will trigger [close] must be one of ON_PAUSE, ON_STOP or 12 | * ON_DESTROY. 13 | */ 14 | internal abstract class LifecycleAutoClose( 15 | private val closeEvent: Event 16 | ) : LifecycleEventObserver { 17 | 18 | init { 19 | require( 20 | closeEvent == Event.ON_PAUSE 21 | || closeEvent == Event.ON_STOP 22 | || closeEvent == Event.ON_DESTROY 23 | ) { "closeEvent must be ON_PAUSE, ON_STOP or ON_DESTROY" } 24 | } 25 | 26 | override fun onStateChanged(source: LifecycleOwner, event: Event) { 27 | if (event == closeEvent) { 28 | close() 29 | source.lifecycle.removeObserver(this) 30 | } 31 | } 32 | 33 | protected abstract fun close() 34 | 35 | } 36 | -------------------------------------------------------------------------------- /winter-junit5/src/test/kotlin/io/jentz/winter/junit5/WinterAllExtensionTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import io.jentz.winter.WinterApplication 4 | import io.kotlintest.matchers.boolean.shouldBeTrue 5 | import io.kotlintest.shouldBe 6 | import org.junit.jupiter.api.Test 7 | import org.junit.jupiter.api.TestInstance 8 | import org.junit.jupiter.api.extension.AfterAllCallback 9 | import org.junit.jupiter.api.extension.ExtendWith 10 | import org.junit.jupiter.api.extension.ExtensionContext 11 | import org.junit.jupiter.api.extension.RegisterExtension 12 | 13 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 14 | @ExtendWith(WinterAllExtensionTest.TestExtension::class) 15 | class WinterAllExtensionTest { 16 | 17 | private object TestApp : WinterApplication() 18 | 19 | class TestExtension : AfterAllCallback { 20 | override fun afterAll(context: ExtensionContext?) { 21 | TestApp.plugins.isEmpty().shouldBeTrue() 22 | } 23 | } 24 | 25 | @JvmField 26 | @RegisterExtension 27 | val extension = WinterAllExtension { 28 | application = TestApp 29 | } 30 | 31 | @Test 32 | fun `session plugin should be registered`() { 33 | TestApp.plugins.size.shouldBe(1) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/adapter/ApplicationGraphOnlyInjectionAdapterTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.adapter 2 | 3 | import io.jentz.winter.WinterApplication 4 | import io.kotlintest.matchers.types.shouldBeInstanceOf 5 | import io.kotlintest.matchers.types.shouldBeSameInstanceAs 6 | import io.kotlintest.shouldBe 7 | import org.junit.jupiter.api.BeforeEach 8 | import org.junit.jupiter.api.Test 9 | 10 | class ApplicationGraphOnlyInjectionAdapterTest { 11 | 12 | private val app = WinterApplication {} 13 | 14 | private val adapter = ApplicationGraphOnlyInjectionAdapter(app) 15 | 16 | @BeforeEach 17 | fun beforeEach() { 18 | app.closeGraphIfOpen() 19 | } 20 | 21 | @Test 22 | fun `#get should open root graph if not open`() { 23 | adapter.get(Any()).shouldBe(app.graph) 24 | } 25 | 26 | @Test 27 | fun `#get should return root graph for any argument`() { 28 | val graph = app.openGraph() 29 | repeat(2) { adapter.get(Any()).shouldBeSameInstanceAs(graph) } 30 | } 31 | 32 | @Test 33 | fun `#useApplicationGraphOnlyAdapter should register adapter`() { 34 | app.useApplicationGraphOnlyAdapter() 35 | app.injectionAdapter.shouldBeInstanceOf() 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /winter-testing/src/main/kotlin/io/jentz/winter/testing/ComponentBuilderExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.Component 4 | import kotlin.reflect.KProperty1 5 | import kotlin.reflect.full.declaredMemberProperties 6 | 7 | /** 8 | * Register a service that uses [property][KProperty1] as source for an instance. 9 | * 10 | * @param source The instance to retrieve the [property] value from. 11 | * @param property The [KProperty1] instance. 12 | * @param override If true this will override an existing service of this type. 13 | */ 14 | internal fun Component.Builder.property( 15 | source: Any, 16 | property: KProperty1, 17 | override: Boolean = false 18 | ) { 19 | register(PropertyService(property.typeKey, source, property), override) 20 | } 21 | 22 | /** 23 | * Register all properties that are annotated with Mock or Spy as a provider. 24 | * 25 | * @param source The source object to search for Mock and Spy properties. 26 | */ 27 | fun Component.Builder.bindAllMocks(source: Any) { 28 | source::class 29 | .declaredMemberProperties 30 | .filter { it.hasMockAnnotation() } 31 | .forEach { 32 | @Suppress("UNCHECKED_CAST") 33 | property(source, it as (KProperty1), true) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /winter-junit4/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | dependencies { 14 | implementation deps.kotlin.stdlib 15 | 16 | api project(':winter') 17 | api project(':winter-testing') 18 | api deps.test.junit4 19 | 20 | testImplementation deps.test.kotlintest 21 | } 22 | 23 | task javadocJar(type: Jar) { 24 | classifier = 'javadoc' 25 | from dokka 26 | } 27 | 28 | task sourcesJar(type: Jar) { 29 | classifier = 'sources' 30 | from sourceSets.main.allSource 31 | } 32 | 33 | artifacts { 34 | archives javadocJar, sourcesJar 35 | } 36 | 37 | dokka { 38 | outputFormat = 'html' 39 | outputDirectory = "$buildDir/javadoc" 40 | includes = ['dokka-packages.md'] 41 | 42 | linkMapping { 43 | dir = "src/main/kotlin" 44 | url = "https://github.com/beyama/winter/blob/master/winter-junit4/src/main/kotlin" 45 | suffix = "#L" 46 | } 47 | } 48 | 49 | detekt { 50 | config = files(rootProject.file('detekt-config.yml')) 51 | } 52 | 53 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 54 | -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/ProcessorConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import javax.annotation.processing.ProcessingEnvironment 5 | 6 | data class ProcessorConfiguration( 7 | val generatedComponentPackage: String?, 8 | val generatedAnnotation: ClassName? 9 | ) { 10 | 11 | companion object { 12 | 13 | private val generatedAnnotations = listOf( 14 | GENERATED_ANNOTATION_JDK9_INTERFACE_NAME, 15 | GENERATED_ANNOTATION_LEGACY_INTERFACE_NAME 16 | ) 17 | 18 | fun from(processingEnv: ProcessingEnvironment): ProcessorConfiguration { 19 | val options = processingEnv.options 20 | 21 | val generatedComponentPackage = options[OPTION_GENERATED_COMPONENT_PACKAGE] 22 | .takeUnless { it.isNullOrBlank() } 23 | 24 | // Android's API jar doesn't include a Generated annotation so we check the 25 | // availability here 26 | val generatedAnnotation = generatedAnnotations.find { 27 | processingEnv.elementUtils.getTypeElement(it.canonicalName) != null 28 | } 29 | 30 | return ProcessorConfiguration(generatedComponentPackage, generatedAnnotation) 31 | 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /winter-compiler/src/test/java/io/jentz/winter/compiler/TypeMappingTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 5 | import com.squareup.kotlinpoet.asClassName 6 | import io.kotlintest.shouldBe 7 | import org.junit.jupiter.api.Test 8 | 9 | class TypeMappingTest { 10 | 11 | @Test 12 | fun `should map Java classes to Kotlin classes`() { 13 | ClassName("java.lang", "String") 14 | .kotlinTypeName 15 | .shouldBe(String::class.asClassName()) 16 | } 17 | 18 | @Test 19 | fun `should map generic Java classes to Kotlin classes`() { 20 | val jvmString = ClassName("java.lang", "String") 21 | val jvmMap = ClassName("java.util", "Map") 22 | val jvmList = ClassName("java.util", "List") 23 | val kMap = Map::class.asClassName() 24 | val kList = List::class.asClassName() 25 | val kString = String::class.asClassName() 26 | 27 | jvmMap.parameterizedBy(jvmString, jvmString) 28 | .kotlinTypeName 29 | .shouldBe(kMap.parameterizedBy(kString, kString)) 30 | 31 | jvmList.parameterizedBy(jvmString) 32 | .kotlinTypeName 33 | .shouldBe(kList.parameterizedBy(kString)) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /winter-rxjava2/src/main/kotlin/io/jentz/winter/rxjava2/WinterDisposablePlugin.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.rxjava2 2 | 3 | import io.jentz.winter.* 4 | import io.jentz.winter.plugin.SimplePlugin 5 | import io.reactivex.disposables.CompositeDisposable 6 | import io.reactivex.disposables.Disposable 7 | 8 | /** 9 | * Winter plugin that adds a [CompositeDisposable] to every graph, adds all singleton scoped 10 | * instances which implement [Disposable] to it and disposes the [CompositeDisposable] when 11 | * the [Graph] gets closed. 12 | */ 13 | object WinterDisposablePlugin : SimplePlugin() { 14 | override fun graphInitializing(parentGraph: Graph?, builder: Component.Builder) { 15 | builder.constant(CompositeDisposable()) 16 | } 17 | 18 | override fun postConstruct(graph: Graph, scope: Scope, instance: Any) { 19 | if (scope == Scope.Singleton && instance is Disposable) { 20 | graph.instance().add(instance) 21 | } 22 | } 23 | 24 | override fun graphClose(graph: Graph) { 25 | graph.instance().dispose() 26 | } 27 | } 28 | 29 | fun WinterApplication.installDisposablePlugin() { 30 | plugins += WinterDisposablePlugin 31 | } 32 | 33 | fun WinterApplication.uninstallDisposablePlugin() { 34 | plugins -= WinterDisposablePlugin 35 | } 36 | -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/src/main/java/io/jentz/winter/androidx/viewmodel/savedstate/WinterSavedStateViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.viewmodel.savedstate 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.AbstractSavedStateViewModelFactory 5 | import androidx.lifecycle.SavedStateHandle 6 | import androidx.lifecycle.ViewModel 7 | import androidx.savedstate.SavedStateRegistryOwner 8 | import io.jentz.winter.GFactory 9 | import io.jentz.winter.Graph 10 | import java.util.* 11 | 12 | @PublishedApi 13 | internal class SavedStateHandleHolder { 14 | var handles = LinkedList() 15 | } 16 | 17 | @PublishedApi 18 | internal class WinterSavedStateViewModelFactory( 19 | private val graph: Graph, 20 | owner: SavedStateRegistryOwner, 21 | defaultArgs: Bundle?, 22 | private val factory: GFactory 23 | ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) { 24 | 25 | @Suppress("UNCHECKED_CAST") 26 | override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T { 27 | val holder: SavedStateHandleHolder = graph.instance() 28 | holder.handles.push(handle) 29 | return try { 30 | factory.invoke(graph) as T 31 | } finally { 32 | holder.handles.pop() 33 | } 34 | } 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/quotes/QuotesAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.quotes 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import io.jentz.winter.android.sample.R 7 | import io.jentz.winter.android.sample.model.Quote 8 | import io.jentz.winter.androidx.inject.ActivityScope 9 | import io.jentz.winter.inject.InjectConstructor 10 | 11 | @ActivityScope 12 | @InjectConstructor 13 | class QuotesAdapter( 14 | private val inflater: LayoutInflater 15 | ) : RecyclerView.Adapter() { 16 | 17 | class ViewHolder( 18 | val quoteItemView: QuoteItemView 19 | ) : RecyclerView.ViewHolder(quoteItemView) 20 | 21 | var list: List? = null 22 | set(value) { 23 | field = value 24 | notifyDataSetChanged() 25 | } 26 | 27 | override fun getItemCount(): Int = list?.count() ?: 0 28 | 29 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = 30 | ViewHolder(inflater.inflate(R.layout.quote_item_view, parent, false) as QuoteItemView) 31 | 32 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 33 | val quote = list?.get(position) ?: return 34 | holder.quoteItemView.render(quote) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /winter-compiler-test/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'kotlin-kapt' 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_1_8 7 | targetCompatibility = JavaVersion.VERSION_1_8 8 | 9 | test { 10 | useJUnitPlatform() 11 | 12 | dependsOn 'cleanTest' 13 | 14 | testLogging { 15 | events "skipped", "failed" 16 | 17 | exceptionFormat "full" 18 | showExceptions true 19 | showCauses true 20 | showStackTraces true 21 | } 22 | afterSuite { desc, result -> 23 | if (!desc.parent) { 24 | println "\nTest result: ${result.resultType}" 25 | println "Test summary: ${result.testCount} tests, " + 26 | "${result.successfulTestCount} succeeded, " + 27 | "${result.failedTestCount} failed, " + 28 | "${result.skippedTestCount} skipped" 29 | } 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation deps.kotlin.stdlib 35 | 36 | implementation project(':winter') 37 | implementation deps.kotlin.reflect 38 | 39 | kapt project(':winter-compiler') 40 | 41 | testImplementation project(':winter-junit5') 42 | testImplementation deps.test.jupiterApi 43 | testImplementation deps.test.kotlintest 44 | testImplementation deps.kotlin.reflect 45 | 46 | testRuntimeOnly deps.test.jupiterEngine 47 | } 48 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/DependencyGraphContextWrapper.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx 2 | 3 | import android.content.Context 4 | import android.content.ContextWrapper 5 | import android.view.LayoutInflater 6 | import io.jentz.winter.Graph 7 | 8 | /** 9 | * A [ContextWrapper] that holds a reference to a [Graph] and creates a clone of [LayoutInflater] 10 | * that is bound to this. 11 | * 12 | * This is useful if you need to provide a specific dependency graph to a view hierarchy other than 13 | * your Activity graph. 14 | */ 15 | class DependencyGraphContextWrapper(base: Context, val graph: Graph) : ContextWrapper(base) { 16 | 17 | companion object { 18 | /** 19 | * Use with [getSystemService] to retrieve the [Graph] instance. 20 | */ 21 | const val WINTER_GRAPH = "winter_graph" 22 | } 23 | 24 | private val layoutInflater by lazy { 25 | LayoutInflater.from(baseContext).cloneInContext(this) 26 | } 27 | 28 | // Seams like some vendors call this with null which then should return null 29 | @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") 30 | override fun getSystemService(name: String?): Any? = when (name) { 31 | Context.LAYOUT_INFLATER_SERVICE -> layoutInflater 32 | WINTER_GRAPH -> graph 33 | else -> super.getSystemService(name) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /winter-testing/src/test/kotlin/io/jentz/winter/testing/ComponentBuilderExtTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.graph 4 | import io.kotlintest.shouldBe 5 | import org.junit.jupiter.api.Test 6 | import org.mockito.Spy 7 | import javax.inject.Named 8 | 9 | @Suppress("unused") 10 | class ComponentBuilderExtTest { 11 | 12 | private val testField = 12 13 | 14 | @Mock 15 | @Named("mock field") 16 | private val mockField = "mock field" 17 | 18 | @field:Spy 19 | @field:Named("spy field") 20 | private val spyField = "spy field" 21 | 22 | @Mock 23 | @Named("mock property") 24 | val mockProperty = "mock property" 25 | 26 | @Test 27 | fun `#property should register property by KProperty1 instance`() { 28 | graph { 29 | val source = this@ComponentBuilderExtTest 30 | property(source, source::class.getDeclaredMemberProperty("testField")) 31 | }.instance().shouldBe(12) 32 | } 33 | 34 | @Test 35 | fun `#bindAllMocks should provide all Mock or Spy annotated fields`() { 36 | val graph = graph { bindAllMocks(this@ComponentBuilderExtTest) } 37 | graph.instance("mock field").shouldBe("mock field") 38 | graph.instance("spy field").shouldBe("spy field") 39 | graph.instance("mock property").shouldBe("mock property") 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/model/InjectorModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler.model 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.asClassName 5 | import com.squareup.kotlinpoet.metadata.ImmutableKmClass 6 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 7 | import javax.lang.model.element.Element 8 | import javax.lang.model.element.TypeElement 9 | 10 | @KotlinPoetMetadataPreview 11 | class InjectorModel( 12 | val originatingElement: TypeElement, 13 | superClassWithInjector: TypeElement?, 14 | private val kmClass: ImmutableKmClass? 15 | ) { 16 | 17 | val typeName = originatingElement.asClassName() 18 | 19 | val generatedClassName = generatedClassNameForClassName(typeName) 20 | 21 | val superclassInjectorClassName = superClassWithInjector 22 | ?.let { generatedClassNameForClassName(it.asClassName()) } 23 | 24 | private val _targets: MutableList = mutableListOf() 25 | 26 | val targets: List get() = _targets 27 | 28 | fun addTarget(fieldOrSetter: Element) { 29 | _targets += InjectTargetModel.forElement(fieldOrSetter, kmClass) 30 | } 31 | 32 | private fun generatedClassNameForClassName(name: ClassName) = ClassName( 33 | name.packageName, 34 | "${name.simpleNames.joinToString("_")}_WinterMembersInjector" 35 | ) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/LifecycleServiceEvaluatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import com.nhaarman.mockitokotlin2.mock 4 | import com.nhaarman.mockitokotlin2.verify 5 | import com.nhaarman.mockitokotlin2.whenever 6 | import io.jentz.winter.* 7 | import io.jentz.winter.plugin.Plugin 8 | import io.jentz.winter.plugin.Plugins 9 | import io.kotlintest.matchers.collections.shouldHaveSize 10 | import io.kotlintest.matchers.types.shouldBeSameInstanceAs 11 | import io.kotlintest.shouldBe 12 | import io.kotlintest.shouldThrow 13 | import org.eclipse.jgit.merge.StrategySimpleTwoWayInCore 14 | import org.junit.jupiter.api.Test 15 | 16 | class LifecycleServiceEvaluatorTest : AbstractCyclicServiceEvaluatorTest() { 17 | 18 | private val graph = emptyGraph() 19 | 20 | private val plugin: Plugin = mock() 21 | 22 | private val plugins = Plugins(plugin) 23 | 24 | override val evaluator = LifecycleServiceEvaluator(graph, plugins, true) 25 | 26 | @Test 27 | fun `should call service and plugin post-construct callbacks`() { 28 | val service = BoundTestService(evaluator) { "FOO" } 29 | evaluator 30 | .evaluate(service) 31 | .shouldBe("FOO") 32 | 33 | service.postConstructCalled.shouldHaveSize(1) 34 | service.postConstructCalled.first().shouldBe("FOO") 35 | 36 | verify(plugin).postConstruct(graph, Scope.Prototype, "FOO") 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/KotlinMetadataExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.metadata.ImmutableKmProperty 7 | import com.squareup.kotlinpoet.metadata.ImmutableKmType 8 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 9 | import kotlinx.metadata.Flag 10 | import kotlinx.metadata.KmClassifier 11 | import kotlinx.metadata.KmProperty 12 | import kotlinx.metadata.jvm.jvmInternalName 13 | 14 | @KotlinPoetMetadataPreview 15 | val ImmutableKmProperty.hasAccessibleSetter: Boolean 16 | get() = Flag.IS_PUBLIC(setterFlags) || Flag.IS_INTERNAL(setterFlags) 17 | 18 | val KmProperty.isNullable: Boolean get() = Flag.Type.IS_NULLABLE(returnType.flags) 19 | 20 | @KotlinPoetMetadataPreview 21 | fun ImmutableKmType.asTypeName(): TypeName { 22 | val tokens = (classifier as KmClassifier.Class).name.jvmInternalName.split("/") 23 | val packageParts = tokens.dropLast(1) 24 | val classParts = tokens.last().split("$") 25 | val className = ClassName(packageParts.joinToString("."), *classParts.toTypedArray()) 26 | if (arguments.isNotEmpty()) { 27 | val args = arguments.mapNotNull { it.type?.asTypeName() } 28 | return className.parameterizedBy(args) 29 | } 30 | return className 31 | } 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | dist: trusty 3 | jdk: oraclejdk8 4 | 5 | branches: 6 | except: 7 | - gh-pages 8 | 9 | android: 10 | components: 11 | - tools 12 | - platform-tools 13 | - extra-google-m2repository 14 | - extra-android-m2repository 15 | - sys-img-armeabi-v7a-android-17 16 | licenses: 17 | - 'android-sdk-preview-license-.+' 18 | - 'android-sdk-license-.+' 19 | - 'google-gdk-license-.+' 20 | 21 | before_install: 22 | - touch $HOME/.android/repositories.cfg 23 | - yes | sdkmanager "platforms;android-28" 24 | - yes | sdkmanager "build-tools;28.0.2" 25 | - yes | sdkmanager tools 26 | # Install the system image 27 | - sdkmanager "system-images;android-18;default;armeabi-v7a" 28 | # Create and start emulator for the script. Meant to race the install task. 29 | - echo no | avdmanager create avd --force -n test -k "system-images;android-18;default;armeabi-v7a" 30 | - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window & 31 | 32 | before_script: 33 | - android-wait-for-emulator 34 | - adb shell input keyevent 82 35 | - chmod +x gradlew 36 | 37 | script: ./gradlew detekt test connectedDebugAndroidTest --stacktrace 38 | 39 | before_cache: 40 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 41 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 42 | 43 | cache: 44 | directories: 45 | - $HOME/.gradle/caches/ 46 | - $HOME/.gradle/wrapper/ 47 | - $HOME/.android/build-cache 48 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/plugin/Plugins.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.plugin 2 | 3 | /** 4 | * Container for [Winter plugins][Plugin]. 5 | */ 6 | class Plugins private constructor( 7 | private val list: List 8 | ): Iterable { 9 | 10 | constructor() : this(emptyList()) 11 | 12 | constructor(vararg plugins: Plugin) : this(plugins.toList()) 13 | 14 | /** 15 | * The number of registered list. 16 | */ 17 | val size: Int get() = list.size 18 | 19 | /** 20 | * Returns true if the plugin is already registered. 21 | * 22 | * @param plugin The plugin to check for. 23 | * @return true if the registry contains the plugin 24 | */ 25 | fun contains(plugin: Plugin): Boolean = list.contains(plugin) 26 | 27 | /** 28 | * Returns true if the registry contains no plugin. 29 | */ 30 | fun isEmpty(): Boolean = list.isEmpty() 31 | 32 | /** 33 | * Returns true if the registry contains list. 34 | */ 35 | fun isNotEmpty(): Boolean = list.isNotEmpty() 36 | 37 | operator fun plus(plugin: Plugin): Plugins = 38 | if (contains(plugin)) this else Plugins(list + plugin) 39 | 40 | operator fun minus(plugin: Plugin): Plugins = 41 | if (contains(plugin)) Plugins(list - plugin) else this 42 | 43 | override fun iterator(): Iterator = list.iterator() 44 | 45 | companion object { 46 | val EMPTY = Plugins() 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /winter-androidx-fragment/src/main/java/io/jentz/winter/androidx/fragment/exportAndroidTypes.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.fragment 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.activity.OnBackPressedDispatcherOwner 5 | import androidx.fragment.app.FragmentActivity 6 | import androidx.fragment.app.FragmentManager 7 | import androidx.savedstate.SavedStateRegistryOwner 8 | import io.jentz.winter.Component 9 | 10 | internal fun exportAndroidTypes( 11 | instance: Any, 12 | enableWinterFragmentFactory: Boolean, 13 | builder: Component.Builder 14 | ) { 15 | with(builder) { 16 | if (instance is SavedStateRegistryOwner) { 17 | constant(instance) 18 | constant(instance.savedStateRegistry) 19 | } 20 | 21 | if (instance is OnBackPressedDispatcherOwner) { 22 | constant(instance) 23 | constant(instance.onBackPressedDispatcher) 24 | } 25 | 26 | if (instance is ComponentActivity) { 27 | constant(instance) 28 | } 29 | 30 | if (instance is FragmentActivity) { 31 | constant(instance) 32 | constant(instance.supportFragmentManager) 33 | 34 | if (enableWinterFragmentFactory) { 35 | eagerSingleton( 36 | onPostConstruct = { instance().fragmentFactory = it } 37 | ) { WinterFragmentFactory(this) } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /winter-junit5/src/test/kotlin/io/jentz/winter/junit5/WinterEachExtensionTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import io.jentz.winter.WinterApplication 4 | import io.kotlintest.matchers.boolean.shouldBeTrue 5 | import io.kotlintest.shouldBe 6 | import org.junit.jupiter.api.BeforeEach 7 | import org.junit.jupiter.api.Test 8 | import org.junit.jupiter.api.extension.AfterEachCallback 9 | import org.junit.jupiter.api.extension.Extension 10 | import org.junit.jupiter.api.extension.RegisterExtension 11 | 12 | 13 | class WinterEachExtensionTest { 14 | 15 | companion object { 16 | 17 | val app = WinterApplication { 18 | constant(42) 19 | } 20 | 21 | @JvmField 22 | @RegisterExtension 23 | // static extensions are registered before non static so after each is called the last. 24 | val testExtension: Extension = AfterEachCallback { 25 | app.plugins.isEmpty().shouldBeTrue() 26 | } 27 | 28 | } 29 | 30 | @JvmField 31 | @RegisterExtension 32 | val winterExtension = WinterEachExtension { 33 | application = app 34 | } 35 | 36 | @BeforeEach 37 | fun beforeEach() { 38 | app.createGraph() 39 | } 40 | 41 | @Test 42 | fun `session plugin should be registered`() { 43 | app.plugins.size.shouldBe(1) 44 | } 45 | 46 | @Test 47 | fun `should resolve parameters`(@WInject theAnswer: Int) { 48 | theAnswer.shouldBe(42) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/DI.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.WinterApplication 6 | import javax.annotation.processing.ProcessingEnvironment 7 | 8 | @KotlinPoetMetadataPreview 9 | object DI : WinterApplication() { 10 | 11 | init { 12 | component { 13 | singleton { Logger(instance().messager) } 14 | 15 | prototype { instance().filer } 16 | 17 | prototype { instance().elementUtils } 18 | 19 | prototype { instance().typeUtils } 20 | 21 | prototype { ProcessorConfiguration.from(instance()) } 22 | 23 | prototype { TypeUtils(instance(), instance()) } 24 | 25 | prototype { Generator(instance(), instance(), instance()) } 26 | 27 | singleton { FilerSourceWriter(instance()) } 28 | } 29 | 30 | injectionAdapter = object : InjectionAdapter { 31 | override fun get(instance: Any): Graph? { 32 | if (instance is ProcessingEnvironment) { 33 | closeGraphIfOpen() // in testing 34 | return openGraph { constant(instance) } 35 | } 36 | return graphOrNull 37 | } 38 | } 39 | } 40 | 41 | 42 | 43 | 44 | } -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/plugin/Plugin.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.plugin 2 | 3 | import io.jentz.winter.Component 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.Scope 6 | 7 | /** 8 | * The interface for Winter plugins. 9 | */ 10 | interface Plugin { 11 | /** 12 | * This is called when a [Graph] is initializing and allows to manipulate (derive) the backing 13 | * [io.jentz.winter.Component]. 14 | * 15 | * @param parentGraph The parent graph of the new graph that is being initialized. 16 | * @param builder The [Component.Builder] for the new graph. 17 | */ 18 | fun graphInitializing(parentGraph: Graph?, builder: Component.Builder) 19 | 20 | /** 21 | * This is called when a [Graph] is initialized and before eager dependencies are resolved. 22 | * 23 | * @param graph The [Graph] instance. 24 | */ 25 | fun graphInitialized(graph: Graph) 26 | 27 | /** 28 | * This is called whenever a [Graph] is going to be closed. 29 | * 30 | * @param graph The [Graph] that is going to be closed. 31 | */ 32 | fun graphClose(graph: Graph) 33 | 34 | /** 35 | * This is called whenever a new instance was created. 36 | * 37 | * @param graph The [Graph] the instance was created in. 38 | * @param scope The [Scope] of the instance. 39 | * @param instance The instance that was created. 40 | */ 41 | fun postConstruct(graph: Graph, scope: Scope, instance: Any) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /winter-coroutines/src/test/kotlin/io/jentz/winter/coroutines/ComponentBuilderExtTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.coroutines 2 | 3 | import io.jentz.winter.graph 4 | import io.kotlintest.matchers.boolean.shouldBeFalse 5 | import io.kotlintest.matchers.boolean.shouldBeTrue 6 | import io.kotlintest.matchers.types.shouldBeInstanceOf 7 | import io.kotlintest.matchers.types.shouldBeSameInstanceAs 8 | import kotlinx.coroutines.CompletableJob 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Job 11 | import org.junit.jupiter.api.Test 12 | 13 | class ComponentBuilderExtTest { 14 | 15 | @Test 16 | fun `should add SupervisorJob to context if no job is provided`() { 17 | val scope: CoroutineScope = graph { coroutineScope() }.instance() 18 | scope.coroutineContext[Job].shouldBeInstanceOf() 19 | } 20 | 21 | @Test 22 | fun `should not add SupervisorJob to context if job is provided`() { 23 | val job = Job() 24 | val scope: CoroutineScope = graph { coroutineScope(job) }.instance() 25 | scope.coroutineContext[Job].shouldBeSameInstanceAs(job) 26 | } 27 | 28 | @Test 29 | fun `should cancel job when the graph gets closed`() { 30 | val graph = graph { coroutineScope() } 31 | val scope: CoroutineScope = graph.instance() 32 | scope.coroutineContext[Job]!!.isCancelled.shouldBeFalse() 33 | graph.close() 34 | scope.coroutineContext[Job]!!.isCancelled.shouldBeTrue() 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/assertions.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | import org.opentest4j.AssertionFailedError 4 | 5 | inline fun expectValueToChange(from: T, to: T, valueProvider: () -> T, block: () -> Unit) { 6 | val a = valueProvider() 7 | if (a != from) fail("Expected initial value to be ${formatValue(from)} but was ${formatValue(a)}") 8 | block() 9 | val b = valueProvider() 10 | if (b != to) fail("Expected change from ${formatValue(from)} to ${formatValue(to)} but was ${formatValue(b)}") 11 | } 12 | 13 | internal inline fun > Component.shouldContainServiceOfType(key: TypeKey<*>) { 14 | val service = this[key] 15 | ?: fail("Component was expected to contain service with key <$key> but doesn't") 16 | if (service !is S) fail("Service with key <$key> was expected to be <${S::class}> but was <${service.javaClass}>.") 17 | } 18 | 19 | internal fun Component.shouldContainService(key: TypeKey<*>) { 20 | if (!containsKey(key)) fail("Component was expected to contain service with key <$key> but doesn't") 21 | } 22 | 23 | internal fun Component.shouldNotContainService(key: TypeKey<*>) { 24 | if (containsKey(key)) fail("Component wasn't expected to contain service with key <$key> but does.") 25 | } 26 | 27 | fun fail(message: String): Nothing { 28 | throw AssertionFailedError(message) 29 | } 30 | 31 | @PublishedApi 32 | internal fun formatValue(any: Any?) = when (any) { 33 | is String -> "\"$any\"" 34 | else -> "<$any>" 35 | } -------------------------------------------------------------------------------- /winter-junit5/src/main/kotlin/io/jentz/winter/junit5/WinterAllExtension.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import io.jentz.winter.testing.WinterTestSession.Builder 4 | import org.junit.jupiter.api.extension.AfterAllCallback 5 | import org.junit.jupiter.api.extension.BeforeAllCallback 6 | import org.junit.jupiter.api.extension.ExtensionContext 7 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace 8 | 9 | /** 10 | * JUnit5 extension that starts a [io.jentz.winter.testing.WinterTestSession] before all tests 11 | * and stops the session after all tests. 12 | * 13 | * For more details see [io.jentz.winter.testing.WinterTestSession]. 14 | */ 15 | open class WinterAllExtension( 16 | block: Builder.() -> Unit 17 | ) : AbstractWinterExtension( 18 | Namespace.create("io.jentz.winter.all"), 19 | Builder().apply(block) 20 | ), BeforeAllCallback, AfterAllCallback { 21 | 22 | /** 23 | * Default constructor to use this with [org.junit.jupiter.api.extension.RegisterExtension]. 24 | * 25 | * The default configuration will operate on the application graph and will bind all `Mock` 26 | * annotated properties to it. 27 | * 28 | * This class is open and can be extended for other default configurations. 29 | */ 30 | constructor(): this({ bindAllMocks() }) 31 | 32 | final override fun beforeAll(context: ExtensionContext) { 33 | before(context) 34 | } 35 | 36 | final override fun afterAll(context: ExtensionContext) { 37 | after(context) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /winter-junit5/src/main/kotlin/io/jentz/winter/junit5/WinterEachExtension.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import io.jentz.winter.testing.WinterTestSession.Builder 4 | import org.junit.jupiter.api.extension.AfterEachCallback 5 | import org.junit.jupiter.api.extension.BeforeEachCallback 6 | import org.junit.jupiter.api.extension.ExtensionContext 7 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace 8 | 9 | /** 10 | * JUnit5 extension that starts a [io.jentz.winter.testing.WinterTestSession] before each test 11 | * and stops the session after each test. 12 | * 13 | * For more details see [io.jentz.winter.testing.WinterTestSession]. 14 | */ 15 | open class WinterEachExtension( 16 | block: Builder.() -> Unit 17 | ) : AbstractWinterExtension( 18 | Namespace.create("io.jentz.winter.each"), 19 | Builder().apply(block) 20 | ), BeforeEachCallback, AfterEachCallback { 21 | 22 | /** 23 | * Default constructor to use this with [org.junit.jupiter.api.extension.RegisterExtension]. 24 | * 25 | * The default configuration will operate on the application graph and will bind all `Mock` 26 | * annotated properties to it. 27 | * 28 | * This class is open and can be extended for other default configurations. 29 | */ 30 | constructor(): this({ bindAllMocks() }) 31 | 32 | final override fun beforeEach(context: ExtensionContext) { 33 | before(context) 34 | } 35 | 36 | final override fun afterEach(context: ExtensionContext) { 37 | after(context) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/model/QuoteRepository.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.model 2 | 3 | import io.jentz.winter.inject.ApplicationScope 4 | import io.jentz.winter.inject.InjectConstructor 5 | import io.reactivex.Single 6 | 7 | @ApplicationScope 8 | @InjectConstructor 9 | class QuoteRepository { 10 | 11 | companion object { 12 | val quotes: List = listOf( 13 | Quote( 14 | "Dr. Seuss", 15 | "Don't cry because it's over, smile because it happened." 16 | ), 17 | Quote( 18 | "Marilyn Monroe", 19 | "I'm selfish, impatient and a little insecure. I make mistakes, " + 20 | "I am out of control and at times hard to handle. But if you can't " + 21 | "handle me at my worst, then you sure as hell don't deserve me at my " + 22 | "best." 23 | ), 24 | Quote( 25 | "Oscar Wilde", 26 | "Be yourself; everyone else is already taken." 27 | ), 28 | Quote( 29 | "Albert Einstein", 30 | "Two things are infinite: the universe and human stupidity; " + 31 | "and I'm not sure about the universe." 32 | ) 33 | ) 34 | } 35 | 36 | fun getQuotes(): Single> = Single.just(quotes) 37 | 38 | } -------------------------------------------------------------------------------- /winter-testing/src/main/kotlin/io/jentz/winter/testing/GraphExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.WinterException 5 | import javax.inject.Inject 6 | import kotlin.reflect.KMutableProperty1 7 | import kotlin.reflect.full.declaredMemberProperties 8 | import kotlin.reflect.jvm.isAccessible 9 | import kotlin.reflect.jvm.javaField 10 | 11 | /** 12 | * Injects dependencies into all properties annotated with [Inject] by using reflection. 13 | */ 14 | fun Graph.injectWithReflection(target: Any) { 15 | target::class 16 | .declaredMemberProperties 17 | .filter { it.findAnnotationIncludingField() != null } 18 | .forEach { property -> 19 | property.isAccessible = true 20 | 21 | val instance = instanceOrNullByKey(property.typeKey) 22 | val field = property.javaField 23 | 24 | when { 25 | property is KMutableProperty1 -> { 26 | @Suppress("UNCHECKED_CAST") 27 | (property as KMutableProperty1).set(target, instance) 28 | } 29 | field != null -> { 30 | field.set(target, instance) 31 | } 32 | else -> { 33 | throw WinterException( 34 | "Can't set property `${target.javaClass.name}::${property.name}` " + 35 | "no setter and no backing field found." 36 | ) 37 | } 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/src/androidTest/java/io/jentz/winter/androidx/integration/test/DependencyGraphContextWrapperTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.integration.test 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import androidx.test.platform.app.InstrumentationRegistry 6 | import io.jentz.winter.androidx.DependencyGraphContextWrapper 7 | import io.jentz.winter.emptyGraph 8 | import io.kotlintest.matchers.types.shouldBeInstanceOf 9 | import io.kotlintest.matchers.types.shouldBeSameInstanceAs 10 | import io.kotlintest.shouldBe 11 | import org.junit.Test 12 | 13 | class DependencyGraphContextWrapperTest { 14 | 15 | private val graph = emptyGraph() 16 | 17 | private val context = InstrumentationRegistry.getInstrumentation().targetContext 18 | 19 | private val wrapper = DependencyGraphContextWrapper(context, graph) 20 | 21 | @Test 22 | fun getSystemService_should_return_cloned_layout_inflater() { 23 | wrapper.getSystemService(Context.LAYOUT_INFLATER_SERVICE).let { 24 | it.shouldBeInstanceOf() 25 | (it as LayoutInflater).context.shouldBeSameInstanceAs(wrapper) 26 | } 27 | } 28 | 29 | @Test 30 | fun getSystemService_called_with_null_should_return_null() { 31 | wrapper.getSystemService(null).shouldBe(null) 32 | } 33 | 34 | @Test 35 | fun getSystemService_called_with_graph_constant_should_return_graph() { 36 | wrapper.getSystemService(DependencyGraphContextWrapper.WINTER_GRAPH) 37 | .shouldBeSameInstanceAs(graph) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /deploy_website.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | extend FileUtils 5 | 6 | REPO ='git@github.com:beyama/winter.git' 7 | CNAME = "winter.jentz.io" 8 | TMP_DIR = 'tmp_clone' 9 | WORKING_DIR = File.dirname(File.expand_path(__FILE__)) 10 | TRY_RUN = !ARGV.empty? 11 | 12 | cd WORKING_DIR 13 | 14 | current_branch = `git rev-parse --abbrev-ref HEAD` 15 | puts "Generating website based on branch #{current_branch}" 16 | 17 | # checkout gh-pages branch into TMP_DIR 18 | rm_rf TMP_DIR 19 | 20 | if TRY_RUN 21 | mkdir TMP_DIR 22 | else 23 | `git clone #{REPO} #{TMP_DIR}` 24 | 25 | cd TMP_DIR 26 | 27 | `git checkout -t origin/gh-pages` 28 | 29 | # Remove old site 30 | rm_rf '*' 31 | 32 | cd WORKING_DIR 33 | end 34 | 35 | # write CNAME file to gh-pages 36 | File.open(File.join(TMP_DIR, "CNAME"), "w") { |f| f.write(CNAME) } 37 | 38 | # build Javadocs 39 | `./gradlew dokka` 40 | 41 | # copy Javadocs 42 | Dir["*/build/javadoc"].each do |dir| 43 | cp_r dir, TMP_DIR 44 | end 45 | 46 | # build asciidoc page 47 | latest_version = `git describe master --abbrev=0 --tags`.strip 48 | `asciidoctor --attribute winterVersion=#{latest_version} --destination-dir #{TMP_DIR} doc/index.adoc` 49 | 50 | if TRY_RUN 51 | if RUBY_PLATFORM =~ /darwin/ 52 | `open #{File.join(TMP_DIR, "index.html")}` 53 | end 54 | 55 | exit 0 56 | end 57 | 58 | cd TMP_DIR 59 | 60 | # Stage all files in git and create a commit 61 | `git add .` 62 | `git add -u` 63 | `git commit -m "Website at $(date)"` 64 | 65 | # Push the new files up to GitHub 66 | `git push origin gh-pages` 67 | 68 | # Clean up 69 | cd WORKING_DIR 70 | rm_rf TMP_DIR 71 | -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/src/main/java/io/jentz/winter/androidx/viewmodel/savedstate/ComponentBuilderExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.viewmodel.savedstate 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.ViewModelStore 7 | import io.jentz.winter.Component 8 | import io.jentz.winter.GFactory 9 | import io.jentz.winter.typeKey 10 | 11 | /** 12 | * Register a [ViewModel] factory on this component that requires a 13 | * [androidx.lifecycle.SavedStateHandle]. 14 | * 15 | * Be careful, view models outlive activities and therefore shouldn't depend on anything that has 16 | * the same lifetime as an activity. 17 | * Since this needs a [androidx.savedstate.SavedStateRegistryOwner] to create the [ViewModel] 18 | * instance you have to declare those types of view models on your activity scope. Be extra careful 19 | * to only use dependencies that are part of you application or presentation scope. 20 | */ 21 | inline fun Component.Builder.savedStateViewModel( 22 | override: Boolean = false, 23 | defaultArgs: Bundle? = null, 24 | noinline factory: GFactory 25 | ) { 26 | 27 | if (!containsKey(typeKey())) { 28 | 29 | singleton { SavedStateHandleHolder() } 30 | 31 | prototype { instance().handles.last } 32 | 33 | } 34 | 35 | prototype(override = override) { 36 | ViewModelProvider( 37 | instance(), 38 | WinterSavedStateViewModelFactory(this, instance(), defaultArgs, factory) 39 | ).get(R::class.java) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/quotes/QuotesActivity.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.quotes 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.core.view.isVisible 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import io.jentz.winter.Winter 8 | import io.jentz.winter.android.sample.R 9 | import io.jentz.winter.android.sample.viewmodel.ViewModel 10 | import io.reactivex.android.schedulers.AndroidSchedulers 11 | import io.reactivex.disposables.Disposable 12 | import kotlinx.android.synthetic.main.activity_quotes.* 13 | import javax.inject.Inject 14 | 15 | class QuotesActivity : AppCompatActivity() { 16 | 17 | @Inject lateinit var viewModel: ViewModel 18 | @Inject lateinit var adapter: QuotesAdapter 19 | 20 | private var disposable: Disposable? = null 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | Winter.inject(this) 24 | 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.activity_quotes) 27 | 28 | listView.adapter = adapter 29 | listView.layoutManager = LinearLayoutManager(this) 30 | } 31 | 32 | override fun onResume() { 33 | super.onResume() 34 | disposable = viewModel.toFlowable() 35 | .observeOn(AndroidSchedulers.mainThread()) 36 | .subscribe(this::render) 37 | } 38 | 39 | override fun onPause() { 40 | super.onPause() 41 | disposable?.dispose() 42 | } 43 | 44 | private fun render(viewState: QuotesViewState) { 45 | progressIndicatorView.isVisible = viewState.isLoading 46 | adapter.list = viewState.quotes 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /winter-java/src/test/java/io/jentz/winter/java/JWinterTest.java: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.java; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import io.jentz.winter.Graph; 8 | 9 | import static io.jentz.winter.java.JWinter.instance; 10 | import static io.jentz.winter.java.JWinter.instanceOrNull; 11 | import static io.jentz.winter.java.JWinter.provider; 12 | import static io.jentz.winter.java.JWinter.providerOrNull; 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertNull; 15 | 16 | @SuppressWarnings("ConstantConditions") 17 | class JWinterTest { 18 | 19 | private Graph graph = Di.getTestComponent().createGraph(); 20 | 21 | @Test 22 | void testInstance() { 23 | assertEquals(instance(graph, String.class), "prototype"); 24 | assertEquals(instance(graph, String.class, "a"), "prototype a"); 25 | } 26 | 27 | @Test 28 | void testInstanceOrNull() { 29 | assertNull(instanceOrNull(graph, List.class)); 30 | assertEquals(instanceOrNull(graph, String.class), "prototype"); 31 | assertEquals(instanceOrNull(graph, String.class, "a"), "prototype a"); 32 | } 33 | 34 | @Test 35 | void testProvider() { 36 | assertEquals(provider(graph, String.class).invoke(), "prototype"); 37 | assertEquals(provider(graph, String.class, "a").invoke(), "prototype a"); 38 | } 39 | 40 | @Test 41 | void testProviderOrNull() { 42 | assertNull(providerOrNull(graph, List.class)); 43 | assertEquals(providerOrNull(graph, String.class).invoke(), "prototype"); 44 | assertEquals(providerOrNull(graph, String.class, "a").invoke(), "prototype a"); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /winter-androidx-integration-test/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | android { 5 | compileSdkVersion versions.compileSdk 6 | buildToolsVersion versions.androidTools 7 | 8 | defaultConfig { 9 | applicationId "io.jentz.winter.androidx.integration.test" 10 | minSdkVersion versions.minSdk 11 | targetSdkVersion versions.compileSdk 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_1_8 20 | targetCompatibility JavaVersion.VERSION_1_8 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | } 31 | 32 | dependencies { 33 | implementation project(":winter") 34 | implementation project(":winter-androidx") 35 | implementation project(":winter-androidx-fragment") 36 | implementation project(':winter-androidx-viewmodel-savedstate') 37 | 38 | implementation deps.kotlin.stdlib 39 | implementation deps.androidx.fragment 40 | 41 | androidTestImplementation project(":winter-junit4") 42 | androidTestImplementation deps.test.kotlintest 43 | androidTestImplementation deps.androidx.test.runner 44 | androidTestImplementation deps.androidx.test.rules 45 | androidTestImplementation deps.androidx.test.junit 46 | androidTestImplementation deps.androidx.test.espressoCore 47 | androidTestImplementation deps.kotlin.reflect 48 | 49 | } 50 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/IntegrationTestApp.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.view.LayoutInflater 6 | import io.jentz.winter.Winter 7 | import io.jentz.winter.android.sample.quotes.QuotesViewModel 8 | import io.jentz.winter.android.sample.quotes.QuotesViewState 9 | import io.jentz.winter.android.sample.viewmodel.ViewModel 10 | import io.jentz.winter.androidx.inject.ActivityScope 11 | import io.jentz.winter.androidx.inject.PresentationScope 12 | import io.jentz.winter.androidx.useAndroidPresentationScopeAdapter 13 | import io.jentz.winter.inject.ApplicationScope 14 | import io.jentz.winter.rxjava2.installDisposablePlugin 15 | import io.jentz.winter.typeKey 16 | 17 | class IntegrationTestApp : Application() { 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | Winter.component(ApplicationScope::class) { 23 | include(generatedComponent.subcomponent(ApplicationScope::class)) 24 | 25 | subcomponent(PresentationScope::class) { 26 | include(generatedComponent.subcomponent(PresentationScope::class)) 27 | 28 | alias(typeKey(), typeKey>(generics = true)) 29 | 30 | subcomponent(ActivityScope::class) { 31 | include(generatedComponent.subcomponent(ActivityScope::class)) 32 | 33 | prototype { instance().layoutInflater } 34 | } 35 | } 36 | } 37 | Winter.installDisposablePlugin() 38 | Winter.useAndroidPresentationScopeAdapter() 39 | Winter.inject(this) 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /winter-java/src/main/kotlin/io/jentz/winter/java/JWinter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.java 2 | 3 | import io.jentz.winter.ClassTypeKey 4 | import io.jentz.winter.Graph 5 | import io.jentz.winter.Provider 6 | import io.jentz.winter.TypeKey 7 | 8 | object JWinter { 9 | 10 | /** 11 | * Creates a [TypeKey] with argument type Unit and return type [R] from a Java class. 12 | * 13 | * @param type The return type. 14 | * @param qualifier The optional qualifier. 15 | * @return The type key. 16 | */ 17 | @JvmStatic 18 | @JvmOverloads 19 | fun key( 20 | type: Class, 21 | qualifier: Any? = null 22 | ): TypeKey = ClassTypeKey(type, qualifier) 23 | 24 | /** 25 | * @see Graph.instance 26 | */ 27 | @JvmStatic 28 | @JvmOverloads 29 | fun instance(graph: Graph, type: Class, qualifier: Any? = null): R = 30 | graph.instanceByKey(key(type, qualifier)) 31 | 32 | /** 33 | * @see Graph.instanceOrNull 34 | */ 35 | @JvmStatic 36 | @JvmOverloads 37 | fun instanceOrNull(graph: Graph, type: Class, qualifier: Any? = null): R? = 38 | graph.instanceOrNullByKey(key(type, qualifier)) 39 | 40 | /** 41 | * @see Graph.provider 42 | */ 43 | @JvmStatic 44 | @JvmOverloads 45 | fun provider(graph: Graph, type: Class, qualifier: Any? = null): Provider = 46 | graph.providerByKey(key(type, qualifier)) 47 | 48 | /** 49 | * @see Graph.providerOrNull 50 | */ 51 | @JvmStatic 52 | @JvmOverloads 53 | fun providerOrNull( 54 | graph: Graph, 55 | type: Class, 56 | qualifier: Any? = null 57 | ): Provider? = graph.providerOrNullByKey(key(type, qualifier)) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /winter-testing/src/test/kotlin/io/jentz/winter/testing/PropertyServiceTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.WinterException 4 | import io.jentz.winter.typeKey 5 | import io.kotlintest.shouldBe 6 | import io.kotlintest.shouldThrow 7 | import org.junit.jupiter.api.Test 8 | 9 | class PropertyServiceTest { 10 | 11 | private class PublicNonFinalField { 12 | lateinit var field: String 13 | } 14 | 15 | private class PrivateFinalField { 16 | private val field = 42 17 | } 18 | 19 | private class NullProperty { 20 | var property: String? = null 21 | } 22 | 23 | @Test 24 | fun `#instance should get value from field on every invocation`() { 25 | val instance = PublicNonFinalField() 26 | val service = PropertyService(typeKey(), instance, PublicNonFinalField::class.getDeclaredMemberProperty("field")) 27 | 28 | (1 until 5).map(Int::toString).forEach { int -> 29 | instance.field = int 30 | service.instance().shouldBe(int) 31 | } 32 | } 33 | 34 | @Test 35 | fun `#instance should get value from private final field`() { 36 | val instance = PrivateFinalField() 37 | val service = PropertyService(typeKey(), instance, PrivateFinalField::class.getDeclaredMemberProperty("field")) 38 | service.instance().shouldBe(42) 39 | } 40 | 41 | @Test 42 | fun `#instance should throw exception when field returns null`() { 43 | val instance = NullProperty() 44 | val service = PropertyService(typeKey(), instance, NullProperty::class.getDeclaredMemberProperty("property")) 45 | 46 | shouldThrow { 47 | service.instance() 48 | }.message.shouldBe("Property `io.jentz.winter.testing.PropertyServiceTest\$NullProperty::property returned null`.") 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /winter-testing/src/test/kotlin/io/jentz/winter/testing/GraphExtTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.graph 4 | import io.kotlintest.shouldBe 5 | import org.junit.jupiter.api.BeforeEach 6 | import org.junit.jupiter.api.Test 7 | import javax.inject.Inject 8 | import javax.inject.Named 9 | 10 | class GraphExtTest { 11 | 12 | @Inject 13 | @Named("one") 14 | private var privateNamedVar: Int? = null 15 | 16 | @field:[Inject Named("two")] 17 | private var privateNamedVarWithFieldAnnotation: Int? = null 18 | 19 | @set:Inject 20 | private var getterAndSetterWithBackingField: String? 21 | get() = backingField 22 | set(value) { 23 | backingField = value 24 | } 25 | 26 | private var backingField: String? = null 27 | 28 | @Inject 29 | lateinit var lateinitProperty: String 30 | 31 | private val graph = graph { 32 | prototype("one") { 1 } 33 | prototype("two") { 2 } 34 | prototype { "test" } 35 | } 36 | 37 | @BeforeEach 38 | fun beforeEach() { 39 | privateNamedVar = null 40 | privateNamedVarWithFieldAnnotation = null 41 | backingField = null 42 | lateinitProperty = "" 43 | } 44 | 45 | @Test 46 | fun `#injectWithReflection should inject into named private var`() { 47 | graph.injectWithReflection(this) 48 | privateNamedVar.shouldBe(1) 49 | privateNamedVarWithFieldAnnotation.shouldBe(2) 50 | } 51 | 52 | @Test 53 | fun `#injectWithReflection should inject into setter`() { 54 | graph.injectWithReflection(this) 55 | backingField.shouldBe("test") 56 | } 57 | 58 | @Test 59 | fun `#injectWithReflection should inject into lateinit property`() { 60 | graph.injectWithReflection(this) 61 | lateinitProperty.shouldBe("test") 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/java/io/jentz/winter/compiler/BaseProcessorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.FileSpec 4 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 5 | import io.jentz.winter.junit5.WinterEachExtension 6 | import org.junit.jupiter.api.AfterEach 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Assertions.assertNotNull 9 | import org.junit.jupiter.api.BeforeEach 10 | import org.junit.jupiter.api.extension.RegisterExtension 11 | 12 | @KotlinPoetMetadataPreview 13 | abstract class BaseProcessorTest { 14 | 15 | protected val writer = TestSourceWriter() 16 | 17 | @JvmField 18 | @RegisterExtension 19 | val extension = WinterEachExtension { 20 | application = DI 21 | extend { constant(writer, override = true) } 22 | } 23 | 24 | @BeforeEach 25 | fun beforeEach() { 26 | currentDateFixed = ISO8601_FORMAT.parse("2019-02-10T14:52Z") 27 | writer.sources.clear() 28 | } 29 | 30 | @AfterEach 31 | fun afterEach() { 32 | currentDateFixed = null 33 | } 34 | 35 | fun generatesSource(name: String) { 36 | val sourceFile = "$name.kt" 37 | val generatedSource = writer.sources[name] 38 | val expectedSource = javaClass.classLoader?.getResource(sourceFile)?.readText() 39 | 40 | assertNotNull(expectedSource, "Resource with name `$sourceFile` not found.") 41 | assertNotNull(generatedSource, "Expected `$sourceFile` to be generated but was not.") 42 | assertEquals(expectedSource, generatedSource) 43 | } 44 | 45 | class TestSourceWriter : SourceWriter { 46 | val sources: MutableMap = mutableMapOf() 47 | 48 | override fun write(fileSpec: FileSpec) { 49 | sources[fileSpec.name] = fileSpec.toString() 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /winter-junit4/src/main/kotlin/io/jentz/winter/junit4/WinterRule.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit4 2 | 3 | import io.jentz.winter.Graph 4 | import io.jentz.winter.testing.WinterTestSession 5 | import io.jentz.winter.testing.WinterTestSessionBlock 6 | import org.junit.rules.ExternalResource 7 | 8 | /** 9 | * JUnit4 rule that starts a [io.jentz.winter.testing.WinterTestSession] before each test 10 | * and stops the session after each test. 11 | * 12 | * For more details see [io.jentz.winter.testing.WinterTestSession]. 13 | */ 14 | open class WinterRule private constructor( 15 | private val testInstances: List, 16 | block: WinterTestSessionBlock 17 | ) : ExternalResource() { 18 | 19 | /** 20 | * Create an instance with [test] instance. 21 | * 22 | * @param test The test instance used by the [WinterTestSession]. 23 | * @param block The [WinterTestSession] builder block. 24 | */ 25 | constructor(test: Any, block: WinterTestSessionBlock) : this(listOf(test), block) 26 | 27 | /** 28 | * Creates an instance without test instance. 29 | * 30 | * @param block The [WinterTestSession] builder block. 31 | */ 32 | constructor(block: WinterTestSessionBlock) : this(emptyList(), block) 33 | 34 | private val testSessionBuilder = WinterTestSession.Builder().apply(block) 35 | 36 | private var testSession: WinterTestSession? = null 37 | 38 | private val requireSession: WinterTestSession get() = requireNotNull(testSession) { 39 | "Winter test session was not created." 40 | } 41 | 42 | val testGraph: Graph? get() = testSession?.testGraph 43 | 44 | val requireTestGraph: Graph get() = requireSession.requireTestGraph 45 | 46 | override fun before() { 47 | testSession = testSessionBuilder.build(testInstances) 48 | testSession?.start() 49 | } 50 | 51 | override fun after() { 52 | testSession?.stop() 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /android-sample-app/src/main/java/io/jentz/winter/android/sample/quotes/QuotesViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.android.sample.quotes 2 | 3 | import io.jentz.winter.android.sample.model.Quote 4 | import io.jentz.winter.android.sample.model.QuoteRepository 5 | import io.jentz.winter.android.sample.viewmodel.ViewModel 6 | import io.jentz.winter.androidx.inject.PresentationScope 7 | import io.jentz.winter.inject.InjectConstructor 8 | import io.reactivex.Flowable 9 | import io.reactivex.disposables.Disposable 10 | import java.util.concurrent.TimeUnit 11 | 12 | @InjectConstructor 13 | @PresentationScope 14 | class QuotesViewModel( 15 | repository: QuoteRepository 16 | ) : ViewModel, Disposable { 17 | 18 | private sealed class Result { 19 | object IsLoading : Result() 20 | data class Quotes(val quotes: List) : Result() 21 | } 22 | 23 | private var disposable: Disposable? = null 24 | 25 | private val viewStates: Flowable = repository 26 | .getQuotes() 27 | .delay(FAKE_NETWORK_DELAY, TimeUnit.MILLISECONDS) 28 | .map { Result.Quotes(it) } 29 | .toFlowable() 30 | .startWith(Result.IsLoading) 31 | .scan(QuotesViewState()) { state, result -> 32 | when (result) { 33 | is Result.IsLoading -> state.copy(isLoading = true) 34 | is Result.Quotes -> state.copy(isLoading = false, quotes = result.quotes) 35 | } 36 | } 37 | .replay(1) 38 | .autoConnect(1) { disposable = it } 39 | 40 | override fun toFlowable(): Flowable = viewStates 41 | 42 | override fun isDisposed(): Boolean = disposable?.isDisposed ?: false 43 | 44 | override fun dispose() { 45 | disposable?.dispose() 46 | } 47 | 48 | companion object { 49 | private const val FAKE_NETWORK_DELAY = 1000L 50 | } 51 | } -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/evaluator/checkForCyclicDependencies.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.* 4 | 5 | internal inline fun checkForCyclicDependencies( 6 | key: TypeKey<*>, 7 | check: () -> Boolean, 8 | buildPendingKeysList: () -> List> 9 | ) { 10 | 11 | if (!check()) return 12 | 13 | val pendingKeysList = buildPendingKeysList() 14 | 15 | val index = pendingKeysList.indexOf(key) 16 | when { 17 | index == 0 && pendingKeysList.size == 1 -> { 18 | throw CyclicDependencyException( 19 | key, 20 | "Cyclic dependency found: `$key` is directly dependent of itself.\n" + 21 | "Dependency chain: $key => $key") 22 | } 23 | index > -1 -> { 24 | val chain = pendingKeysList.listIterator(index) 25 | .asSequence() 26 | .joinToString(separator = " -> ", postfix = " => $key") 27 | 28 | throw CyclicDependencyException( 29 | key, 30 | "Cyclic dependency found: `$key` is dependent of itself.\n" + 31 | "Dependency chain: $chain") 32 | } 33 | } 34 | 35 | } 36 | 37 | internal fun handleException(key: TypeKey<*>, t: Throwable): Nothing { 38 | when (t) { 39 | is EntryNotFoundException -> { 40 | throw DependencyResolutionException( 41 | key, 42 | "Error while resolving dependency with key: $key " + 43 | "reason: could not find dependency with key ${t.key}", 44 | t 45 | ) 46 | } 47 | is WinterException -> { 48 | throw t 49 | } 50 | else -> { 51 | throw DependencyResolutionException( 52 | key, "Factory of dependency with key $key threw an exception on invocation.", t 53 | ) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /winter-androidx/src/test/kotlin/io/jentz/winter/androidx/LifecycleAutoCloseTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.Lifecycle.Event.* 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.LifecycleRegistry 7 | import io.kotlintest.matchers.boolean.shouldBeFalse 8 | import io.kotlintest.matchers.boolean.shouldBeTrue 9 | import io.kotlintest.shouldBe 10 | import io.kotlintest.shouldThrow 11 | import org.junit.Test 12 | 13 | class LifecycleAutoCloseTest : LifecycleOwner { 14 | 15 | private val registry = LifecycleRegistry(this) 16 | 17 | override fun getLifecycle(): Lifecycle = registry 18 | 19 | @Test 20 | fun `should throw an exception if the close event is a start event or ON_ANY`() { 21 | listOf(ON_CREATE, ON_START, ON_RESUME, ON_ANY).forEach { event -> 22 | shouldThrow { 23 | LifecycleAutoCloseImpl(event) 24 | } 25 | } 26 | } 27 | 28 | @Test 29 | fun `should call close if a close event was emitted`() { 30 | listOf(ON_PAUSE, ON_STOP, ON_DESTROY).forEach { event -> 31 | val observer = LifecycleAutoCloseImpl(event) 32 | observer.closeCalled.shouldBeFalse() 33 | observer.onStateChanged(this, event) 34 | observer.closeCalled.shouldBeTrue() 35 | } 36 | } 37 | 38 | @Test 39 | fun `should unregister itself if the close event was emitted`() { 40 | val observer = LifecycleAutoCloseImpl(ON_STOP) 41 | registry.addObserver(observer) 42 | observer.onStateChanged(this, ON_STOP) 43 | registry.observerCount.shouldBe(0) 44 | } 45 | 46 | private class LifecycleAutoCloseImpl( 47 | closeEvent: Lifecycle.Event 48 | ) : LifecycleAutoClose(closeEvent) { 49 | 50 | var closeCalled = false 51 | 52 | override fun close() { 53 | closeCalled = true 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /winter-java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | test { 14 | useJUnitPlatform() 15 | 16 | dependsOn 'cleanTest' 17 | 18 | testLogging { 19 | events "skipped", "failed" 20 | 21 | exceptionFormat "full" 22 | showExceptions true 23 | showCauses true 24 | showStackTraces true 25 | } 26 | afterSuite { desc, result -> 27 | if (!desc.parent) { 28 | println "\nTest result: ${result.resultType}" 29 | println "Test summary: ${result.testCount} tests, " + 30 | "${result.successfulTestCount} succeeded, " + 31 | "${result.failedTestCount} failed, " + 32 | "${result.skippedTestCount} skipped" 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation deps.kotlin.stdlib 39 | 40 | api project(':winter') 41 | 42 | testImplementation deps.test.jupiterApi 43 | testRuntimeOnly deps.test.jupiterEngine 44 | } 45 | 46 | task javadocJar(type: Jar) { 47 | classifier = 'javadoc' 48 | from dokka 49 | } 50 | 51 | task sourcesJar(type: Jar) { 52 | classifier = 'sources' 53 | from sourceSets.main.allSource 54 | } 55 | 56 | artifacts { 57 | archives javadocJar, sourcesJar 58 | } 59 | 60 | dokka { 61 | outputFormat = 'html' 62 | outputDirectory = "$buildDir/javadoc" 63 | includes = ['dokka-packages.md'] 64 | 65 | linkMapping { 66 | dir = "src/main/kotlin" 67 | url = "https://github.com/beyama/winter/blob/master/winter-java/src/main/kotlin" 68 | suffix = "#L" 69 | } 70 | } 71 | 72 | detekt { 73 | config = files(rootProject.file('detekt-config.yml')) 74 | } 75 | 76 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 77 | -------------------------------------------------------------------------------- /winter-testing/src/main/kotlin/io/jentz/winter/testing/ReflectExt.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.testing 2 | 3 | import io.jentz.winter.ClassTypeKey 4 | import io.jentz.winter.TypeKey 5 | import io.jentz.winter.WinterException 6 | import javax.inject.Named 7 | import kotlin.reflect.KClass 8 | import kotlin.reflect.KMutableProperty1 9 | import kotlin.reflect.KProperty1 10 | import kotlin.reflect.full.declaredMemberProperties 11 | import kotlin.reflect.full.findAnnotation 12 | import kotlin.reflect.jvm.javaField 13 | 14 | internal val KProperty1<*, *>.typeKey: TypeKey 15 | get() { 16 | val clazz = (returnType.classifier as? KClass<*>)?.javaObjectType 17 | ?: throw IllegalArgumentException("Can't get return type for property `$name`") 18 | return ClassTypeKey(clazz, namedAnnotationValue) 19 | } 20 | 21 | internal val KProperty1<*, *>.namedAnnotationValue: String? 22 | get() = findAnnotationIncludingField()?.value 23 | 24 | internal inline 25 | fun KProperty1<*, *>.findAnnotationIncludingField(): T? = 26 | findAnnotation() 27 | ?: (this as? KMutableProperty1)?.setter?.findAnnotation() 28 | ?: javaField?.getAnnotation(T::class.java) 29 | 30 | internal fun KClass<*>.getDeclaredMemberProperty(name: String): KProperty1 { 31 | val property = this.declaredMemberProperties.find { it.name == name } 32 | ?: throw WinterException("Property with name `$name` not found.") 33 | 34 | @Suppress("UNCHECKED_CAST") 35 | return property as KProperty1 36 | } 37 | 38 | internal fun KProperty1<*, *>.hasMockAnnotation(): Boolean { 39 | if (annotations.any { containsMockOrSpy(it.annotationClass.java.simpleName) }) { 40 | return true 41 | } 42 | val field = javaField ?: return false 43 | return field.annotations.any { containsMockOrSpy(it.annotationClass.java.name) } 44 | } 45 | 46 | private fun containsMockOrSpy(string: String): Boolean = 47 | string.contains("Mock") || string.contains("Spy") 48 | -------------------------------------------------------------------------------- /android-sample-app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /winter-junit5/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | test { 14 | useJUnitPlatform() 15 | 16 | dependsOn 'cleanTest' 17 | 18 | testLogging { 19 | events "skipped", "failed" 20 | 21 | exceptionFormat "full" 22 | showExceptions true 23 | showCauses true 24 | showStackTraces true 25 | } 26 | afterSuite { desc, result -> 27 | if (!desc.parent) { 28 | println "\nTest result: ${result.resultType}" 29 | println "Test summary: ${result.testCount} tests, " + 30 | "${result.successfulTestCount} succeeded, " + 31 | "${result.failedTestCount} failed, " + 32 | "${result.skippedTestCount} skipped" 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation deps.kotlin.stdlib 39 | 40 | api project(':winter') 41 | api project(':winter-testing') 42 | api deps.test.jupiterApi 43 | 44 | testImplementation deps.test.kotlintest 45 | 46 | testRuntimeOnly deps.test.jupiterEngine 47 | } 48 | 49 | task javadocJar(type: Jar) { 50 | classifier = 'javadoc' 51 | from dokka 52 | } 53 | 54 | task sourcesJar(type: Jar) { 55 | classifier = 'sources' 56 | from sourceSets.main.allSource 57 | } 58 | 59 | artifacts { 60 | archives javadocJar, sourcesJar 61 | } 62 | 63 | dokka { 64 | outputFormat = 'html' 65 | outputDirectory = "$buildDir/javadoc" 66 | includes = ['dokka-packages.md'] 67 | 68 | linkMapping { 69 | dir = "src/main/kotlin" 70 | url = "https://github.com/beyama/winter/blob/master/winter-junit5/src/main/kotlin" 71 | suffix = "#L" 72 | } 73 | } 74 | 75 | detekt { 76 | config = files(rootProject.file('detekt-config.yml')) 77 | } 78 | 79 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 80 | -------------------------------------------------------------------------------- /android-sample-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | kapt { 7 | arguments { 8 | arg("winterGeneratedComponentPackage", "io.jentz.winter.android.sample") 9 | } 10 | } 11 | 12 | android { 13 | compileSdkVersion versions.compileSdk 14 | 15 | defaultConfig { 16 | applicationId "io.jentz.winter.android.test" 17 | minSdkVersion versions.minSdk 18 | targetSdkVersion versions.compileSdk 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | 30 | buildTypes { 31 | debug { 32 | // minifyEnabled true 33 | } 34 | release { 35 | minifyEnabled false 36 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 37 | } 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'build/classes/kotlin/main' 42 | } 43 | 44 | } 45 | 46 | dependencies { 47 | implementation project(':winter-androidx') 48 | implementation project(':winter-rxjava2') 49 | 50 | kapt project(':winter-compiler') 51 | 52 | implementation deps.kotlin.stdlib 53 | 54 | implementation deps.androidx.compat 55 | implementation deps.androidx.coreKtx 56 | implementation deps.androidx.recyclerview 57 | 58 | implementation deps.rx.rxjava 59 | implementation deps.rx.rxandroid 60 | 61 | androidTestImplementation project(':winter-junit4') 62 | 63 | androidTestImplementation deps.test.kotlintest 64 | androidTestImplementation deps.androidx.test.runner 65 | androidTestImplementation deps.androidx.test.rules 66 | androidTestImplementation deps.androidx.test.junit 67 | androidTestImplementation deps.androidx.test.espressoCore 68 | androidTestImplementation deps.kotlin.reflect 69 | } 70 | -------------------------------------------------------------------------------- /winter-androidx-fragment/src/main/java/io/jentz/winter/androidx/fragment/WinterFragmentFactory.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx.fragment 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentFactory 5 | import io.jentz.winter.ClassTypeKey 6 | import io.jentz.winter.Graph 7 | 8 | /** 9 | * A [FragmentFactory] that resolves the fragment instances from the given [graph]. 10 | * This allows us to use constructor injection for our [fragments][Fragment]. 11 | * 12 | * This is usually used together with an Android 13 | * [io.jentz.winter.WinterApplication.InjectionAdapter]. 14 | * 15 | * Example: 16 | * ``` 17 | * // somewhere in the Android application class 18 | * Winter.useSimpleAndroidFragmentAdapter(enableWinterFragmentFactory = true) 19 | * 20 | * // in a FragmentActivity inject the factory; it is already setup by the adapter 21 | * @Inject lateinit var fragmentFactory: WinterFragmentFactory 22 | * 23 | * // add a fragment registered in the activity graph 24 | * supportFragmentManager 25 | * .beginTransaction() 26 | * .add(fragmentFactory.instance(), "tag") 27 | * .commit() 28 | * ``` 29 | */ 30 | class WinterFragmentFactory( 31 | private val graph: Graph 32 | ) : FragmentFactory() { 33 | 34 | /** 35 | * Get instance of fragment by fragment class [T] from activity graph. 36 | * 37 | * @return The fragment instance. 38 | */ 39 | inline fun instance(): T = 40 | instance(T::class.java) 41 | 42 | /** 43 | * Get instance of fragment by fragment class [clazz] from activity graph. 44 | * 45 | * @param clazz The fragment class. 46 | * @return The fragment instance 47 | */ 48 | fun instance(clazz: Class): T = 49 | graph.instanceByKey(ClassTypeKey(clazz)) 50 | 51 | override fun instantiate(classLoader: ClassLoader, className: String): Fragment { 52 | val clazz = loadFragmentClass(classLoader, className) 53 | return graph.instanceOrNullByKey(ClassTypeKey(clazz)) 54 | ?: super.instantiate(classLoader, className) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /winter-rxjava2/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | test { 14 | useJUnitPlatform() 15 | 16 | dependsOn 'cleanTest' 17 | 18 | testLogging { 19 | events "skipped", "failed" 20 | 21 | exceptionFormat "full" 22 | showExceptions true 23 | showCauses true 24 | showStackTraces true 25 | } 26 | afterSuite { desc, result -> 27 | if (!desc.parent) { 28 | println "\nTest result: ${result.resultType}" 29 | println "Test summary: ${result.testCount} tests, " + 30 | "${result.successfulTestCount} succeeded, " + 31 | "${result.failedTestCount} failed, " + 32 | "${result.skippedTestCount} skipped" 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | api project(':winter') 39 | implementation deps.kotlin.stdlib 40 | implementation deps.rx.rxjava 41 | 42 | testImplementation deps.test.jupiterApi 43 | testImplementation deps.test.kotlintest 44 | testImplementation deps.kotlin.reflect 45 | 46 | testRuntimeOnly deps.test.jupiterEngine 47 | } 48 | 49 | task javadocJar(type: Jar) { 50 | classifier = 'javadoc' 51 | from dokka 52 | } 53 | 54 | task sourcesJar(type: Jar) { 55 | classifier = 'sources' 56 | from sourceSets.main.allSource 57 | } 58 | 59 | artifacts { 60 | archives javadocJar, sourcesJar 61 | } 62 | 63 | dokka { 64 | outputFormat = 'html' 65 | outputDirectory = "$buildDir/javadoc" 66 | includes = ['dokka-packages.md'] 67 | 68 | linkMapping { 69 | dir = "src/main/kotlin" 70 | url = "https://github.com/beyama/winter/blob/master/winter-rxjava2/src/main/kotlin" 71 | suffix = "#L" 72 | } 73 | } 74 | 75 | detekt { 76 | config = files(rootProject.file('detekt-config.yml')) 77 | } 78 | 79 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter-coroutines/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | test { 14 | useJUnitPlatform() 15 | 16 | dependsOn 'cleanTest' 17 | 18 | testLogging { 19 | events "skipped", "failed" 20 | 21 | exceptionFormat "full" 22 | showExceptions true 23 | showCauses true 24 | showStackTraces true 25 | } 26 | afterSuite { desc, result -> 27 | if (!desc.parent) { 28 | println "\nTest result: ${result.resultType}" 29 | println "Test summary: ${result.testCount} tests, " + 30 | "${result.successfulTestCount} succeeded, " + 31 | "${result.failedTestCount} failed, " + 32 | "${result.skippedTestCount} skipped" 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | api project(':winter') 39 | 40 | implementation deps.kotlin.stdlib 41 | implementation deps.kotlin.coroutines 42 | 43 | testImplementation deps.test.jupiterApi 44 | testImplementation deps.test.kotlintest 45 | testImplementation deps.kotlin.reflect 46 | 47 | testRuntimeOnly deps.test.jupiterEngine 48 | } 49 | 50 | task javadocJar(type: Jar) { 51 | classifier = 'javadoc' 52 | from dokka 53 | } 54 | 55 | task sourcesJar(type: Jar) { 56 | classifier = 'sources' 57 | from sourceSets.main.allSource 58 | } 59 | 60 | artifacts { 61 | archives javadocJar, sourcesJar 62 | } 63 | 64 | dokka { 65 | outputFormat = 'html' 66 | outputDirectory = "$buildDir/javadoc" 67 | includes = ['dokka-packages.md'] 68 | 69 | linkMapping { 70 | dir = "src/main/kotlin" 71 | url = "https://github.com/beyama/winter/blob/master/winter-coroutines/src/main/kotlin" 72 | suffix = "#L" 73 | } 74 | } 75 | 76 | detekt { 77 | config = files(rootProject.file('detekt-config.yml')) 78 | } 79 | 80 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | test { 14 | useJUnitPlatform() 15 | 16 | dependsOn 'cleanTest' 17 | 18 | testLogging { 19 | events "skipped", "failed" 20 | 21 | exceptionFormat "full" 22 | showExceptions true 23 | showCauses true 24 | showStackTraces true 25 | } 26 | afterSuite { desc, result -> 27 | if (!desc.parent) { 28 | println "\nTest result: ${result.resultType}" 29 | println "Test summary: ${result.testCount} tests, " + 30 | "${result.successfulTestCount} succeeded, " + 31 | "${result.failedTestCount} failed, " + 32 | "${result.skippedTestCount} skipped" 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation deps.kotlin.stdlib 39 | api deps.javax.inject 40 | 41 | testImplementation deps.test.jupiterApi 42 | testImplementation deps.test.kotlintest 43 | testImplementation deps.kotlin.reflect 44 | testImplementation deps.mockito.core 45 | testImplementation deps.mockito.kotlin 46 | 47 | testRuntimeOnly deps.test.jupiterEngine 48 | } 49 | 50 | task javadocJar(type: Jar) { 51 | classifier = 'javadoc' 52 | from dokka 53 | } 54 | 55 | task sourcesJar(type: Jar) { 56 | classifier = 'sources' 57 | from sourceSets.main.allSource 58 | } 59 | 60 | artifacts { 61 | archives javadocJar, sourcesJar 62 | } 63 | 64 | dokka { 65 | outputFormat = 'html' 66 | outputDirectory = "$buildDir/javadoc" 67 | includes = ['dokka-packages.md'] 68 | 69 | linkMapping { 70 | dir = "src/main/kotlin" 71 | url = "https://github.com/beyama/winter/blob/master/winter/src/main/kotlin" 72 | suffix = "#L" 73 | } 74 | } 75 | 76 | detekt { 77 | config = files(rootProject.file('detekt-config.yml')) 78 | } 79 | 80 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/CreateServiceEvaluatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import com.nhaarman.mockitokotlin2.mock 4 | import io.jentz.winter.emptyGraph 5 | import io.jentz.winter.graph 6 | import io.jentz.winter.plugin.Plugin 7 | import io.jentz.winter.plugin.Plugins 8 | import io.kotlintest.matchers.types.shouldBeInstanceOf 9 | import org.junit.jupiter.api.Test 10 | 11 | class CreateServiceEvaluatorTest { 12 | 13 | private val graph = emptyGraph() 14 | 15 | private val plugin = mock() 16 | 17 | private val plugins = Plugins() 18 | 19 | @Test 20 | fun `should return DirectServiceEvaluator if no cyclic test and now lifecycle methods are required`() { 21 | createServiceEvaluator(graph, graph.component, plugins, false) 22 | .shouldBeInstanceOf() 23 | } 24 | 25 | @Test 26 | fun `should return CyclicDependenciesCheckingDirectServiceEvaluator if cyclic test is required but no lifecycle methods`() { 27 | createServiceEvaluator(graph, graph.component, plugins, true) 28 | .shouldBeInstanceOf() 29 | } 30 | 31 | @Test 32 | fun `should return LifecycleServiceEvaluator if a plugin is registered`() { 33 | createServiceEvaluator(graph, graph.component, plugins + plugin, false) 34 | .shouldBeInstanceOf() 35 | } 36 | 37 | @Test 38 | fun `should return LifecycleServiceEvaluator if a component requires lifecycle methods`() { 39 | val graph = graph { singleton(onPostConstruct = {}) { Any() } } 40 | createServiceEvaluator(graph, graph.component, plugins, false) 41 | .shouldBeInstanceOf() 42 | } 43 | 44 | @Test 45 | fun `should return LifecycleServiceEvaluator if a component requires lifecycle methods and plugins are registered`() { 46 | val graph = graph { singleton(onPostConstruct = {}) { Any() } } 47 | createServiceEvaluator(graph, graph.component, plugins + plugin, false) 48 | .shouldBeInstanceOf() 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /winter-androidx/src/main/kotlin/io/jentz/winter/androidx/AndroidPresentationScopeInjectionAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.androidx 2 | 3 | import android.app.Activity 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.ViewModelStoreOwner 6 | import io.jentz.winter.Graph 7 | import io.jentz.winter.WinterApplication 8 | import io.jentz.winter.WinterException 9 | import io.jentz.winter.androidx.inject.PresentationScope 10 | import io.jentz.winter.androidx.viewmodel.WinterViewModel 11 | 12 | /** 13 | * Extended version of [SimpleAndroidInjectionAdapter] that retains a [PresentationScope] subgraph 14 | * during Activity re-creation (configuration changes). 15 | * 16 | * It expects an application component like: 17 | * 18 | * ``` 19 | * Winter.component { 20 | * // this sub-graph outlives configuration changes and is only disposed when Activity 21 | * // isFinishing == true 22 | * subcomponent(PresentationScope::class) { 23 | * // this is recreated every time the Activity is recreated 24 | * subcomponent(ActivityScope::class) { 25 | * } 26 | * } 27 | * } 28 | * Winter.useAndroidPresentationScopeAdapter() 29 | * ``` 30 | */ 31 | open class AndroidPresentationScopeInjectionAdapter( 32 | app: WinterApplication 33 | ) : SimpleAndroidInjectionAdapter(app) { 34 | 35 | override fun getActivityParentGraph(activity: Activity): Graph { 36 | activity as? ViewModelStoreOwner ?: throw WinterException( 37 | "Activity `${activity.javaClass.name}` must implement ViewModelStoreOwner" 38 | ) 39 | val model = ViewModelProvider(activity).get(WinterViewModel::class.java) 40 | 41 | model.graph?.let { return it } 42 | 43 | return app.graph.getOrOpenSubgraph(PresentationScope::class, model) { 44 | constant(activity.viewModelStore) 45 | }.also { 46 | model.graph = it 47 | } 48 | } 49 | 50 | } 51 | 52 | /** 53 | * Register an [AndroidPresentationScopeInjectionAdapter] on this [WinterApplication] instance. 54 | */ 55 | fun WinterApplication.useAndroidPresentationScopeAdapter() { 56 | injectionAdapter = AndroidPresentationScopeInjectionAdapter(this) 57 | } 58 | -------------------------------------------------------------------------------- /winter-testing/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'kotlin' 3 | id 'org.jetbrains.dokka' 4 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 5 | } 6 | 7 | version = VERSION_NAME 8 | group = GROUP 9 | 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | 13 | test { 14 | useJUnitPlatform() 15 | 16 | dependsOn 'cleanTest' 17 | 18 | testLogging { 19 | events "skipped", "failed" 20 | 21 | exceptionFormat "full" 22 | showExceptions true 23 | showCauses true 24 | showStackTraces true 25 | } 26 | afterSuite { desc, result -> 27 | if (!desc.parent) { 28 | println "\nTest result: ${result.resultType}" 29 | println "Test summary: ${result.testCount} tests, " + 30 | "${result.successfulTestCount} succeeded, " + 31 | "${result.failedTestCount} failed, " + 32 | "${result.skippedTestCount} skipped" 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | api project(':winter') 39 | 40 | implementation deps.kotlin.stdlib 41 | api deps.kotlin.reflect 42 | 43 | testImplementation deps.test.jupiterApi 44 | testImplementation deps.test.kotlintest 45 | testImplementation deps.mockito.core 46 | testImplementation deps.mockito.kotlin 47 | 48 | testRuntimeOnly deps.test.jupiterEngine 49 | } 50 | 51 | task javadocJar(type: Jar) { 52 | classifier = 'javadoc' 53 | from dokka 54 | } 55 | 56 | task sourcesJar(type: Jar) { 57 | classifier = 'sources' 58 | from sourceSets.main.allSource 59 | } 60 | 61 | artifacts { 62 | archives javadocJar, sourcesJar 63 | } 64 | 65 | dokka { 66 | outputFormat = 'html' 67 | outputDirectory = "$buildDir/javadoc" 68 | includes = ['dokka-packages.md'] 69 | 70 | linkMapping { 71 | dir = "src/main/kotlin" 72 | url = "https://github.com/beyama/winter/blob/master/winter-testing/src/main/kotlin" 73 | suffix = "#L" 74 | } 75 | } 76 | 77 | detekt { 78 | config = files(rootProject.file('detekt-config.yml')) 79 | } 80 | 81 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 82 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/AbstractCyclicServiceEvaluatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.CyclicDependencyException 4 | import io.jentz.winter.typeKey 5 | import io.kotlintest.shouldBe 6 | import io.kotlintest.shouldThrow 7 | import org.junit.jupiter.api.Test 8 | 9 | abstract class AbstractCyclicServiceEvaluatorTest : AbstractServiceEvaluatorTest() { 10 | 11 | @Test 12 | fun `should check for cyclic dependencies`() { 13 | val d = BoundTestService(evaluator, typeKey("d")) 14 | val c = BoundTestService(evaluator, typeKey("c"), d) 15 | val b = BoundTestService(evaluator, typeKey("b"), c) 16 | val a = BoundTestService(evaluator, typeKey("a"), b) 17 | d.dependency = b 18 | 19 | shouldThrow { 20 | evaluator.evaluate(a) 21 | }.message.shouldBe( 22 | "Cyclic dependency found: " + 23 | "`ClassTypeKey(class java.lang.String qualifier = b)` " + 24 | "is dependent of itself.\n" + 25 | "Dependency chain: " + 26 | "ClassTypeKey(class java.lang.String qualifier = b) -> " + 27 | "ClassTypeKey(class java.lang.String qualifier = c) -> " + 28 | "ClassTypeKey(class java.lang.String qualifier = d) => " + 29 | "ClassTypeKey(class java.lang.String qualifier = b)" 30 | ) 31 | } 32 | 33 | @Test 34 | fun `should check for direct cyclic dependencies`() { 35 | val a = BoundTestService(evaluator, typeKey("a")) 36 | a.dependency = a 37 | 38 | shouldThrow { 39 | evaluator.evaluate(a) 40 | }.message.shouldBe( 41 | "Cyclic dependency found: " + 42 | "`ClassTypeKey(class java.lang.String qualifier = a)` " + 43 | "is directly dependent of itself.\n" + 44 | "Dependency chain: " + 45 | "ClassTypeKey(class java.lang.String qualifier = a) => " + 46 | "ClassTypeKey(class java.lang.String qualifier = a)" 47 | ) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/generator/ComponentGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler.generator 2 | 3 | import com.squareup.kotlinpoet.* 4 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 5 | import io.jentz.winter.Component 6 | import io.jentz.winter.compiler.APPLICATION_SCOPE_CLASS_NAME 7 | import io.jentz.winter.compiler.ProcessorConfiguration 8 | import io.jentz.winter.compiler.model.FactoryModel 9 | 10 | @KotlinPoetMetadataPreview 11 | class ComponentGenerator( 12 | private val configuration: ProcessorConfiguration, 13 | private val factories: List 14 | ) { 15 | 16 | private val packageName = checkNotNull(configuration.generatedComponentPackage) { 17 | "BUG: ComponentGenerator instantiated but package is null." 18 | } 19 | 20 | private val generatedClassName = ClassName(packageName, "GeneratedComponent") 21 | 22 | fun generate(): FileSpec { 23 | val groupedFactories = factories.groupBy { 24 | it.scopeAnnotationName ?: APPLICATION_SCOPE_CLASS_NAME 25 | } 26 | 27 | val generatedComponent = CodeBlock.builder() 28 | .beginControlFlow("component(%S)", "generated") 29 | .apply { 30 | groupedFactories.forEach { (scopeName, factories) -> 31 | beginControlFlow("subcomponent(%T::class)", scopeName) 32 | factories.forEach { factory -> 33 | addStatement("%T().register(this, false)", factory.generatedClassName) 34 | } 35 | endControlFlow() 36 | } 37 | } 38 | .endControlFlow() 39 | .build() 40 | 41 | return FileSpec.builder(generatedClassName.packageName, "generatedComponent") 42 | .addImport("io.jentz.winter", "component") 43 | .addProperty( 44 | PropertySpec.builder("generatedComponent", Component::class.asClassName()) 45 | .generatedAnnotation(configuration.generatedAnnotation) 46 | .initializer(generatedComponent) 47 | .build() 48 | ) 49 | .build() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /winter-compiler/src/test/java/io/jentz/winter/compiler/MembersInjectorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.google.common.truth.Truth.assert_ 4 | import com.google.testing.compile.Compiler 5 | import com.google.testing.compile.JavaFileObjects.forResource 6 | import com.google.testing.compile.JavaSourceSubjectFactory 7 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 8 | import org.junit.jupiter.api.Test 9 | 10 | 11 | @KotlinPoetMetadataPreview 12 | class MembersInjectorTest : BaseProcessorTest() { 13 | 14 | @Test 15 | fun `should generate injector for class with string field`() { 16 | assert_() 17 | .about(JavaSourceSubjectFactory.javaSource()) 18 | .that(forResource("WithInjectedField.java")) 19 | .processedWith(WinterProcessor()) 20 | .compilesWithoutError() 21 | 22 | generatesSource("WithInjectedField_WinterMembersInjector") 23 | } 24 | 25 | @Test 26 | fun `should invoke superclass injector`() { 27 | Compiler.javac() 28 | .withProcessors(WinterProcessor()) 29 | .compile( 30 | forResource("WithInjectedField.java"), 31 | forResource("WithInjectedFieldExtended.java") 32 | ) 33 | 34 | generatesSource("WithInjectedFieldExtended_WinterMembersInjector") 35 | } 36 | 37 | @Test 38 | fun `should generate injector for field with generics type`() { 39 | assert_() 40 | .about(JavaSourceSubjectFactory.javaSource()) 41 | .that(forResource("WithInjectedGenericFields.java")) 42 | .processedWith(WinterProcessor()) 43 | .compilesWithoutError() 44 | 45 | generatesSource("WithInjectedGenericFields_WinterMembersInjector") 46 | } 47 | 48 | @Test 49 | fun `should generate injector for javax Provider and Lazy fields`() { 50 | assert_() 51 | .about(JavaSourceSubjectFactory.javaSource()) 52 | .that(forResource("WithInjectedProviderAndLazyFields.java")) 53 | .processedWith(WinterProcessor()) 54 | .compilesWithoutError() 55 | 56 | generatesSource("WithInjectedProviderAndLazyFields_WinterMembersInjector") 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/TypeUtils.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import javax.lang.model.element.AnnotationMirror 4 | import javax.lang.model.element.AnnotationValue 5 | import javax.lang.model.element.Element 6 | import javax.lang.model.element.TypeElement 7 | import javax.lang.model.type.TypeMirror 8 | import javax.lang.model.util.Elements 9 | import javax.lang.model.util.Types 10 | import kotlin.reflect.KClass 11 | 12 | class TypeUtils( 13 | private val elements: Elements, 14 | private val types: Types 15 | ) { 16 | 17 | fun getFactoryTypeFromAnnotation( 18 | typeElement: TypeElement, 19 | annotation: KClass 20 | ): TypeElement? = getAnnotationMirror(typeElement, annotation) 21 | ?.let { getAnnotationValue(it, "value") } 22 | ?.let { it.value as? TypeMirror } 23 | ?.let { types.asElement(it) as TypeElement } 24 | ?.takeUnless { it.qualifiedName.contentEquals(Nothing::class.java.name) } 25 | ?.also { validateFactoryType(typeElement, it) } 26 | 27 | private fun getAnnotationMirror(element: Element, annotation: KClass): AnnotationMirror? { 28 | val annotationName = annotation.java.name 29 | return element.annotationMirrors.find { it.annotationType.toString() == annotationName } 30 | } 31 | 32 | private fun getAnnotationValue(mirror: AnnotationMirror, name: String): AnnotationValue? = elements 33 | .getElementValuesWithDefaults(mirror) 34 | .entries 35 | .find { entry -> entry.key.simpleName.toString() == name } 36 | ?.value 37 | 38 | private fun validateFactoryType(typeElement: TypeElement, factoryTypeElement: TypeElement) { 39 | require(factoryTypeElement.typeParameters.isEmpty()) { 40 | "The factory type must not be a generic type (${factoryTypeElement.qualifiedName})." 41 | } 42 | require(isAssignable(typeElement, factoryTypeElement)) { 43 | "Type $typeElement is not assignable to $factoryTypeElement." 44 | } 45 | } 46 | 47 | private fun isAssignable(typeElement: TypeElement, typeElement1: TypeElement): Boolean = 48 | types.isAssignable(typeElement.asType(), typeElement1.asType()) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /winter-junit5/src/main/kotlin/io/jentz/winter/junit5/AbstractWinterExtension.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.junit5 2 | 3 | import io.jentz.winter.testing.WinterTestSession 4 | import org.junit.jupiter.api.extension.ExtensionContext 5 | import org.junit.jupiter.api.extension.ParameterContext 6 | import org.junit.jupiter.api.extension.ParameterResolutionException 7 | import org.junit.jupiter.api.extension.ParameterResolver 8 | 9 | abstract class AbstractWinterExtension( 10 | private val namespace: ExtensionContext.Namespace, 11 | private val sessionBuilder: WinterTestSession.Builder 12 | ) : ParameterResolver { 13 | 14 | protected fun before(context: ExtensionContext) { 15 | val instances = context.testInstances.map { it.allInstances }.orElse(emptyList()) 16 | sessionBuilder.build(instances).apply { 17 | context.session = this 18 | start() 19 | } 20 | } 21 | 22 | protected fun after(context: ExtensionContext) { 23 | context.session.stop() 24 | } 25 | 26 | final override fun supportsParameter( 27 | parameterContext: ParameterContext, 28 | extensionContext: ExtensionContext 29 | ): Boolean = parameterContext.isAnnotated(WInject::class.java) 30 | 31 | final override fun resolveParameter( 32 | parameterContext: ParameterContext, 33 | extensionContext: ExtensionContext 34 | ): Any { 35 | val parameter = parameterContext.parameter 36 | 37 | val type = parameter.type 38 | 39 | val qualifier: String? = parameterContext 40 | .findAnnotation(WInject::class.java) 41 | .map { it.qualifier } 42 | .filter { it.isNotBlank() } 43 | .orElse(null) 44 | 45 | try { 46 | return extensionContext.session.resolve(type, qualifier) 47 | } catch (t: Throwable) { 48 | throw ParameterResolutionException("Error resolving parameter `${parameter}`", t) 49 | } 50 | } 51 | 52 | private var ExtensionContext.session: WinterTestSession 53 | get() = getStore(namespace).get(SESSION, WinterTestSession::class.java) 54 | set(value) = getStore(namespace).put(SESSION, value) 55 | 56 | companion object { 57 | private const val SESSION = "session" 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /winter-androidx-fragment/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'org.jetbrains.dokka' 5 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 6 | } 7 | 8 | version = VERSION_NAME 9 | group = GROUP 10 | 11 | android { 12 | compileSdkVersion versions.compileSdk 13 | buildToolsVersion versions.androidTools 14 | 15 | defaultConfig { 16 | minSdkVersion versions.minSdk 17 | targetSdkVersion versions.compileSdk 18 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 19 | consumerProguardFiles 'proguard-consumer-rules.pro' 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | 33 | sourceSets { 34 | main.java.srcDirs += 'src/main/kotlin' 35 | test.java.srcDirs += 'src/test/kotlin' 36 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 37 | } 38 | 39 | } 40 | 41 | dependencies { 42 | implementation project(":winter") 43 | implementation project(":winter-androidx") 44 | 45 | implementation deps.kotlin.stdlib 46 | implementation deps.androidx.fragment 47 | 48 | testImplementation deps.test.junit4 49 | testImplementation deps.test.kotlintest 50 | testImplementation deps.kotlin.reflect 51 | } 52 | 53 | task androidJavadocJar(type: Jar) { 54 | classifier = 'javadoc' 55 | from dokka 56 | } 57 | 58 | task androidSourcesJar(type: Jar) { 59 | classifier = 'sources' 60 | from android.sourceSets.main.java.sourceFiles 61 | } 62 | 63 | artifacts { 64 | archives androidSourcesJar, androidJavadocJar 65 | } 66 | 67 | dokka { 68 | outputFormat = 'html' 69 | outputDirectory = "$buildDir/javadoc" 70 | includes = ['dokka-packages.md'] 71 | 72 | linkMapping { 73 | dir = "src/main/kotlin" 74 | url = "https://github.com/beyama/winter/blob/master/winter-androidx-fragment/src/main/kotlin" 75 | suffix = "#L" 76 | } 77 | } 78 | 79 | detekt { 80 | config = files(rootProject.file('detekt-config.yml')) 81 | } 82 | 83 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter-rxjava2/src/test/kotlin/io/jentz/winter/rxjava2/WinterDisposablePluginTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.rxjava2 2 | 3 | import io.jentz.winter.Winter 4 | import io.jentz.winter.graph 5 | import io.kotlintest.matchers.boolean.shouldBeFalse 6 | import io.kotlintest.matchers.boolean.shouldBeTrue 7 | import io.kotlintest.matchers.types.shouldBeInstanceOf 8 | import io.kotlintest.shouldBe 9 | import io.reactivex.disposables.CompositeDisposable 10 | import io.reactivex.disposables.Disposable 11 | import org.junit.jupiter.api.AfterEach 12 | import org.junit.jupiter.api.BeforeEach 13 | import org.junit.jupiter.api.Test 14 | import org.junit.jupiter.api.TestInstance 15 | 16 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 17 | class WinterDisposablePluginTest { 18 | 19 | @BeforeEach 20 | fun beforeEach() { 21 | Winter.installDisposablePlugin() 22 | } 23 | 24 | @AfterEach 25 | fun afterEach() { 26 | Winter.uninstallDisposablePlugin() 27 | } 28 | 29 | @Test 30 | fun `should install and uninstall plugin`() { 31 | graph { }.instanceOrNull().shouldBeInstanceOf() 32 | 33 | Winter.uninstallDisposablePlugin() 34 | 35 | graph { }.instanceOrNull().shouldBe(null) 36 | } 37 | 38 | @Test 39 | fun `should dispose CompositeDisposable when graph gets closed`() { 40 | graph {}.apply { 41 | val disposable: CompositeDisposable = instance() 42 | disposable.isDisposed.shouldBeFalse() 43 | close() 44 | disposable.isDisposed.shouldBeTrue() 45 | } 46 | } 47 | 48 | @Test 49 | fun `should add all disposable singletons to CompositeDisposable`() { 50 | val d1 = CompositeDisposable() 51 | val d2 = CompositeDisposable() 52 | graph { 53 | singleton("d1") { d1 } 54 | prototype("d2") { d2 } 55 | }.apply { 56 | val disposables: CompositeDisposable = instance() 57 | instance("d1") 58 | instance("d2") 59 | 60 | disposables.size().shouldBe(1) 61 | 62 | disposables.remove(d1).shouldBeTrue() 63 | disposables.remove(d2).shouldBeFalse() 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /winter-androidx-viewmodel-savedstate/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'org.jetbrains.dokka' 5 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 6 | } 7 | 8 | version = VERSION_NAME 9 | group = GROUP 10 | 11 | android { 12 | compileSdkVersion versions.compileSdk 13 | buildToolsVersion versions.androidTools 14 | 15 | defaultConfig { 16 | minSdkVersion versions.minSdk 17 | targetSdkVersion versions.compileSdk 18 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 19 | consumerProguardFiles 'proguard-consumer-rules.pro' 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | 33 | sourceSets { 34 | main.java.srcDirs += 'src/main/kotlin' 35 | test.java.srcDirs += 'src/test/kotlin' 36 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 37 | } 38 | 39 | } 40 | 41 | dependencies { 42 | implementation project(":winter") 43 | implementation project(":winter-androidx") 44 | 45 | implementation deps.kotlin.stdlib 46 | implementation deps.androidx.viewmodelSavedstate 47 | 48 | testImplementation deps.test.junit4 49 | testImplementation deps.test.kotlintest 50 | testImplementation deps.kotlin.reflect 51 | } 52 | 53 | task androidJavadocJar(type: Jar) { 54 | classifier = 'javadoc' 55 | from dokka 56 | } 57 | 58 | task androidSourcesJar(type: Jar) { 59 | classifier = 'sources' 60 | from android.sourceSets.main.java.sourceFiles 61 | } 62 | 63 | artifacts { 64 | archives androidSourcesJar, androidJavadocJar 65 | } 66 | 67 | dokka { 68 | outputFormat = 'html' 69 | outputDirectory = "$buildDir/javadoc" 70 | includes = ['dokka-packages.md'] 71 | 72 | linkMapping { 73 | dir = "src/main/kotlin" 74 | url = "https://github.com/beyama/winter/blob/master/winter-androidx-viewmodel-savedstate/src/main/kotlin" 75 | suffix = "#L" 76 | } 77 | } 78 | 79 | detekt { 80 | config = files(rootProject.file('detekt-config.yml')) 81 | } 82 | 83 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter-androidx/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'org.jetbrains.dokka' 5 | id 'io.gitlab.arturbosch.detekt' version '1.0.0.RC9.2' 6 | } 7 | 8 | version = VERSION_NAME 9 | group = GROUP 10 | 11 | android { 12 | compileSdkVersion versions.compileSdk 13 | buildToolsVersion versions.androidTools 14 | 15 | defaultConfig { 16 | minSdkVersion versions.minSdk 17 | targetSdkVersion versions.compileSdk 18 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 19 | consumerProguardFiles 'proguard-consumer-rules.pro' 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | 33 | sourceSets { 34 | main.java.srcDirs += 'src/main/kotlin' 35 | test.java.srcDirs += 'src/test/kotlin' 36 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 37 | } 38 | 39 | } 40 | 41 | dependencies { 42 | implementation project(":winter") 43 | 44 | implementation deps.kotlin.stdlib 45 | 46 | implementation deps.androidx.lifecycleCommon 47 | implementation deps.androidx.viewmodel 48 | 49 | testImplementation deps.test.junit4 50 | testImplementation deps.test.kotlintest 51 | testImplementation deps.androidx.lifecycleRuntime 52 | testImplementation deps.kotlin.reflect 53 | } 54 | 55 | task androidJavadocJar(type: Jar) { 56 | classifier = 'javadoc' 57 | from dokka 58 | } 59 | 60 | task androidSourcesJar(type: Jar) { 61 | classifier = 'sources' 62 | from android.sourceSets.main.java.sourceFiles 63 | } 64 | 65 | artifacts { 66 | archives androidSourcesJar, androidJavadocJar 67 | } 68 | 69 | dokka { 70 | outputFormat = 'html' 71 | outputDirectory = "$buildDir/javadoc" 72 | includes = ['dokka-packages.md'] 73 | 74 | linkMapping { 75 | dir = "src/main/kotlin" 76 | url = "https://github.com/beyama/winter/blob/master/winter-androidx/src/main/kotlin" 77 | suffix = "#L" 78 | } 79 | } 80 | 81 | detekt { 82 | config = files(rootProject.file('detekt-config.yml')) 83 | } 84 | 85 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/evaluator/AbstractServiceEvaluatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.evaluator 2 | 3 | import io.jentz.winter.DependencyResolutionException 4 | import io.jentz.winter.EntryNotFoundException 5 | import io.jentz.winter.typeKey 6 | import io.kotlintest.matchers.types.shouldBeSameInstanceAs 7 | import io.kotlintest.shouldBe 8 | import io.kotlintest.shouldThrow 9 | import org.junit.jupiter.api.Test 10 | 11 | abstract class AbstractServiceEvaluatorTest { 12 | 13 | internal abstract val evaluator: ServiceEvaluator 14 | 15 | @Test 16 | fun `should call new instance and return result`() { 17 | evaluator 18 | .evaluate(BoundTestService(evaluator) { "FOO" }) 19 | .shouldBe("FOO") 20 | } 21 | 22 | @Test 23 | fun `should throw DependencyResolutionException if service throws an EntryNotFoundException`() { 24 | val exception = EntryNotFoundException(typeKey>(), "") 25 | val b = BoundTestService(evaluator, typeKey("b"), throwOnNewInstance = { exception }) 26 | val a = BoundTestService(evaluator, typeKey("a"), b) 27 | 28 | shouldThrow { 29 | evaluator.evaluate(a) 30 | }.run { 31 | key.shouldBe(typeKey("b")) 32 | message.shouldBe("Error while resolving dependency with key: " + 33 | "ClassTypeKey(class java.lang.String qualifier = b) " + 34 | "reason: could not find dependency with key " + 35 | "ClassTypeKey(interface java.util.List qualifier = null)") 36 | cause.shouldBeSameInstanceAs(exception) 37 | } 38 | } 39 | 40 | @Test 41 | fun `should throw DependencyResolutionException if service throws an exception`() { 42 | val exception = Exception() 43 | val b = BoundTestService(evaluator, typeKey("b"), 44 | throwOnNewInstance = { exception }) 45 | val a = BoundTestService(evaluator, typeKey("a"), b) 46 | 47 | shouldThrow { 48 | evaluator.evaluate(a) 49 | }.run { 50 | key.shouldBe(typeKey("b")) 51 | message.shouldBe( 52 | "Factory of dependency with key " + 53 | "ClassTypeKey(class java.lang.String qualifier = b) " + 54 | "threw an exception on invocation.") 55 | cause.shouldBeSameInstanceAs(exception) 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /winter-compiler/src/test/java/io/jentz/winter/compiler/InjectConstructorAnnotationTest.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.google.common.truth.Truth.assert_ 4 | import com.google.testing.compile.JavaFileObjects.forResource 5 | import com.google.testing.compile.JavaSourceSubjectFactory.javaSource 6 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 7 | import org.junit.jupiter.api.Test 8 | 9 | 10 | @KotlinPoetMetadataPreview 11 | class InjectConstructorAnnotationTest : BaseProcessorTest() { 12 | 13 | @Test 14 | fun `should fail if class contains more than one constructor`() { 15 | assert_() 16 | .about(javaSource()) 17 | .that(forResource("InjectConstructorAnnotationWithTwoConstructors.java")) 18 | .processedWith(WinterProcessor()) 19 | .failsToCompile() 20 | .withErrorContaining( 21 | "Class `test.InjectConstructorAnnotationWithTwoConstructors` " + 22 | "is annotated with InjectConstructor and therefore must have exactly one non-private constructor." 23 | ) 24 | } 25 | 26 | @Test 27 | fun `should fail if class contains an Inject annotated constructor`() { 28 | assert_() 29 | .about(javaSource()) 30 | .that(forResource("InjectConstructorAnnotationWithInjectConstructor.java")) 31 | .processedWith(WinterProcessor()) 32 | .failsToCompile() 33 | .withErrorContaining( 34 | "Class `test.InjectConstructorAnnotationWithInjectConstructor` " + 35 | "is annotated with InjectConstructor and therefore must not have a constructor with Inject annotation." 36 | ) 37 | } 38 | 39 | @Test 40 | fun `should generate factory`() { 41 | assert_() 42 | .about(javaSource()) 43 | .that(forResource("InjectConstructorAnnotation.java")) 44 | .processedWith(WinterProcessor()) 45 | .compilesWithoutError() 46 | 47 | generatesSource("InjectConstructorAnnotation_WinterFactory") 48 | } 49 | 50 | @Test 51 | fun `should register factory with type from annotation`() { 52 | assert_() 53 | .about(javaSource()) 54 | .that(forResource("InjectConstructorAnnotationWithType.java")) 55 | .processedWith(WinterProcessor()) 56 | .compilesWithoutError() 57 | 58 | generatesSource("InjectConstructorAnnotationWithType_WinterFactory") 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /winter-compiler/build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.jvm.Jvm 2 | 3 | plugins { 4 | id 'kotlin' 5 | id 'kotlin-kapt' 6 | id 'org.jetbrains.dokka' 7 | } 8 | 9 | version = VERSION_NAME 10 | group = GROUP 11 | 12 | sourceCompatibility = JavaVersion.VERSION_1_8 13 | targetCompatibility = JavaVersion.VERSION_1_8 14 | 15 | test { 16 | useJUnitPlatform() 17 | 18 | dependsOn 'cleanTest' 19 | 20 | testLogging { 21 | events "skipped", "failed" 22 | 23 | exceptionFormat "full" 24 | showExceptions true 25 | showCauses true 26 | showStackTraces true 27 | } 28 | afterSuite { desc, result -> 29 | if (!desc.parent) { 30 | println "\nTest result: ${result.resultType}" 31 | println "Test summary: ${result.testCount} tests, " + 32 | "${result.successfulTestCount} succeeded, " + 33 | "${result.failedTestCount} failed, " + 34 | "${result.skippedTestCount} skipped" 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation project(':winter') 41 | 42 | implementation deps.kotlin.stdlib 43 | 44 | implementation deps.kotlinpoet.kotlinpoet 45 | implementation deps.kotlinpoet.metadata 46 | 47 | implementation deps.incap.incap 48 | kapt deps.incap.processor 49 | 50 | testImplementation project(':winter-junit5') 51 | testImplementation deps.test.jupiterApi 52 | testImplementation deps.test.kotlintest 53 | testImplementation deps.test.compileTesting 54 | testImplementation deps.javax.annotationApi // to get the legacy Generated annotation on >= JDK9 55 | 56 | if (!Jvm.current().javaVersion.isJava9Compatible()) { 57 | testImplementation files(Jvm.current().getToolsJar()) 58 | } 59 | 60 | testRuntimeOnly deps.test.jupiterEngine 61 | } 62 | 63 | task javadocJar(type: Jar) { 64 | classifier = 'javadoc' 65 | from dokka 66 | } 67 | 68 | task sourcesJar(type: Jar) { 69 | classifier = 'sources' 70 | from sourceSets.main.allSource 71 | } 72 | 73 | artifacts { 74 | archives javadocJar, sourcesJar 75 | } 76 | 77 | dokka { 78 | outputFormat = 'html' 79 | outputDirectory = "$buildDir/javadoc" 80 | includes = ['dokka-packages.md'] 81 | 82 | linkMapping { 83 | dir = "src/main/kotlin" 84 | url = "https://github.com/beyama/winter-compiler/blob/master/winter/src/main/kotlin" 85 | suffix = "#L" 86 | } 87 | } 88 | 89 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') -------------------------------------------------------------------------------- /winter-compiler/src/main/java/io/jentz/winter/compiler/WinterProcessor.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter.compiler 2 | 3 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 4 | import io.jentz.winter.WinterException 5 | import io.jentz.winter.delegate.inject 6 | import io.jentz.winter.delegate.injectProvider 7 | import io.jentz.winter.inject.InjectConstructor 8 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessor 9 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.* 10 | import javax.annotation.processing.AbstractProcessor 11 | import javax.annotation.processing.ProcessingEnvironment 12 | import javax.annotation.processing.RoundEnvironment 13 | import javax.inject.Inject 14 | import javax.lang.model.SourceVersion 15 | import javax.lang.model.element.TypeElement 16 | 17 | @KotlinPoetMetadataPreview 18 | @IncrementalAnnotationProcessor(DYNAMIC) 19 | class WinterProcessor : AbstractProcessor() { 20 | 21 | private val logger: Logger by inject() 22 | private val configuration: ProcessorConfiguration by inject() 23 | private val generatorProvider: () -> Generator by injectProvider() 24 | 25 | override fun init(processingEnv: ProcessingEnvironment) { 26 | super.init(processingEnv) 27 | DI.inject(processingEnv, this) 28 | } 29 | 30 | override fun getSupportedOptions(): Set { 31 | return if (configuration.generatedComponentPackage != null) { 32 | setOf( 33 | AGGREGATING.processorOption, 34 | OPTION_GENERATED_COMPONENT_PACKAGE 35 | ) 36 | } else { 37 | setOf( 38 | ISOLATING.processorOption, 39 | OPTION_GENERATED_COMPONENT_PACKAGE 40 | ) 41 | } 42 | } 43 | 44 | override fun getSupportedAnnotationTypes(): Set = 45 | setOf(Inject::class.java.canonicalName, InjectConstructor::class.java.canonicalName) 46 | 47 | override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported() 48 | 49 | override fun process( 50 | annotations: MutableSet, 51 | roundEnv: RoundEnvironment 52 | ): Boolean { 53 | 54 | try { 55 | generatorProvider().process(roundEnv) 56 | } catch (e: WinterException) { 57 | logger.error("Skipping annotation processing: ${e.cause?.message}") 58 | } catch (t: Throwable) { 59 | logger.error("Skipping annotation processing: ${t.message}") 60 | } 61 | 62 | return true 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/TypeKey.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | import java.lang.reflect.ParameterizedType 4 | import java.lang.reflect.Type 5 | 6 | /** 7 | * Interface for all type keys. 8 | */ 9 | interface TypeKey { 10 | 11 | val qualifier: Any? 12 | 13 | /** 14 | * Test if [other] has the same type. 15 | * Like [equals] without looking onto the [qualifier]. 16 | */ 17 | fun typeEquals(other: TypeKey<*>): Boolean 18 | 19 | } 20 | 21 | class ClassTypeKey @JvmOverloads constructor( 22 | val type: Class, 23 | override val qualifier: Any? = null 24 | ) : TypeKey { 25 | 26 | private var _hashCode = 0 27 | 28 | override fun typeEquals(other: TypeKey<*>): Boolean { 29 | if (other === this) return true 30 | if (other is GenericClassTypeKey<*>) return Types.equals(type, other.type) 31 | if (other !is ClassTypeKey) return false 32 | return other.type == type 33 | } 34 | 35 | override fun equals(other: Any?): Boolean { 36 | return other is TypeKey<*> && other.qualifier == qualifier && typeEquals(other) 37 | } 38 | 39 | override fun hashCode(): Int { 40 | if (_hashCode == 0) { 41 | _hashCode = Types.hashCode(type, qualifier) 42 | } 43 | return _hashCode 44 | } 45 | 46 | override fun toString(): String = "ClassTypeKey($type qualifier = $qualifier)" 47 | 48 | } 49 | 50 | abstract class GenericClassTypeKey @JvmOverloads constructor( 51 | override val qualifier: Any? = null 52 | ) : TypeKey { 53 | 54 | private var _hashCode = 0 55 | val type: Type = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] 56 | 57 | override fun typeEquals(other: TypeKey<*>): Boolean { 58 | if (other === this) return true 59 | if (other is ClassTypeKey) return Types.equals(other.type, type) 60 | if (other is GenericClassTypeKey<*>) return Types.equals(type, other.type) 61 | return false 62 | } 63 | 64 | override fun equals(other: Any?): Boolean { 65 | return other is TypeKey<*> && other.qualifier == qualifier && typeEquals(other) 66 | } 67 | 68 | override fun hashCode(): Int { 69 | if (_hashCode == 0) { 70 | _hashCode = Types.hashCode(type) 71 | _hashCode = 31 * _hashCode + (qualifier?.hashCode() ?: 0) 72 | } 73 | return _hashCode 74 | } 75 | 76 | override fun toString(): String = "GenericClassTypeKey($type qualifier = $qualifier)" 77 | 78 | } 79 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /winter/src/main/kotlin/io/jentz/winter/Types.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | import java.lang.reflect.* 4 | 5 | @Suppress("MagicNumber") 6 | internal object Types { 7 | 8 | fun equals(left: Type, right: Type): Boolean { 9 | if (left.javaClass != right.javaClass) return false 10 | 11 | return when (left) { 12 | is Class<*> -> left == right 13 | is ParameterizedType -> { 14 | right as ParameterizedType 15 | equals(left.rawType, right.rawType) 16 | && equals(left.actualTypeArguments, right.actualTypeArguments) 17 | } 18 | is WildcardType -> { 19 | right as WildcardType 20 | equals(left.lowerBounds, right.lowerBounds) 21 | && equals(left.upperBounds, right.upperBounds) 22 | } 23 | is GenericArrayType -> { 24 | right as GenericArrayType 25 | equals(left.genericComponentType, right.genericComponentType) 26 | } 27 | is TypeVariable<*> -> { 28 | right as TypeVariable<*> 29 | equals(left.bounds, right.bounds) 30 | } 31 | else -> left == right 32 | } 33 | } 34 | 35 | private fun equals(left: Array, right: Array): Boolean { 36 | if (left.size != right.size) return false 37 | return left.indices.all { equals(left[it], right[it]) } 38 | } 39 | 40 | fun hashCode(type: Type): Int = when (type) { 41 | is Class<*> -> type.hashCode() 42 | is ParameterizedType -> { 43 | var hashCode = hashCode(type.rawType) 44 | for (arg in type.actualTypeArguments) 45 | hashCode = hashCode * 31 + hashCode(arg) 46 | hashCode 47 | } 48 | is WildcardType -> { 49 | var hashCode = 0 50 | for (arg in type.upperBounds) 51 | hashCode = hashCode * 19 + hashCode(arg) 52 | for (arg in type.lowerBounds) 53 | hashCode = hashCode * 17 + hashCode(arg) 54 | hashCode 55 | } 56 | is GenericArrayType -> 53 + hashCode(type.genericComponentType) 57 | is TypeVariable<*> -> { 58 | var hashCode = 0 59 | for (arg in type.bounds) 60 | hashCode = hashCode * 29 + hashCode(arg) 61 | hashCode 62 | } 63 | else -> type.hashCode() 64 | } 65 | 66 | fun hashCode(cls: Class<*>, qualifier: Any? = null): Int = 67 | 31 * cls.hashCode() + (qualifier?.hashCode() ?: 0) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /winter/src/test/kotlin/io/jentz/winter/testClasses.kt: -------------------------------------------------------------------------------- 1 | package io.jentz.winter 2 | 3 | import io.jentz.winter.inject.Factory 4 | import io.jentz.winter.inject.MembersInjector 5 | 6 | interface Pump 7 | 8 | class Heater 9 | 10 | class Thermosiphon(val heater: Heater) : Pump 11 | 12 | class CoffeeMaker(val heater: Heater, val pump: Pump) 13 | 14 | class Parent(val child: Child) 15 | 16 | class Child { 17 | var parent: Parent? = null 18 | } 19 | 20 | open class Service { 21 | var property = 0 22 | } 23 | 24 | @Suppress("unused", "ClassName") 25 | class Service_WinterMembersInjector : MembersInjector { 26 | override fun inject(graph: Graph, target: Service) { 27 | target.property = 42 28 | } 29 | } 30 | 31 | @Suppress("unused", "ClassName") 32 | class Service_WinterFactory : Factory { 33 | override fun register(builder: Component.Builder, override: Boolean): TypeKey { 34 | return builder.prototype(factory = this) 35 | } 36 | 37 | override fun invoke(graph: Graph): Service = Service() 38 | } 39 | 40 | class ExtendedService : Service() 41 | 42 | /** 43 | * We can't really test Soft- and WeakReferences so this is a version that is baked by a field 44 | * instead of a reference so we have control and not the GC. 45 | */ 46 | 47 | internal class UnboundReferenceService( 48 | override val key: TypeKey, 49 | val block: Graph.() -> T 50 | ) : UnboundService { 51 | 52 | override val requiresLifecycleCallbacks: Boolean get() = false 53 | 54 | override fun bind(graph: Graph): BoundService { 55 | return BoundReferenceService(graph, this) 56 | } 57 | } 58 | 59 | internal class BoundReferenceService( 60 | graph: Graph, 61 | override val unboundService: UnboundReferenceService 62 | ) : AbstractBoundSingletonService(graph) { 63 | 64 | var postConstructCalledCount = 0 65 | var postConstructLastArgument: Any? = null 66 | var closeCalled = 0 67 | 68 | override val scope: Scope get() = Scope("referenceTest") 69 | 70 | override fun onPostConstruct(instance: R) { 71 | postConstructCalledCount += 1 72 | postConstructLastArgument = instance 73 | } 74 | 75 | override fun onClose() { 76 | closeCalled += 1 77 | } 78 | 79 | public override var instance: Any = UNINITIALIZED_VALUE 80 | 81 | override fun newInstance(): R { 82 | return unboundService.block(graph).also { instance = it } 83 | } 84 | 85 | } 86 | 87 | internal inline fun Component.Builder.reference(noinline block: GFactory) { 88 | register(UnboundReferenceService(typeKey(), block), false) 89 | } 90 | --------------------------------------------------------------------------------