Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ast/src/main/kotlin/motif/ast/IrAnnotation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ interface IrAnnotation : IrEquivalence {

val members: List<IrMethod>

val annotationValueMap: Map<String, Any?>

fun matchesClass(annotationClass: KClass<out Annotation>): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class CompilerAnnotation(val env: XProcessingEnv, val mirror: XAnnotation) : IrA

override val type: IrType = CompilerType(env, mirror.type)

override val annotationValueMap: Map<String, Any?>
get() = mirror.annotationValues.associate { it.name to it.value }

override val members: List<IrMethod> by lazy {
val annotationMethods = mirror.type.typeElement?.getDeclaredMethods().orEmpty()
mirror.annotationValues.map { annotationValue ->
Expand Down
65 changes: 49 additions & 16 deletions compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ object JavaCodeGenerator {
addSuperinterface(superClassName.j)
objectsField?.let { addField(it.spec()) }
addField(dependenciesField.spec())
cacheFields.forEach { addField(it.spec()) }
cacheFields.forEach { addField(it.spec(useNullFieldInitialization)) }
addMethod(constructor.spec())
alternateConstructor?.let { addMethod(it.spec()) }
accessMethodImpls.forEach { addMethod(it.spec()) }
childMethodImpls.forEach { addMethod(it.spec()) }
addMethod(scopeProviderMethod.spec())
factoryProviderMethods.forEach { addMethods(it.specs()) }
factoryProviderMethods.forEach { addMethods(it.specs(useNullFieldInitialization)) }
dependencyProviderMethods.forEach { addMethod(it.spec()) }
dependencies?.let { addType(it.spec()) }
objectsImpl?.let { addType(it.spec()) }
Expand Down Expand Up @@ -80,10 +80,14 @@ object JavaCodeGenerator {
private fun DependenciesField.spec(): FieldSpec =
FieldSpec.builder(dependenciesClassName.j, name, Modifier.PRIVATE, Modifier.FINAL).build()

private fun CacheField.spec(): FieldSpec =
FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE)
.initializer("\$T.NONE", None::class.java)
.build()
private fun CacheField.spec(useNullFieldInitialization: Boolean): FieldSpec =
if (useNullFieldInitialization) {
FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE).build()
} else {
FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE)
.initializer("\$T.NONE", None::class.java)
.build()
}

private fun Constructor.spec(): MethodSpec =
MethodSpec.constructorBuilder()
Expand Down Expand Up @@ -164,30 +168,59 @@ object JavaCodeGenerator {
private fun ScopeProviderMethod.spec(): MethodSpec =
MethodSpec.methodBuilder(name).returns(scopeClassName.j).addStatement("return this").build()

private fun FactoryProviderMethod.specs(): List<MethodSpec> {
private fun FactoryProviderMethod.specs(useNullFieldInitialization: Boolean): List<MethodSpec> {
val primarySpec =
MethodSpec.methodBuilder(name).returns(returnTypeName.j).addStatement(body.spec()).build()
MethodSpec.methodBuilder(name)
.returns(returnTypeName.j)
.addStatement(body.spec(useNullFieldInitialization))
.build()
val spreadSpecs = spreadProviderMethods.map { it.spec() }
return listOf(primarySpec) + spreadSpecs
}

private fun FactoryProviderMethodBody.spec(): CodeBlock =
private fun FactoryProviderMethodBody.spec(useNullFieldInitialization: Boolean): CodeBlock =
when (this) {
is FactoryProviderMethodBody.Cached -> spec()
is FactoryProviderMethodBody.Cached -> spec(useNullFieldInitialization)
is FactoryProviderMethodBody.Uncached -> spec()
}

private fun FactoryProviderMethodBody.Cached.spec(): CodeBlock =
CodeBlock.builder()
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
private fun FactoryProviderMethodBody.Cached.spec(
useNullFieldInitialization: Boolean,
): CodeBlock {
if (useNullFieldInitialization) {
val localFieldName = "_$cacheFieldName"
return CodeBlock.builder()
// Using a local variable reduces atomic read overhead
.add("Object $localFieldName = \$N;\n", cacheFieldName)
.beginControlFlow("if (\$N == null)", localFieldName)
.beginControlFlow("synchronized (this)")
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
.add("\$N = \$L;", cacheFieldName, instantiation.spec())
.beginControlFlow("if (\$N == null)", cacheFieldName)
.add("\$N = \$L;\n", localFieldName, instantiation.spec())
.beginControlFlow("if (\$N == null)", localFieldName)
.add(
"throw new \$T(\$S);\n",
NullPointerException::class.java,
"Factory method cannot return null",
)
.endControlFlow()
.add("\$N = \$L;\n", cacheFieldName, localFieldName)
.endControlFlow()
.endControlFlow()
.endControlFlow()
.add("return (\$T) \$N", returnTypeName.j, cacheFieldName)
.add("return (\$T) \$N", returnTypeName.j, localFieldName)
.build()
}
return CodeBlock.builder()
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
.beginControlFlow("synchronized (this)")
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
.add("\$N = \$L;", cacheFieldName, instantiation.spec())
.endControlFlow()
.endControlFlow()
.endControlFlow()
.add("return (\$T) \$N", returnTypeName.j, cacheFieldName)
.build()
}

private fun FactoryProviderMethodBody.Uncached.spec(): CodeBlock =
CodeBlock.of("return \$L", instantiation.spec())
Expand Down
74 changes: 52 additions & 22 deletions compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview
import com.squareup.kotlinpoet.javapoet.toKClassName
import motif.internal.None
Expand All @@ -48,7 +49,7 @@ object KotlinCodeGenerator {
addSuperinterface(superClassName.kt)
objectsField?.let { addProperty(it.spec()) }
addProperty(dependenciesField.spec())
cacheFields.forEach { addProperty(it.spec()) }
cacheFields.forEach { addProperty(it.spec(useNullFieldInitialization)) }
primaryConstructor(constructor.spec())
alternateConstructor?.let { addFunction(it.spec()) }
accessMethodImpls
Expand All @@ -59,7 +60,7 @@ object KotlinCodeGenerator {
.forEach { addProperty(it.propSpec()) }
childMethodImpls.forEach { addFunction(it.spec()) }
addFunction(scopeProviderMethod.spec())
factoryProviderMethods.forEach { addFunctions(it.specs()) }
factoryProviderMethods.forEach { addFunctions(it.specs(useNullFieldInitialization)) }
dependencyProviderMethods.forEach { addFunction(it.spec()) }
dependencies?.let { addType(it.spec()) }
objectsImpl?.let { addType(it.spec()) }
Expand Down Expand Up @@ -96,12 +97,20 @@ object KotlinCodeGenerator {
.initializer(name)
.build()

private fun CacheField.spec(): PropertySpec =
PropertySpec.builder(name, Any::class, KModifier.PRIVATE)
.mutable(true)
.addAnnotation(Volatile::class)
.initializer("%T.NONE", None::class)
.build()
private fun CacheField.spec(useNullFieldInitialization: Boolean): PropertySpec =
if (useNullFieldInitialization) {
PropertySpec.builder(name, Any::class.asTypeName().copy(true), KModifier.PRIVATE)
.mutable(true)
.addAnnotation(Volatile::class)
.initializer("null")
.build()
} else {
PropertySpec.builder(name, Any::class, KModifier.PRIVATE)
.mutable(true)
.addAnnotation(Volatile::class)
.initializer("%T.NONE", None::class)
.build()
}

private fun Constructor.spec(): FunSpec =
FunSpec.constructorBuilder()
Expand Down Expand Up @@ -194,34 +203,55 @@ object KotlinCodeGenerator {
.addStatement("return this")
.build()

private fun FactoryProviderMethod.specs(): List<FunSpec> {
private fun FactoryProviderMethod.specs(useNullFieldInitialization: Boolean): List<FunSpec> {
val primarySpec =
FunSpec.builder(name)
.addModifiers(KModifier.INTERNAL)
.returns(returnTypeName.reloadedForTypeArgs(env))
.addCode(body.spec())
.addCode(body.spec(useNullFieldInitialization))
.build()
val spreadSpecs = spreadProviderMethods.map { it.spec() }
return listOf(primarySpec) + spreadSpecs
}

private fun FactoryProviderMethodBody.spec(): CodeBlock =
private fun FactoryProviderMethodBody.spec(useNullFieldInitialization: Boolean): CodeBlock =
when (this) {
is FactoryProviderMethodBody.Cached -> spec()
is FactoryProviderMethodBody.Cached -> spec(useNullFieldInitialization)
is FactoryProviderMethodBody.Uncached -> spec()
}

private fun FactoryProviderMethodBody.Cached.spec(): CodeBlock =
CodeBlock.builder()
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
.beginControlFlow("synchronized (this)")
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
.addStatement("%N=%L", cacheFieldName, instantiation.spec())
.endControlFlow()
.endControlFlow()
.endControlFlow()
.add("return ( %N as %T )", cacheFieldName, returnTypeName.reloadedForTypeArgs(env))
private fun FactoryProviderMethodBody.Cached.spec(
useNullFieldInitialization: Boolean,
): CodeBlock {
if (useNullFieldInitialization) {
val localFieldName = "_$cacheFieldName"
val codeBlockBuilder =
CodeBlock.builder()
// Using a local variable reduces atomic read overhead
.addStatement("var $localFieldName = %N;\n", cacheFieldName)
.beginControlFlow("if (%N == null)", localFieldName)
.beginControlFlow("synchronized (this)")
.beginControlFlow("if (%N == null)", cacheFieldName)
.addStatement("%N = %L", localFieldName, instantiation.spec())
.addStatement("%N = %N", cacheFieldName, localFieldName)
.endControlFlow()
.endControlFlow()
.endControlFlow()
return codeBlockBuilder
.add("return ( %N as %T )", localFieldName, returnTypeName.reloadedForTypeArgs(env))
.build()
}
return CodeBlock.builder()
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
.beginControlFlow("synchronized (this)")
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
.addStatement("%N=%L", cacheFieldName, instantiation.spec())
.endControlFlow()
.endControlFlow()
.endControlFlow()
.add("return ( %N as %T )", cacheFieldName, returnTypeName.reloadedForTypeArgs(env))
.build()
}

private fun motif.compiler.TypeName.reloadedForTypeArgs(env: XProcessingEnv): TypeName =
if (kt is ParameterizedTypeName) {
Expand Down
1 change: 1 addition & 0 deletions compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import motif.ast.compiler.CompilerMethod
* implementations.
*/
class ScopeImpl(
val useNullFieldInitialization: Boolean,
val className: ClassName,
val superClassName: ClassName,
val internalScope: Boolean,
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ private constructor(
fun create(): ScopeImpl {
val isInternal = (scope.clazz as? CompilerClass)?.isInternal() ?: false
return ScopeImpl(
(scope.clazz.annotations
.find { it.className == motif.Scope::class.java.name }!!
.annotationValueMap[SCOPE_ANNOTATION_FIELD_USE_NULL]
as? Boolean) ?: false,
scope.implClassName,
scope.typeName,
isInternal,
Expand Down Expand Up @@ -429,6 +433,7 @@ private constructor(

private const val OBJECTS_FIELD_NAME = "objects"
private const val DEPENDENCIES_FIELD_NAME = "dependencies"
private const val SCOPE_ANNOTATION_FIELD_USE_NULL = "useNullFieldInitialization"

fun create(env: XProcessingEnv, graph: ResolvedGraph): List<ScopeImpl> =
ScopeImplFactory(env, graph).create()
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.
#
GROUP=com.uber.motif
VERSION_NAME=0.4.0-alpha07-SNAPSHOT
VERSION_NAME=0.4.0-alpha08-SNAPSHOT
POM_DESCRIPTION=Simple DI API for Android / Java.
POM_URL=https://github.com/uber/motif/
POM_SCM_URL=https://github.com/uber/motif/
Expand All @@ -33,4 +33,4 @@ android.useAndroidX=true
android.enableJetifier=true

# https://github.com/Kotlin/dokka/issues/1405
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiField
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiSubstitutor
import java.util.Collections
import kotlin.jvm.java
import kotlin.jvm.javaClass
import kotlin.reflect.KClass
Expand Down Expand Up @@ -62,6 +63,9 @@ class IntelliJAnnotation(private val project: Project, private val psiAnnotation
override fun matchesClass(annotationClass: KClass<out Annotation>): Boolean =
psiAnnotation.qualifiedName == annotationClass.java.name

override val annotationValueMap: Map<String, Any?>
get() = Collections.emptyMap()

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand Down
8 changes: 7 additions & 1 deletion lib/src/main/java/motif/Scope.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@
*/
package motif;

public @interface Scope {}
public @interface Scope {
/**
* @return on false, the field will be initialized with [None.NONE]. Otherwise, null &
* [Initialized.INITIALIZED] will be used to skip the field initialization.
*/
boolean useNullFieldInitialization() default false;
}
8 changes: 4 additions & 4 deletions models/src/main/kotlin/motif/models/Scope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import motif.ast.IrClass
import motif.ast.IrType

/** [Wiki](https://github.com/uber/motif/wiki#scope) */
sealed class Scope(val clazz: IrClass) {

sealed class Scope(val useNullFieldInitialization: Boolean, val clazz: IrClass) {
val source by lazy { ScopeSource(this) }
val simpleName: String by lazy { clazz.simpleName }
val qualifiedName: String by lazy { clazz.qualifiedName }
Expand All @@ -38,15 +37,16 @@ sealed class Scope(val clazz: IrClass) {
}

class ErrorScope internal constructor(clazz: IrClass, val parsingError: ParsingError) :
Scope(clazz) {
Scope(useNullFieldInitialization = false, clazz) {
override val objects: Objects? = null
override val accessMethods: List<AccessMethod> = emptyList()
override val childMethods: List<ChildMethod> = emptyList()
override val factoryMethods: List<FactoryMethod> = emptyList()
override val dependencies: Dependencies? = null
}

class ValidScope internal constructor(clazz: IrClass) : Scope(clazz) {
class ValidScope internal constructor(clazz: IrClass, useNullFieldInitialization: Boolean = false) :
Scope(useNullFieldInitialization, clazz) {

init {
if (clazz.kind != IrClass.Kind.INTERFACE) throw ScopeMustBeAnInterface(clazz)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import javax.inject.Named
import motif.Creatable
import motif.Scope

@Scope
@Scope(useNullFieldInitialization = true)
interface MainScope : Creatable<MainScope.Dependencies> {

fun greeter(): Greeter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import motif.Scope;
import motif.sample.lib.controller.ControllerObjects;

@Scope
@Scope(useNullFieldInitialization = true)
public interface BottomHeaderScope {

BottomHeaderView view();
Expand Down
5 changes: 5 additions & 0 deletions samples/sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ android {
targetSdkVersion deps.build.targetSdkVersion
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

defaultConfig {
minSdkVersion deps.build.minSdkVersion
targetSdkVersion deps.build.targetSdkVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import motif.sample.lib.bottom_header.BottomHeaderScope;
import motif.sample.lib.controller.ControllerObjects;

@Scope
@Scope(useNullFieldInitialization = true)
public interface BottomSheetScope {

BottomSheetView view();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import motif.sample.lib.controller.ControllerObjects;
import motif.sample.lib.db.Photo;

@Scope
@Scope(useNullFieldInitialization = true)
public interface PhotoGridScope {

PhotoGridView view();
Expand Down
Loading