diff options
author | Yigit Boyar <yboyar@google.com> | 2017-12-04 12:53:31 -0800 |
---|---|---|
committer | Yigit Boyar <yboyar@google.com> | 2017-12-05 15:18:58 -0800 |
commit | fab603376f9ab1573ee214988ebce0d2e68510f1 (patch) | |
tree | fb5cb7456d3f38f4bfa4e8c5550d069c0f7dd80b /compiler/src/main/kotlin/android/databinding | |
parent | 510d4f6bf7a4d5ff3a87afaef839699abdfe82b4 (diff) | |
download | data-binding-fab603376f9ab1573ee214988ebce0d2e68510f1.tar.gz |
Generate data binding mappers in libraries
This CL changes compilation to generate mappers in libraries.
Then they are merged in the mapper generated by the app.
This is 1 step further to keep generated code in libraries.
Now we only have data binding component and BR left.
Bug: 68392907
Test: existing tests pass
Change-Id: Id3c68429fa157d9f7969890d0a4c8a344899a7f4
Diffstat (limited to 'compiler/src/main/kotlin/android/databinding')
4 files changed, 328 insertions, 146 deletions
diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/BRWriter.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/BRWriter.kt index 2de922e5..8eb31bd1 100644 --- a/compiler/src/main/kotlin/android/databinding/tool/writer/BRWriter.kt +++ b/compiler/src/main/kotlin/android/databinding/tool/writer/BRWriter.kt @@ -18,7 +18,7 @@ package android.databinding.tool.writer import android.databinding.tool.util.StringUtils -class BRWriter(properties: Set<String>, val useFinal : Boolean) { +class BRWriter(val properties: Set<String>, val useFinal : Boolean) { val indexedProps = properties.sorted().withIndex() fun write(pkg : String): String = "package $pkg;${StringUtils.LINE_SEPARATOR}$klass" val klass: String by lazy { diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriter.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriter.kt index fe417da7..44ddbfc3 100644 --- a/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriter.kt +++ b/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriter.kt @@ -16,10 +16,12 @@ package android.databinding.tool.writer import android.databinding.tool.DataBindingCompilerArgs import android.databinding.tool.LayoutBinder -class BindingMapperWriter(var pkg : String, var className: String, val layoutBinders : List<LayoutBinder>, +class BindingMapperWriter(var pkg : String, var className: String, + val layoutBinders : List<LayoutBinder>, val compilerArgs: DataBindingCompilerArgs) { private val appClassName : String = className private val testClassName = "Test$className" + private val baseMapperClassName = "android.databinding.DataBinderMapper" val generateAsTest = compilerArgs.isTestVariant && compilerArgs.isApp val generateTestOverride = !generateAsTest && compilerArgs.isEnabledForTests init { @@ -30,10 +32,13 @@ class BindingMapperWriter(var pkg : String, var className: String, val layoutBin fun write(brWriter : BRWriter) = kcode("") { nl("package $pkg;") nl("import ${compilerArgs.modulePackage}.BR;") - val extends = if (generateAsTest) "extends $appClassName" else "" + val extends = if (generateAsTest) { + "extends $appClassName" + } else { + "extends $baseMapperClassName" + } annotateWithGenerated() block("class $className $extends") { - nl("final static int TARGET_MIN_SDK = ${compilerArgs.minApi};") if (generateTestOverride) { nl("static $appClassName mTestOverride;") block("static") { @@ -51,6 +56,7 @@ class BindingMapperWriter(var pkg : String, var className: String, val layoutBin block("public $className()") { } nl("") + nl("@Override") block("public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId)") { block("switch(layoutId)") { layoutBinders.groupBy{it.layoutname }.forEach { @@ -81,7 +87,9 @@ class BindingMapperWriter(var pkg : String, var className: String, val layoutBin } nl("return null;") } - block("android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View[] views, int layoutId)") { + nl("@Override") + block("public android.databinding.ViewDataBinding getDataBinder(android.databinding" + + ".DataBindingComponent bindingComponent, android.view.View[] views, int layoutId)") { block("switch(layoutId)") { layoutBinders.filter{it.isMerge }.groupBy{it.layoutname }.forEach { val firstVal = it.value[0] @@ -108,8 +116,8 @@ class BindingMapperWriter(var pkg : String, var className: String, val layoutBin } nl("return null;") } - - block("int getLayoutId(String tag)") { + nl("@Override") + block("public int getLayoutId(String tag)") { block("if (tag == null)") { nl("return 0;"); } @@ -135,8 +143,8 @@ class BindingMapperWriter(var pkg : String, var className: String, val layoutBin } nl("return 0;") } - - block("String convertBrIdToString(int id)") { + nl("@Override") + block("public String convertBrIdToString(int id)") { block("if (id < 0 || id >= InnerBrLookup.sKeys.length)") { if (generateTestOverride) { block("if(mTestOverride != null)") { diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriterV2.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriterV2.kt index badf70cb..302769e3 100644 --- a/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriterV2.kt +++ b/compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriterV2.kt @@ -21,6 +21,7 @@ import android.databinding.tool.ext.L import android.databinding.tool.ext.N import android.databinding.tool.ext.S import android.databinding.tool.ext.T +import android.databinding.tool.ext.stripNonJava import android.databinding.tool.reflection.ModelAnalyzer import android.databinding.tool.store.GenClassInfoLog import com.squareup.javapoet.AnnotationSpec @@ -30,22 +31,22 @@ import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.FieldSpec import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.ParameterSpec +import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec +import java.util.Locale import javax.annotation.Generated import javax.lang.model.element.Modifier -class BindingMapperWriterV2(private val pkg: String, - private val appClassName: String, - private val genClassInfoLog: GenClassInfoLog, +class BindingMapperWriterV2(private val genClassInfoLog: GenClassInfoLog, private val compilerArgs: DataBindingCompilerArgs) { - private val testClassName = "Test$appClassName" - companion object { private val VIEW_DATA_BINDING = ClassName .get("android.databinding", "ViewDataBinding") private val COMPONENT = ClassName .get("android.databinding", "DataBindingComponent") + val DATA_BINDER_MAPPER: ClassName = ClassName + .get("android.databinding", "DataBinderMapper") private val VIEW = ClassName .get("android.view", "View") private val OBJECT = ClassName @@ -56,93 +57,170 @@ class BindingMapperWriterV2(private val pkg: String, .get("java.lang", "IllegalArgumentException") private val STRING = ClassName .get("java.lang", "String") + private val INTEGER = ClassName + .get("java.lang", "Integer") + private val LAYOUT_ID_LOOKUP_MAP_NAME = "INTERNAL_LAYOUT_ID_LOOKUP" + private val IMPL_CLASS_NAME = "DataBinderMapperImpl" + private val SPARSE_INT_ARRAY = + ClassName.get("android.util", "SparseIntArray") + private val SPARSE_ARRAY = + ClassName.get("android.util", "SparseArray") } - private val appTypeSpec = ClassName.bestGuess("$pkg.${this.appClassName}") - private val generateAsTest = compilerArgs.isTestVariant && compilerArgs.isApp - private val generateTestOverride = !generateAsTest && compilerArgs.isEnabledForTests - private val overrideField = FieldSpec.builder(appTypeSpec, "sTestOverride") - .addModifiers(Modifier.STATIC) - .build() - private val rClassMap = mutableMapOf<String, ClassName>() - val className = if (generateAsTest) { - "Test$appClassName" - } else { - appClassName + + val pkg : String + val className : String + init { + val generateAsTest = compilerArgs.isTestVariant && compilerArgs.isApp + if(generateAsTest) { + val testOverride = MergedBindingMapperWriter.TEST_OVERRIDE + pkg = testOverride.packageName() + className = testOverride.simpleName() + } else { + pkg = compilerArgs.modulePackage + className = IMPL_CLASS_NAME + } } + val qualifiedName = "$pkg.$className" + private fun getRClass(pkg: String): ClassName { return rClassMap.getOrPut(pkg) { ClassName.get(pkg, "R") } } - fun write(brWriter: BRWriter): TypeSpec = TypeSpec.classBuilder(className).apply { - if (generateAsTest) { - superclass(appTypeSpec) + /** + * Layout ids might be non-final while generating the mapper for a library. + * For that case, we generate an internal lookup table that converts an R file into a local + * known field value. + */ + private val localizedLayoutIdMap = mutableMapOf<String, LayoutId>() + + data class LayoutId(val pkg: String, val id: Int, val layoutName: String, + val fieldSpec: FieldSpec) + + private fun getLocalizedLayoutId(pkg: String, layoutName: String): FieldSpec { + val layoutId = localizedLayoutIdMap.getOrPut(layoutName) { + val fieldName = "LAYOUT_${layoutName.stripNonJava().toUpperCase(Locale.US)}" + // must be > 0 + val id = localizedLayoutIdMap.size + 1 + val spec = FieldSpec.builder(TypeName.INT, fieldName, + Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE) + .initializer(L, id) + .build() + LayoutId( + pkg = pkg, + layoutName = layoutName, + id = id, + fieldSpec = spec) } + return layoutId.fieldSpec + } + + fun write(brWriter: BRWriter): TypeSpec = TypeSpec.classBuilder(className).apply { + superclass(DATA_BINDER_MAPPER) + addModifiers(Modifier.PUBLIC) if (ModelAnalyzer.getInstance().hasGeneratedAnnotation()) { addAnnotation(AnnotationSpec.builder(Generated::class.java).apply { addMember("value", S, "Android Data Binding") }.build()) } - val minSdkField = FieldSpec.builder(TypeName.INT, "TARGET_MIN_SDK", - Modifier.FINAL, Modifier.STATIC).apply { - initializer(L, compilerArgs.minApi) - }.build() - addField(minSdkField) - if (generateTestOverride) { - addField(overrideField) - addStaticBlock(CodeBlock.builder() - .beginControlFlow("try").apply { - addStatement("$N = ($T) $T.class.getClassLoader().loadClass($S).newInstance()", - overrideField, appTypeSpec, appTypeSpec, """$pkg.$testClassName""") - }.nextControlFlow("catch($T ignored)", ClassName.get(Throwable::class.java)) - .apply { - addStatement("$N = null", overrideField) - } - .endControlFlow() - .build()) - } addMethod(generateGetViewDataBinder()) addMethod(generateGetViewArrayDataBinder()) addMethod(generateGetLayoutId()) addMethod(generateConvertBrIdToString()) addType(generateInnerBrLookup(brWriter)) + // must write this at the end + createLocalizedLayoutIds(this) }.build() + private fun createLocalizedLayoutIds(builder: TypeSpec.Builder) { + /** + * generated code looks like: + * private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = + * new SparseIntArray(99); + * static { + * INTERNAL_LAYOUT_ID_LOOKUP.put( + * foo.bar.R.layout.generic_view, LAYOUT_GENERICVIEW); + * ... //for all layouts + * } + */ + builder.apply { + // create fields + localizedLayoutIdMap.forEach { + addField(it.value.fieldSpec) + } + // now create conversion hash map + // reverse map from ids to values + val lookupType = SPARSE_INT_ARRAY + val lookupField = FieldSpec.builder( + lookupType, + LAYOUT_ID_LOOKUP_MAP_NAME) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer("new $T($L)", lookupType, localizedLayoutIdMap.size) + .build() + addField(lookupField) + addStaticBlock(CodeBlock.builder().apply { + localizedLayoutIdMap.values.forEach { + addStatement("$N.put($L.layout.$L, $N)", lookupField, + getRClass(it.pkg), it.layoutName, it.fieldSpec) + } + }.build()) + } + } + private fun generateInnerBrLookup(brWriter: BRWriter) = TypeSpec .classBuilder("InnerBrLookup").apply { + /** + * generated code looks like: + * static final SparseArray<String> sKeys = new SparseArray<String>(214); + * static { + * sKeys.put(foo.bar.BR._all, "_all"); + * ....//for all BRs + */ addModifiers(Modifier.PRIVATE, Modifier.STATIC) - val keysField = FieldSpec.builder(ArrayTypeName.of(STRING), "sKeys").apply { + val keysTypeName = ParameterizedTypeName.get( + SPARSE_ARRAY, + STRING + ) + val keysField = FieldSpec.builder(keysTypeName, "sKeys").apply { addModifiers(Modifier.STATIC, Modifier.FINAL) - val placeholders = brWriter.indexedProps.joinToString(",") { S } - val args = listOf(ArrayTypeName.of(STRING), "_all") + - brWriter.indexedProps.map { it.value } - initializer("new $T{$S, $placeholders}", *(args.toTypedArray())) + initializer("new $T($L)", keysTypeName, brWriter.properties.size + 1) }.build() addField(keysField) + addStaticBlock(CodeBlock.builder().apply { + addStatement("$N.put($L.BR.$L, $S)", + keysField, + compilerArgs.modulePackage, + "_all", + "_all") + brWriter.properties.forEach { + addStatement("$N.put($L.BR.$L, $S)", + keysField, + compilerArgs.modulePackage, + it, + it) + } + }.build()) }.build() private fun generateConvertBrIdToString() = MethodSpec .methodBuilder("convertBrIdToString").apply { + addModifiers(Modifier.PUBLIC) + addAnnotation(Override::class.java) val idParam = ParameterSpec.builder(TypeName.INT, "id").build() addParameter(idParam) - returns(BindingMapperWriterV2.STRING) - beginControlFlow("if($N < 0 || $N >= InnerBrLookup.sKeys.length)", - idParam, idParam).apply { - if (generateTestOverride) { - beginControlFlow("if($N != null)", overrideField).apply { - addStatement("return $N.convertBrIdToString($N)", overrideField, idParam) - }.endControlFlow() - addStatement("return null") - } - }.endControlFlow() - addStatement("return InnerBrLookup.sKeys[$N]", idParam) + returns(STRING) + val tmpResult = "tmpVal" + addStatement("$T $L = InnerBrLookup.sKeys.get($N)", STRING, tmpResult, idParam) + addStatement("return $L", tmpResult) }.build() private fun generateGetLayoutId() = MethodSpec.methodBuilder("getLayoutId").apply { + addModifiers(Modifier.PUBLIC) + addAnnotation(Override::class.java) val tagParam = ParameterSpec.builder(STRING, "tag").build() addParameter(tagParam) returns(TypeName.INT) @@ -164,8 +242,8 @@ class BindingMapperWriterV2(private val pkg: String, mapping.value.implementations .map { Pair(it, mapping) } } - .groupBy { pair -> - (pair.first.tag + "_0").hashCode() + .groupBy { (first) -> + (first.tag + "_0").hashCode() } .forEach { code, pairs -> beginControlFlow("case $L:", code).apply { @@ -180,17 +258,13 @@ class BindingMapperWriterV2(private val pkg: String, }.endControlFlow() } }.endControlFlow() - if (generateTestOverride) { - beginControlFlow("if($N != null)", overrideField).apply { - addStatement("return $N.getLayoutId($N)", overrideField, tagParam) - }.endControlFlow() - } addStatement("return 0") }.build() private fun generateGetViewDataBinder(): MethodSpec { return MethodSpec.methodBuilder("getDataBinder").apply { addModifiers(Modifier.PUBLIC) + addAnnotation(Override::class.java) returns(VIEW_DATA_BINDING) val componentParam = ParameterSpec.builder(COMPONENT, "component").build() val viewParam = ParameterSpec.builder(VIEW, "view").build() @@ -198,94 +272,111 @@ class BindingMapperWriterV2(private val pkg: String, addParameter(componentParam) addParameter(viewParam) addParameter(layoutIdParam) + val localizedLayoutId = "localizedLayoutId" + addStatement("$T $L = $L.get($N)", + TypeName.INT, + localizedLayoutId, + LAYOUT_ID_LOOKUP_MAP_NAME, + layoutIdParam) // output looks like: + // localize layout id from R.layout.XY to local private constant // switch(layoutId) // case known_layout_id // verify, generate impl and return - beginControlFlow("switch($N)", layoutIdParam).apply { - genClassInfoLog.mappings().forEach { layoutName, info -> - val rClass = getRClass(info.modulePackage) - beginControlFlow("case $T.layout.$L :", rClass, layoutName).apply { - // we should check the tag to decide which layout we need to inflate - // we do it here because it is ok to pass a non-data-binding layout - addStatement("final $T tag = $N.getTag()", OBJECT, viewParam) - beginControlFlow("if(tag == null)").apply { - addStatement("throw new $T($S)", RUNTIME_EXCEPTION, - "view must have a tag") + beginControlFlow("if($L > 0)", localizedLayoutId).apply { + addStatement("final $T tag = $N.getTag()", OBJECT, viewParam) + beginControlFlow("if(tag == null)").apply { + addStatement("throw new $T($S)", RUNTIME_EXCEPTION, + "view must have a tag") + }.endControlFlow() + beginControlFlow("switch($N)", localizedLayoutId).apply { + genClassInfoLog.mappings().forEach { layoutName, info -> + val layoutIdField = getLocalizedLayoutId(info.modulePackage, layoutName) + beginControlFlow("case $N:", layoutIdField).apply { + // we should check the tag to decide which layout we need to inflate + // we do it here because it is ok to pass a non-data-binding layout + info.implementations.forEach { + beginControlFlow("if ($S.equals(tag))", + "${it.tag}_0").apply { + val binderTypeName = ClassName.bestGuess(it.qualifiedName) + if (it.merge) { + addStatement("return new $T($N, new $T[]{$N})", + binderTypeName, componentParam, VIEW, viewParam) + } else { + addStatement("return new $T($N, $N)", + binderTypeName, componentParam, viewParam) + } + }.endControlFlow() + } + addStatement("throw new $T($S + tag)", ILLEGAL_ARG_EXCEPTION, + "The tag for $layoutName is invalid. Received: ") }.endControlFlow() - info.implementations.forEach { - beginControlFlow("if ($S.equals(tag))", - "${it.tag}_0").apply { - val binderTypeName = ClassName.bestGuess(it.qualifiedName) - if (it.merge) { - addStatement("return new $T($N, new $T[]{$N})", - binderTypeName, componentParam, VIEW, viewParam) - } else { - addStatement("return new $T($N, $N)", - binderTypeName, componentParam, viewParam) - } - }.endControlFlow() - } - addStatement("throw new $T($S + tag)", ILLEGAL_ARG_EXCEPTION, - "The tag for $layoutName is invalid. Received: ") - }.endControlFlow() - } - }.endControlFlow() - if (generateTestOverride) { - beginControlFlow("if($N != null)", overrideField).apply { - addStatement("return $N.getDataBinder($N, $N, $N)", - overrideField, componentParam, viewParam, layoutIdParam) + } }.endControlFlow() - } + }.endControlFlow() addStatement("return null") }.build() } - private fun generateGetViewArrayDataBinder() = MethodSpec.methodBuilder("getDataBinder").apply { - addModifiers(Modifier.PUBLIC) - returns(VIEW_DATA_BINDING) - val componentParam = ParameterSpec.builder(COMPONENT, "component").build() - val viewParam = ParameterSpec.builder(ArrayTypeName.of(VIEW), "views").build() - val layoutIdParam = ParameterSpec.builder(TypeName.INT, "layoutId").build() - addParameter(componentParam) - addParameter(viewParam) - addParameter(layoutIdParam) - // output looks like: - // switch(layoutId) - // case known_layout_id - // verify, generate impl and return - beginControlFlow("switch($N)", layoutIdParam).apply { - genClassInfoLog.mappings().forEach { layoutName, info -> - val mergeImpls = info.implementations.filter { it.merge } - if (mergeImpls.isNotEmpty()) { - val rClass = getRClass(info.modulePackage) - beginControlFlow("case $T.layout.$L:", rClass, layoutName).apply { - // we should check the tag to decide which layout we need to inflate - // we do it here because it is ok to pass non-data-binding view. - addStatement("final $T tag = $N[0].getTag()", OBJECT, viewParam) - beginControlFlow("if(tag == null)").apply { - addStatement("throw new $T($S)", RUNTIME_EXCEPTION, - "view must have a tag") - }.endControlFlow() + private fun generateGetViewArrayDataBinder() = MethodSpec.methodBuilder("getDataBinder") + .apply { + addModifiers(Modifier.PUBLIC) + addAnnotation(Override::class.java) + returns(VIEW_DATA_BINDING) + val componentParam = ParameterSpec.builder(COMPONENT, "component").build() + val viewParam = ParameterSpec.builder(ArrayTypeName.of(VIEW), "views").build() + val layoutIdParam = ParameterSpec.builder(TypeName.INT, "layoutId").build() + addParameter(componentParam) + addParameter(viewParam) + addParameter(layoutIdParam) + beginControlFlow("if($N == null || $N.length == 0)", + viewParam, viewParam).apply { + addStatement("return null") + }.endControlFlow() + + val localizedLayoutId = "localizedLayoutId" + addStatement("$T $L = $L.get($N)", + TypeName.INT, + localizedLayoutId, + LAYOUT_ID_LOOKUP_MAP_NAME, + layoutIdParam) + // output looks like: + // localize layout id from R.layout.XY to local private constant + // switch(layoutId) + // case known_layout_id + // verify, generate impl and return - mergeImpls.forEach { - beginControlFlow("if($S.equals(tag))", - "${it.tag}_0").apply { - val binderTypeName = ClassName.bestGuess(it.qualifiedName) - addStatement("return new $T($N, $N)", - binderTypeName, componentParam, viewParam) - }.endControlFlow() + beginControlFlow("if($L > 0)", localizedLayoutId).apply { + addStatement("final $T tag = $N[0].getTag()", OBJECT, viewParam) + beginControlFlow("if(tag == null)").apply { + addStatement("throw new $T($S)", RUNTIME_EXCEPTION, + "view must have a tag") + }.endControlFlow() + beginControlFlow("switch($N)", localizedLayoutId).apply { + genClassInfoLog.mappings().forEach { layoutName, info -> + val mergeImpls = info.implementations.filter { it.merge } + if (mergeImpls.isNotEmpty()) { + val layoutIdField = getLocalizedLayoutId( + info.modulePackage, + layoutName) + beginControlFlow("case $N:", layoutIdField).apply { + mergeImpls.forEach { + beginControlFlow("if($S.equals(tag))", + "${it.tag}_0").apply { + val binderTypeName = ClassName.bestGuess( + it.qualifiedName) + addStatement("return new $T($N, $N)", + binderTypeName, componentParam, viewParam) + }.endControlFlow() + } + addStatement("throw new $T($S + tag)", ILLEGAL_ARG_EXCEPTION, + "The tag for $layoutName is invalid. Received: ") + }.endControlFlow() + } } }.endControlFlow() - } - } - }.endControlFlow() - if (generateTestOverride) { - beginControlFlow("if($N != null)", overrideField).apply { - addStatement("return $N.getDataBinder($N, $N, $N)", - overrideField, componentParam, viewParam, layoutIdParam) - }.endControlFlow() - } - addStatement("return null") - }.build() + }.endControlFlow() + + addStatement("return null") + }.build() } diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/MergedBindingMapperWriter.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/MergedBindingMapperWriter.kt new file mode 100644 index 00000000..b60b6d41 --- /dev/null +++ b/compiler/src/main/kotlin/android/databinding/tool/writer/MergedBindingMapperWriter.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.databinding.tool.writer + +import android.databinding.tool.DataBindingCompilerArgs +import android.databinding.tool.ext.N +import android.databinding.tool.ext.S +import android.databinding.tool.ext.T +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.CodeBlock +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.TypeSpec +import javax.lang.model.element.Modifier + +class MergedBindingMapperWriter(private val packages: List<String>, + compilerArgs: DataBindingCompilerArgs) { + private val generateAsTest = compilerArgs.isTestVariant && compilerArgs.isApp + private val generateTestOverride = !generateAsTest && compilerArgs.isEnabledForTests + private val overrideField = FieldSpec.builder(BindingMapperWriterV2.DATA_BINDER_MAPPER, + "sTestOverride") + .addModifiers(Modifier.STATIC) + .build() + + companion object { + private val APP_CLASS_NAME = "DataBinderMapperImpl" + private val TEST_CLASS_NAME = "Test$APP_CLASS_NAME" + val MERGED_MAPPER_BASE: ClassName = ClassName.get( + "android.databinding", + "MergedDataBinderMapper") + internal val TEST_OVERRIDE: ClassName = ClassName.get( + "android.databinding", + TEST_CLASS_NAME) + } + + val pkg = "android.databinding" + val qualifiedName = "$pkg.$APP_CLASS_NAME" + + fun write() = TypeSpec.classBuilder(APP_CLASS_NAME).apply { + superclass(MERGED_MAPPER_BASE) + addModifiers(Modifier.PUBLIC) + addMethod(MethodSpec.constructorBuilder().apply { + packages.forEach { pkg -> + val mapper = ClassName.get(pkg, APP_CLASS_NAME) + addStatement("addMapper(new $T())", mapper) + } + if (generateTestOverride) { + beginControlFlow("if($N != null)", overrideField).apply { + addStatement("addMapper($N)", overrideField) + }.endControlFlow() + } + }.build()) + if (generateTestOverride) { + addField(overrideField) + addStaticBlock(CodeBlock.builder() + .beginControlFlow("try").apply { + addStatement("$N = ($T) $T.class.getClassLoader().loadClass($S).newInstance()", + overrideField, BindingMapperWriterV2.DATA_BINDER_MAPPER, + BindingMapperWriterV2.DATA_BINDER_MAPPER, + TEST_OVERRIDE) + }.nextControlFlow("catch($T ignored)", ClassName.get(Throwable::class.java)) + .apply { + addStatement("$N = null", overrideField) + } + .endControlFlow() + .build()) + } + }.build()!! +} |