blob: 140f6b863f6a406d0c499dc853bf5f2e46d3330a [file] [log] [blame]
/*
* Copyright (C) 2015 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.Binding
import android.databinding.tool.BindingTarget
import android.databinding.tool.CallbackWrapper
import android.databinding.tool.InverseBinding
import android.databinding.tool.LayoutBinder
import android.databinding.tool.expr.Expr
import android.databinding.tool.expr.ExprModel
import android.databinding.tool.expr.FieldAccessExpr
import android.databinding.tool.expr.IdentifierExpr
import android.databinding.tool.expr.LambdaExpr
import android.databinding.tool.expr.ListenerExpr
import android.databinding.tool.expr.ResourceExpr
import android.databinding.tool.expr.TernaryExpr
import android.databinding.tool.expr.localizeGlobalVariables
import android.databinding.tool.expr.shouldLocalizeInCallbacks
import android.databinding.tool.expr.toCode
import android.databinding.tool.ext.androidId
import android.databinding.tool.ext.br
import android.databinding.tool.ext.joinToCamelCaseAsVar
import android.databinding.tool.ext.lazyProp
import android.databinding.tool.ext.versionedLazy
import android.databinding.tool.processing.ErrorMessages
import android.databinding.tool.reflection.ModelAnalyzer
import android.databinding.tool.reflection.ModelClass
import android.databinding.tool.util.L
import android.databinding.tool.util.Preconditions
import java.util.ArrayList
import java.util.Arrays
import java.util.BitSet
import java.util.HashMap
fun String.stripNonJava() = this.split("[^a-zA-Z0-9]".toRegex()).map{ it.trim() }.joinToCamelCaseAsVar()
enum class Scope {
GLOBAL,
FIELD,
METHOD,
FLAG,
EXECUTE_PENDING_METHOD,
CONSTRUCTOR_PARAM,
CALLBACK;
companion object {
var currentScope = GLOBAL;
private val scopeStack = arrayListOf<Scope>()
fun enter(scope : Scope) {
scopeStack.add(currentScope)
currentScope = scope
}
fun exit() {
currentScope = scopeStack.removeAt(scopeStack.size - 1)
}
fun reset() {
scopeStack.clear()
currentScope = GLOBAL
}
}
}
class ExprModelExt {
val usedFieldNames = hashMapOf<Scope, MutableSet<String>>();
init {
Scope.values().forEach { usedFieldNames[it] = hashSetOf<String>() }
}
internal val forceLocalize = hashSetOf<Expr>()
val localizedFlags = arrayListOf<FlagSet>()
fun localizeFlag(set : FlagSet, name:String) : FlagSet {
localizedFlags.add(set)
val result = getUniqueName(name, Scope.FLAG, false)
set.localName = result
return set
}
fun getUniqueName(base : String, scope : Scope, isPublic : kotlin.Boolean) : String {
var candidateBase = base
var candidate = candidateBase
if (scope == Scope.CALLBACK || scope == Scope.EXECUTE_PENDING_METHOD) {
candidate = candidate.decapitalize()
}
val checkFields = scope != Scope.METHOD
var i = 0
while (usedFieldNames[scope]!!.contains(candidate)
|| (checkFields && usedFieldNames[Scope.FIELD]!!.contains(candidate))) {
i ++
candidate = candidateBase + i
}
usedFieldNames[scope]!!.add(candidate)
return candidate
}
}
fun ModelClass.defaultValue() = ModelAnalyzer.getInstance().getDefaultValue(toJavaCode())
fun ExprModel.getUniqueFieldName(base : String, isPublic : kotlin.Boolean) : String = ext.getUniqueName(base, Scope.FIELD, isPublic)
fun ExprModel.getUniqueMethodName(base : String, isPublic : kotlin.Boolean) : String = ext.getUniqueName(base, Scope.METHOD, isPublic)
fun ExprModel.getConstructorParamName(base : String) : String = ext.getUniqueName(base, Scope.CONSTRUCTOR_PARAM, false)
fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
val Expr.needsLocalField by lazyProp { expr : Expr ->
expr.canBeEvaluatedToAVariable() && !(expr.isVariable() && !expr.isUsed) && (expr.isDynamic || expr is ResourceExpr)
}
fun Expr.isForcedToLocalize() = model.ext.forceLocalize.contains(this)
// not necessarily unique. Uniqueness is solved per scope
val BindingTarget.readableName by lazyProp { target: BindingTarget ->
if (target.id == null) {
"boundView" + indexFromTag(target.tag)
} else {
target.id.androidId().stripNonJava()
}
}
fun BindingTarget.superConversion(variable : String) : String {
if (resolvedType != null && resolvedType.extendsViewStub()) {
return "new android.databinding.ViewStubProxy((android.view.ViewStub) $variable)"
} else {
return "($interfaceClass) $variable"
}
}
val BindingTarget.fieldName : String by lazyProp { target : BindingTarget ->
val name : String
val isPublic : kotlin.Boolean
if (target.id == null) {
name = "m${target.readableName}"
isPublic = false
} else {
name = target.readableName
isPublic = true
}
target.model.getUniqueFieldName(name, isPublic)
}
val BindingTarget.androidId by lazyProp { target : BindingTarget ->
if (target.id.startsWith("@android:id/")) {
"android.R.id.${target.id.androidId()}"
} else {
"R.id.${target.id.androidId()}"
}
}
val BindingTarget.interfaceClass by lazyProp { target : BindingTarget ->
if (target.resolvedType != null && target.resolvedType.extendsViewStub()) {
"android.databinding.ViewStubProxy"
} else {
target.interfaceType
}
}
val BindingTarget.constructorParamName by lazyProp { target : BindingTarget ->
target.model.getConstructorParamName(target.readableName)
}
// not necessarily unique. Uniqueness is decided per scope
val Expr.readableName by lazyProp { expr : Expr ->
val stripped = expr.uniqueKey.stripNonJava()
L.d("readableUniqueName for [%s] %s is %s", System.identityHashCode(expr), expr, stripped)
stripped
}
val Expr.fieldName by lazyProp { expr : Expr ->
expr.model.getUniqueFieldName("m${expr.readableName.capitalize()}", false)
}
val InverseBinding.fieldName by lazyProp { inverseBinding : InverseBinding ->
val targetName = inverseBinding.target.fieldName;
val eventName = inverseBinding.eventAttribute.stripNonJava()
inverseBinding.model.getUniqueFieldName("$targetName$eventName", false)
}
val Expr.listenerClassName by lazyProp { expr : Expr ->
expr.model.getUniqueFieldName("${expr.resolvedType.simpleName}Impl", false)
}
val Expr.oldValueName by lazyProp { expr : Expr ->
expr.model.getUniqueFieldName("mOld${expr.readableName.capitalize()}", false)
}
fun Expr.scopedName() : String = when(Scope.currentScope) {
Scope.CALLBACK -> callbackLocalName
else -> executePendingLocalName
}
val Expr.callbackLocalName by lazyProp { expr : Expr ->
if(expr.shouldLocalizeInCallbacks()) "${expr.model.ext.getUniqueName(expr.readableName, Scope.CALLBACK, false)}"
else expr.toCode().generate()
}
val Expr.executePendingLocalName by lazyProp { expr : Expr ->
if(expr.isDynamic || expr.needsLocalField) "${expr.model.ext.getUniqueName(expr.readableName, Scope.EXECUTE_PENDING_METHOD, false)}"
else expr.toCode().generate()
}
val Expr.setterName by lazyProp { expr : Expr ->
expr.model.getUniqueMethodName("set${expr.readableName.capitalize()}", true)
}
val Expr.onChangeName by lazyProp { expr : Expr ->
expr.model.getUniqueMethodName("onChange${expr.readableName.capitalize()}", false)
}
val Expr.getterName by lazyProp { expr : Expr ->
expr.model.getUniqueMethodName("get${expr.readableName.capitalize()}", true)
}
fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic
val Expr.dirtyFlagSet by lazyProp { expr : Expr ->
FlagSet(expr.invalidFlags, expr.model.flagBucketCount)
}
val Expr.invalidateFlagSet by lazyProp { expr : Expr ->
FlagSet(expr.id)
}
val Expr.shouldReadFlagSet by versionedLazy { expr : Expr ->
FlagSet(expr.shouldReadFlags, expr.model.flagBucketCount)
}
val Expr.shouldReadWithConditionalsFlagSet by versionedLazy { expr : Expr ->
FlagSet(expr.shouldReadFlagsWithConditionals, expr.model.flagBucketCount)
}
val Expr.conditionalFlags by lazyProp { expr : Expr ->
arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
FlagSet(expr.getRequirementFlagIndex(true)))
}
fun Binding.toAssignmentCode() : String {
val fieldName: String
if (this.target.viewClass.
equals(this.target.interfaceType)) {
fieldName = "this.${this.target.fieldName}"
} else {
fieldName = "((${this.target.viewClass}) this.${this.target.fieldName})"
}
return this.toJavaCode(fieldName, "this.mBindingComponent")
}
val LayoutBinder.requiredComponent by lazyProp { layoutBinder: LayoutBinder ->
val requiredFromBindings = layoutBinder.
bindingTargets.
flatMap { it.bindings }.
firstOrNull { it.bindingAdapterInstanceClass != null }?.bindingAdapterInstanceClass
val requiredFromInverse = layoutBinder.
bindingTargets.
flatMap { it.inverseBindings }.
firstOrNull { it.bindingAdapterInstanceClass != null }?.bindingAdapterInstanceClass
requiredFromBindings ?: requiredFromInverse
}
fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
buckets.withIndex().forEach {
if (it.value != 0L) {
cb(getWordSuffix(it.index), buckets[it.index])
}
}
}
fun getWordSuffix(wordIndex : Int) : String {
return if(wordIndex == 0) "" else "_$wordIndex"
}
fun FlagSet.localValue(bucketIndex : Int) =
if (localName == null) binaryCode(bucketIndex)
else "$localName${getWordSuffix(bucketIndex)}"
fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
fun longToBinary(l : Long) = "0x${java.lang.Long.toHexString(l)}L"
fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
val min = Math.min(buckets.size, other.buckets.size)
val result = arrayListOf<T>()
for (i in 0..(min - 1)) {
// if these two can match by any chance, call the callback
if (intersect(other, i)) {
result.add(cb(getWordSuffix(i), i))
}
}
return result
}
fun indexFromTag(tag : String) : kotlin.Int {
val startIndex : kotlin.Int
if (tag.startsWith("binding_")) {
startIndex = "binding_".length;
} else {
startIndex = tag.lastIndexOf('_') + 1
}
return Integer.parseInt(tag.substring(startIndex))
}
class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
val model = layoutBinder.model
val indices = HashMap<BindingTarget, kotlin.Int>()
val mDirtyFlags by lazy {
val fs = FlagSet(BitSet(), model.flagBucketCount);
Arrays.fill(fs.buckets, -1)
fs.isDynamic = true
model.localizeFlag(fs, "mDirtyFlags")
fs
}
val className = layoutBinder.implementationName
val baseClassName = "${layoutBinder.className}"
val includedBinders by lazy {
layoutBinder.bindingTargets.filter { it.isBinder }
}
val variables by lazy {
model.exprMap.values.filterIsInstance(IdentifierExpr::class.java).filter { it.isVariable() }
}
val callbacks by lazy {
model.exprMap.values.filterIsInstance(LambdaExpr::class.java)
}
fun write(minSdk : kotlin.Int) : String {
Scope.reset()
layoutBinder.resolveWhichExpressionsAreUsed()
calculateIndices();
return kcode("package ${layoutBinder.`package`};") {
nl("import ${layoutBinder.modulePackage}.R;")
nl("import ${layoutBinder.modulePackage}.BR;")
nl("import android.support.annotation.NonNull;")
nl("import android.support.annotation.Nullable;")
nl("import android.view.View;")
val classDeclaration : String
if (layoutBinder.hasVariations()) {
classDeclaration = "$className extends $baseClassName"
} else {
classDeclaration = "$className extends android.databinding.ViewDataBinding"
}
nl("@SuppressWarnings(\"unchecked\")")
annotateWithGenerated()
block("public class $classDeclaration ${buildImplements()}") {
nl(declareIncludeViews())
nl(declareViews())
nl(declareVariables())
nl(declareBoundValues())
nl(declareListeners())
try {
Scope.enter(Scope.GLOBAL)
nl(declareInverseBindingImpls());
} finally {
Scope.exit()
}
nl(declareConstructor(minSdk))
nl(declareInvalidateAll())
nl(declareHasPendingBindings())
nl(declareSetVariable())
nl(variableSettersAndGetters())
nl(onFieldChange())
try {
Scope.enter(Scope.GLOBAL)
nl(executePendingBindings())
} finally {
Scope.exit()
}
nl(declareListenerImpls())
try {
Scope.enter(Scope.CALLBACK)
nl(declareCallbackImplementations())
} finally {
Scope.exit()
}
nl(declareDirtyFlags())
if (!layoutBinder.hasVariations()) {
nl(declareFactories())
}
nl(flagMapping())
nl("//end")
}
}.generate()
}
fun buildImplements() : String {
return if (callbacks.isEmpty()) {
""
} else {
"implements " + callbacks.map { it.callbackWrapper.cannonicalListenerName }.distinct().joinToString(", ")
}
}
fun calculateIndices() : Unit {
val taggedViews = layoutBinder.bindingTargets.filter{
it.isUsed && it.tag != null && !it.isBinder
}
taggedViews.filter {
it.includedLayout == null
}.forEach {
indices.put(it, indexFromTag(it.tag))
}
// put any included layouts after the normal views
taggedViews.filter {
it.includedLayout != null
}.forEach {
indices.put(it, maxIndex() + 1)
}
val indexStart = maxIndex() + 1
layoutBinder.bindingTargets.filter{
it.isUsed && !taggedViews.contains(it)
}.withIndex().forEach {
indices.put(it.value, it.index + indexStart)
}
}
fun declareIncludeViews() = kcode("") {
nl("@Nullable")
nl("private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;")
nl("@Nullable")
nl("private static final android.util.SparseIntArray sViewsWithIds;")
nl("static {") {
val hasBinders = layoutBinder.bindingTargets.firstOrNull{ it.isUsed && it.isBinder } != null
if (!hasBinders) {
tab("sIncludes = null;")
} else {
val numBindings = layoutBinder.bindingTargets.filter{ it.isUsed }.count()
tab("sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts($numBindings);")
val includeMap = HashMap<BindingTarget, ArrayList<BindingTarget>>()
layoutBinder.bindingTargets.filter{ it.isUsed && it.isBinder }.forEach {
val includeTag = it.tag;
val parent = layoutBinder.bindingTargets.firstOrNull {
it.isUsed && !it.isBinder && includeTag.equals(it.tag)
} ?: throw IllegalStateException("Could not find parent of include file")
var list = includeMap[parent]
if (list == null) {
list = ArrayList<BindingTarget>()
includeMap.put(parent, list)
}
list.add(it)
}
includeMap.keys.forEach {
val index = indices[it]
tab("sIncludes.setIncludes($index, ") {
tab ("new String[] {${
includeMap[it]!!.map {
"\"${it.includedLayout}\""
}.joinToString(", ")
}},")
tab("new int[] {${
includeMap[it]!!.map {
"${indices[it]}"
}.joinToString(", ")
}},")
tab("new int[] {${
includeMap[it]!!.map {
"R.layout.${it.includedLayout}"
}.joinToString(", ")
}});")
}
}
}
val viewsWithIds = layoutBinder.bindingTargets.filter {
it.isUsed && !it.isBinder && (!it.supportsTag() || (it.id != null && (it.tag == null || it.includedLayout != null)))
}
if (viewsWithIds.isEmpty()) {
tab("sViewsWithIds = null;")
} else {
tab("sViewsWithIds = new android.util.SparseIntArray();")
viewsWithIds.forEach {
tab("sViewsWithIds.put(${it.androidId}, ${indices[it]});")
}
}
}
nl("}")
}
fun maxIndex() : kotlin.Int {
val maxIndex = indices.values.max()
if (maxIndex == null) {
return -1
} else {
return maxIndex
}
}
fun declareConstructor(minSdk : kotlin.Int) = kcode("") {
val bindingCount = maxIndex() + 1
val parameterType : String
val superParam : String
if (layoutBinder.isMerge) {
parameterType = "View[]"
superParam = "root[0]"
} else {
parameterType = "View"
superParam = "root"
}
val rootTagsSupported = minSdk >= 14
if (layoutBinder.hasVariations()) {
nl("")
nl("public $className(@Nullable android.databinding.DataBindingComponent bindingComponent, @NonNull $parameterType root) {") {
tab("this(bindingComponent, root, mapBindings(bindingComponent, root, $bindingCount, sIncludes, sViewsWithIds));")
}
nl("}")
nl("private $className(android.databinding.DataBindingComponent bindingComponent, $parameterType root, Object[] bindings) {") {
tab("super(bindingComponent, $superParam, ${model.observables.size}") {
layoutBinder.sortedTargets.filter { it.id != null }.forEach {
tab(", ${fieldConversion(it)}")
}
tab(");")
}
}
} else {
nl("public $baseClassName(@NonNull android.databinding.DataBindingComponent bindingComponent, @NonNull $parameterType root) {") {
tab("super(bindingComponent, $superParam, ${model.observables.size});")
tab("final Object[] bindings = mapBindings(bindingComponent, root, $bindingCount, sIncludes, sViewsWithIds);")
}
}
if (layoutBinder.requiredComponent != null) {
tab("ensureBindingComponentIsNotNull(${layoutBinder.requiredComponent}.class);")
}
val taggedViews = layoutBinder.sortedTargets.filter{it.isUsed }
taggedViews.forEach {
if (!layoutBinder.hasVariations() || it.id == null) {
tab("this.${it.fieldName} = ${fieldConversion(it)};")
if (it.isBinder) {
tab("setContainedBinding(this.${it.fieldName});")
}
}
if (!it.isBinder) {
if (it.resolvedType != null && it.resolvedType.extendsViewStub()) {
tab("this.${it.fieldName}.setContainingBinding(this);")
}
if (it.supportsTag() && it.tag != null &&
(rootTagsSupported || it.tag.startsWith("binding_"))) {
val originalTag = it.originalTag;
var tagValue = "null"
if (originalTag != null && !originalTag.startsWith("@{")) {
tagValue = "\"$originalTag\""
if (originalTag.startsWith("@")) {
var packageName = layoutBinder.modulePackage
if (originalTag.startsWith("@android:")) {
packageName = "android"
}
val slashIndex = originalTag.indexOf('/')
val resourceId = originalTag.substring(slashIndex + 1)
tagValue = "this.${it.fieldName}.getResources().getString($packageName.R.string.$resourceId)"
}
}
if (it.includedLayout == null) {
tab("this.${it.fieldName}.setTag($tagValue);")
}
} else if (it.tag != null && !it.tag.startsWith("binding_") &&
it.originalTag != null) {
L.e(ErrorMessages.ROOT_TAG_NOT_SUPPORTED, it.originalTag)
}
}
}
tab("setRootTag(root);")
tab(declareCallbackInstances())
tab("invalidateAll();");
nl("}")
}
fun declareCallbackInstances() = kcode("// listeners") {
callbacks.groupBy { it.callbackWrapper.minApi }
.forEach {
if (it.key > 1) {
block("if(getBuildSdkInt() < ${it.key})") {
it.value.forEach { lambda ->
nl("${lambda.fieldName} = null;")
}
}
block("else") {
it.value.forEach { lambda ->
nl("${lambda.fieldName} = ${lambda.generateConstructor()};")
}
}
} else {
it.value.forEach { lambda ->
nl("${lambda.fieldName} = ${lambda.generateConstructor()};")
}
}
}
}
fun declareCallbackImplementations() = kcode("// callback impls") {
callbacks.groupBy { it.callbackWrapper }.forEach {
val wrapper = it.key
val lambdas = it.value
val shouldReturn = !wrapper.method.returnType.isVoid
if (shouldReturn) {
lambdas.forEach {
it.callbackExprModel.ext.forceLocalize.add(it.expr)
}
}
block("public final ${wrapper.method.returnType.canonicalName} ${wrapper.listenerMethodName}(${wrapper.allArgsWithTypes()})") {
Preconditions.check(lambdas.size > 0, "bindings list should not be empty")
if (lambdas.size == 1) {
val lambda = lambdas[0]
nl(lambda.callbackExprModel.localizeGlobalVariables(lambda))
nl(lambda.executionPath.toCode())
if (shouldReturn) {
nl("return ${lambda.expr.scopedName()};")
}
} else {
block("switch(${CallbackWrapper.SOURCE_ID})") {
lambdas.forEach { lambda ->
block("case ${lambda.callbackId}:") {
nl(lambda.callbackExprModel.localizeGlobalVariables(lambda))
nl(lambda.executionPath.toCode())
if (shouldReturn) {
nl("return ${lambda.expr.scopedName()};")
} else {
nl("break;")
}
}
}
if (shouldReturn) {
block("default:") {
nl("return ${wrapper.method.returnType.defaultValue()};")
}
}
}
}
}
}
}
fun fieldConversion(target : BindingTarget) : String {
if (!target.isUsed) {
return "null"
} else {
val index = indices[target] ?: throw IllegalStateException("Unknown binding target")
val variableName = "bindings[$index]"
return target.superConversion(variableName)
}
}
fun declareInvalidateAll() = kcode("") {
nl("@Override")
block("public void invalidateAll()") {
val fs = FlagSet(layoutBinder.model.invalidateAnyBitSet,
layoutBinder.model.flagBucketCount);
block("synchronized(this)") {
for (i in (0..(mDirtyFlags.buckets.size - 1))) {
tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
}
}
includedBinders.filter{it.isUsed }.forEach { binder ->
nl("${binder.fieldName}.invalidateAll();")
}
nl("requestRebind();");
}
}
fun declareHasPendingBindings() = kcode("") {
nl("@Override")
nl("public boolean hasPendingBindings() {") {
if (mDirtyFlags.buckets.size > 0) {
tab("synchronized(this) {") {
val flagCheck = 0.rangeTo(mDirtyFlags.buckets.size - 1).map {
"${mDirtyFlags.localValue(it)} != 0"
}.joinToString(" || ")
tab("if ($flagCheck) {") {
tab("return true;")
}
tab("}")
}
tab("}")
}
includedBinders.filter{it.isUsed }.forEach { binder ->
tab("if (${binder.fieldName}.hasPendingBindings()) {") {
tab("return true;")
}
tab("}")
}
tab("return false;")
}
nl("}")
}
fun declareSetVariable() = kcode("") {
nl("@Override")
block("public boolean setVariable(int variableId, @Nullable Object variable) ") {
nl("boolean variableSet = true;")
variables.forEachIndexed { i, expr ->
val elseStr : String
if (i == 0) {
elseStr = ""
} else {
elseStr = "else "
}
block ("${elseStr}if (${expr.name.br()} == variableId)") {
nl("${expr.setterName}((${expr.resolvedType.toJavaCode()}) variable);")
}
}
if (variables.isNotEmpty()) {
block("else") {
nl("variableSet = false;")
}
}
tab("return variableSet;")
}
}
fun variableSettersAndGetters() = kcode("") {
variables.forEach {
if (it.userDefinedType != null) {
block("public void ${it.setterName}(${if (it.resolvedType.isPrimitive) "" else "@Nullable "}${it.resolvedType.toJavaCode()} ${it.readableName})") {
val used = it.isIsUsedInCallback || it.isUsed
if (used && it.isObservable) {
nl("updateRegistration(${it.id}, ${it.readableName});");
}
nl("this.${it.fieldName} = ${it.readableName};")
if (used) {
// set dirty flags!
val flagSet = it.invalidateFlagSet
block("synchronized(this)") {
mDirtyFlags.mapOr(flagSet) { suffix, index ->
nl("${mDirtyFlags.localName}$suffix |= ${flagSet.localValue(index)};")
}
}
nl("notifyPropertyChanged(${it.name.br()});")
nl("super.requestRebind();")
}
}
// if there are variations, we'll use base class to generate the fields and their
// getters
if (!layoutBinder.hasVariations()) {
nl("")
if (!it.resolvedType.isPrimitive) {
nl("@Nullable")
}
block("public ${it.resolvedType.toJavaCode()} ${it.getterName}()") {
nl("return ${it.fieldName};")
}
}
}
}
}
fun onFieldChange() = kcode("") {
nl("@Override")
nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
tab("switch (localFieldId) {") {
model.observables.forEach {
tab("case ${it.id} :") {
tab("return ${it.onChangeName}((${it.resolvedType.toJavaCode()}) object, fieldId);")
}
}
}
tab("}")
tab("return false;")
}
nl("}")
nl("")
model.observables.forEach {
block("private boolean ${it.onChangeName}(${it.resolvedType.toJavaCode()} ${it.readableName}, int fieldId)") {
block("if (fieldId == ${"".br()})") {
val flagSet : FlagSet
if (it is FieldAccessExpr && it.resolvedType.isObservableField) {
flagSet = it.bindableDependents.map { expr -> expr.invalidateFlagSet }
.foldRight(it.invalidateFlagSet) { l, r -> l.or(r) }
} else {
flagSet = it.invalidateFlagSet
}
block("synchronized(this)") {
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.localName}$suffix |= ${flagSet.localValue(index)};")
}
}
nl("return true;")
}
val accessedFields: List<FieldAccessExpr> = it.parents.filterIsInstance(FieldAccessExpr::class.java)
accessedFields.filter { it.isUsed && it.hasBindableAnnotations() }
.flatMap { expr -> expr.dirtyingProperties.map { Pair(it, expr)} }
.groupBy { it.first }
.forEach {
// If two expressions look different but resolve to the same method,
// we are not yet able to merge them. This is why we merge their
// flags below.
block("else if (fieldId == ${it.key})") {
block("synchronized(this)") {
val flagSet = it.value.foldRight(FlagSet()) { l, r -> l.second.invalidateFlagSet.or(r) }
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.localValue(index)} |= ${flagSet.localValue(index)};")
}
}
nl("return true;")
}
}
nl("return false;")
}
nl("")
}
}
fun declareViews() = kcode("// views") {
val oneLayout = !layoutBinder.hasVariations();
layoutBinder.sortedTargets.filter {it.isUsed && (oneLayout || it.id == null)}.forEach {
val access : String
if (oneLayout && it.id != null) {
access = "public"
} else {
access = "private"
}
nl(if (it.includedLayout == null) "@NonNull" else "@Nullable")
nl("$access final ${it.interfaceClass} ${it.fieldName};")
}
}
fun declareVariables() = kcode("// variables") {
//if it has variations, fields are declared in the base class as well as getters
if (!layoutBinder.hasVariations()) {
variables.forEach {
nl("@Nullable")
nl("private ${it.resolvedType.toJavaCode()} ${it.fieldName};")
}
}
callbacks.forEach {
val wrapper = it.callbackWrapper
if (!wrapper.klass.isPrimitive) {
nl("@Nullable")
}
nl("private final ${wrapper.klass.canonicalName} ${it.fieldName}").app(";")
}
}
fun declareBoundValues() = kcode("// values") {
layoutBinder.sortedTargets.filter { it.isUsed }
.flatMap { it.bindings }
.filter { it.requiresOldValue() }
.flatMap{ it.componentExpressions.toList() }
.groupBy { it }
.forEach {
val expr = it.key
nl("private ${expr.resolvedType.toJavaCode()} ${expr.oldValueName};")
}
}
fun declareListeners() = kcode("// listeners") {
model.exprMap.values.filter {
it is ListenerExpr
}.groupBy { it }.forEach {
val expr = it.key as ListenerExpr
nl("private ${expr.listenerClassName} ${expr.fieldName};")
}
}
fun declareInverseBindingImpls() = kcode("// Inverse Binding Event Handlers") {
layoutBinder.sortedTargets.filter { it.isUsed }.forEach { target ->
target.inverseBindings.forEach { inverseBinding ->
val invClass : String
val param : String
if (inverseBinding.isOnBinder) {
invClass = "android.databinding.ViewDataBinding.PropertyChangedInverseListener"
param = "BR.${inverseBinding.eventAttribute}"
} else {
invClass = "android.databinding.InverseBindingListener"
param = ""
}
block("private $invClass ${inverseBinding.fieldName} = new $invClass($param)") {
nl("@Override")
block("public void onChange()") {
if (inverseBinding.inverseExpr != null) {
val valueExpr = inverseBinding.variableExpr
val getterCall = inverseBinding.getterCall
nl("// Inverse of ${inverseBinding.expr}")
nl("// is ${inverseBinding.inverseExpr}")
nl("${valueExpr.resolvedType.toJavaCode()} ${valueExpr.name} = ${getterCall.toJava("mBindingComponent", target.fieldName)};")
nl(inverseBinding.callbackExprModel.localizeGlobalVariables(valueExpr))
nl(inverseBinding.executionPath.toCode())
} else {
block("synchronized($className.this)") {
val flagSet = inverseBinding.chainedExpressions.fold(FlagSet(), { initial, expr ->
initial.or(FlagSet(expr.id))
})
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.localValue(index)} |= ${flagSet.binaryCode(index)};")
}
}
nl("requestRebind();")
}
}
}.app(";")
}
}
}
fun declareDirtyFlags() = kcode("// dirty flag") {
model.ext.localizedFlags.forEach { flag ->
flag.notEmpty { suffix, value ->
nl("private")
app(" ", if(flag.isDynamic) null else "static final");
app(" ", " ${flag.type} ${flag.localName}$suffix = ${longToBinary(value)};")
}
}
}
fun flagMapping() = kcode("/* flag mapping") {
if (model.flagMapping != null) {
val mapping = model.flagMapping
for (i in mapping.indices) {
tab("flag $i (${longToBinary(1L + i)}): ${model.findFlagExpression(i)}")
}
}
nl("flag mapping end*/")
}
fun executePendingBindings() = kcode("") {
nl("@Override")
block("protected void executeBindings()") {
val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
tmpDirtyFlags.localName = "dirtyFlags";
for (i in (0..mDirtyFlags.buckets.size - 1)) {
nl("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
}
block("synchronized(this)") {
for (i in (0..mDirtyFlags.buckets.size - 1)) {
nl("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
nl("${mDirtyFlags.localValue(i)} = 0;")
}
}
model.pendingExpressions.filter { it.needsLocalField }.forEach {
nl("${it.resolvedType.toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.defaultValue};")
}
L.d("writing executePendingBindings for %s", className)
do {
val batch = ExprModel.filterShouldRead(model.pendingExpressions)
val justRead = arrayListOf<Expr>()
L.d("batch: %s", batch)
while (!batch.none()) {
val readNow = batch.filter { it.shouldReadNow(justRead) }
if (readNow.isEmpty()) {
throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
}
L.d("new read now. batch size: %d, readNow size: %d", batch.size, readNow.size)
nl(readWithDependants(readNow, justRead, batch, tmpDirtyFlags))
batch.removeAll(justRead)
}
nl("// batch finished")
} while (model.markBitsRead())
// verify everything is read.
val batch = ExprModel.filterShouldRead(model.pendingExpressions)
if (batch.isNotEmpty()) {
L.e("could not generate code for %s. This might be caused by circular dependencies."
+ "Please report on b.android.com. %d %s %s", layoutBinder.layoutname,
batch.size, batch[0], batch[0].toCode().generate())
}
//
layoutBinder.sortedTargets.filter { it.isUsed }
.flatMap { it.bindings }
.groupBy {
"${tmpDirtyFlags.mapOr(it.expr.dirtyFlagSet) { suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${it.expr.dirtyFlagSet.localValue(index)}) != 0"
}.joinToString(" || ") }"
}.forEach {
block("if (${it.key})") {
it.value.groupBy { Math.max(1, it.minApi) }.forEach {
val setterValues = kcode("") {
it.value.forEach { binding ->
nl(binding.toAssignmentCode()).app(";")
}
}
nl("// api target ${it.key}")
if (it.key > 1) {
block("if(getBuildSdkInt() >= ${it.key})") {
nl(setterValues)
}
} else {
nl(setterValues)
}
}
}
}
layoutBinder.sortedTargets.filter { it.isUsed }
.flatMap { it.bindings }
.filter { it.requiresOldValue() }
.groupBy {"${tmpDirtyFlags.mapOr(it.expr.dirtyFlagSet) { suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${it.expr.dirtyFlagSet.localValue(index)}) != 0"
}.joinToString(" || ")
}"}.forEach {
block("if (${it.key})") {
it.value.groupBy { it.expr }.map { it.value.first() }.forEach {
it.componentExpressions.forEach { expr ->
nl("this.${expr.oldValueName} = ${expr.toCode().generate()};")
}
}
}
}
includedBinders.filter{it.isUsed }.forEach { binder ->
nl("executeBindingsOn(${binder.fieldName});")
}
layoutBinder.sortedTargets.filter{
it.isUsed && it.resolvedType != null && it.resolvedType.extendsViewStub()
}.forEach {
block("if (${it.fieldName}.getBinding() != null)") {
nl("executeBindingsOn(${it.fieldName}.getBinding());")
}
}
}
}
fun readWithDependants(expressionList: List<Expr>, justRead: MutableList<Expr>,
batch: MutableList<Expr>, tmpDirtyFlags: FlagSet,
inheritedFlags: FlagSet? = null) : KCode = kcode("") {
expressionList.groupBy { it.shouldReadFlagSet }.forEach {
val flagSet = it.key
val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
val expressions = it.value
val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
}.joinToString(" || ")
})"
val readCode = kcode("") {
val dependants = ArrayList<Expr>()
expressions.groupBy { condition(it) }.forEach {
val condition = it.key
val assignedValues = it.value.filter { it.needsLocalField && !it.isVariable() }
if (!assignedValues.isEmpty()) {
val assignment = kcode("") {
assignedValues.forEach { expr: Expr ->
tab("// read $expr")
tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";")
}
}
if (condition != null) {
tab("if ($condition) {") {
app("", assignment)
}
tab ("}")
} else {
app("", assignment)
}
it.value.filter { it.isObservable }.forEach { expr: Expr ->
tab("updateRegistration(${expr.id}, ${expr.executePendingLocalName});")
}
}
it.value.forEach { expr: Expr ->
justRead.add(expr)
L.d("%s / readWithDependants %s", className, expr);
L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
// if I am the condition for an expression, set its flag
expr.dependants.filter {
!it.isConditional && it.dependant is TernaryExpr &&
(it.dependant as TernaryExpr).pred == expr
}.map { it.dependant }.groupBy {
// group by when those ternaries will be evaluated (e.g. don't set conditional flags for no reason)
val ternaryBitSet = it.shouldReadFlagsWithConditionals
val isBehindTernary = ternaryBitSet.nextSetBit(model.invalidateAnyFlagIndex) == -1
if (!isBehindTernary) {
val ternaryFlags = it.shouldReadWithConditionalsFlagSet
"if(${tmpDirtyFlags.mapOr(ternaryFlags){ suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${ternaryFlags.localValue(index)}) != 0"
}.joinToString(" || ")})"
} else {
// TODO if it is behind a ternary, we should set it when its predicate is elevated
// Normally, this would mean that there is another code path to re-read our current expression.
// Unfortunately, this may not be true due to the coverage detection in `expr#markAsReadIfDone`, this may never happen.
// for v1.0, we'll go with always setting it and suffering an unnecessary calculation for this edge case.
// we can solve this by listening to elevation events from the model.
""
}
}.forEach {
val hasAnotherIf = it.key != ""
val cond : (KCode.() -> Unit) = {
it.apply {
val predicate = if (expr.resolvedType.isNullable) {
"Boolean.TRUE.equals(${expr.executePendingLocalName})"
} else {
expr.executePendingLocalName
}
block("if($predicate)") {
it.value.forEach {
val set = it.getRequirementFlagSet(true)
mDirtyFlags.mapOr(set) { suffix, index ->
tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
}
}
}
block("else") {
it.value.forEach {
val set = it.getRequirementFlagSet(false)
mDirtyFlags.mapOr(set) { suffix, index ->
tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
}
}
}
}
}
if (hasAnotherIf) {
block(it.key, cond)
} else {
cond()
}
}
val chosen = expr.dependants.filter {
val dependant = it.dependant
batch.contains(dependant) &&
dependant.shouldReadFlagSet.andNot(flagSet).isEmpty &&
dependant.shouldReadNow(justRead)
}
if (chosen.isNotEmpty()) {
dependants.addAll(chosen.map { it.dependant })
}
}
}
if (dependants.isNotEmpty()) {
val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
nl(readWithDependants(dependants, justRead, batch, tmpDirtyFlags, nextInheritedFlags))
}
}
if (needsIfWrapper) {
block(ifClause) {
nl(readCode)
}
} else {
nl(readCode)
}
}
}
fun condition(expr : Expr) : String? {
if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
// create an if case for all dependencies that might be null
val nullables = expr.dependencies.filter {
it.isMandatory && it.other.resolvedType.isNullable
}.map { it.other }
if (!expr.isEqualityCheck && nullables.isNotEmpty()) {
return "${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}"
} else {
return null
}
} else {
return null
}
}
fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
model.exprMap.values.filter {
it.isUsed && it is ListenerExpr
}.groupBy { it }.forEach {
val expr = it.key as ListenerExpr
val listenerType = expr.resolvedType;
val extendsImplements : String
if (listenerType.isInterface) {
extendsImplements = "implements"
} else {
extendsImplements = "extends"
}
nl("public static class ${expr.listenerClassName} $extendsImplements ${listenerType.canonicalName}{") {
if (expr.target.isDynamic) {
tab("private ${expr.target.resolvedType.toJavaCode()} value;")
tab("public ${expr.listenerClassName} setValue(${expr.target.resolvedType.toJavaCode()} value) {") {
tab("this.value = value;")
tab("return value == null ? null : this;")
}
tab("}")
}
val listenerMethod = expr.method
val parameterTypes = listenerMethod.parameterTypes
val returnType = listenerMethod.getReturnType(parameterTypes.toList())
tab("@Override")
tab("public $returnType ${listenerMethod.name}(${
parameterTypes.withIndex().map {
"${it.value.toJavaCode()} arg${it.index}"
}.joinToString(", ")
}) {") {
val obj : String
if (expr.target.isDynamic) {
obj = "this.value"
} else {
obj = expr.target.toCode().generate();
}
val returnStr : String
if (!returnType.isVoid) {
returnStr = "return "
} else {
returnStr = ""
}
val args = parameterTypes.withIndex().map {
"arg${it.index}"
}.joinToString(", ")
tab("$returnStr$obj.${expr.name}($args);")
}
tab("}")
}
nl("}")
}
}
fun declareFactories() = kcode("") {
nl("@NonNull")
block("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.view.ViewGroup root, boolean attachToRoot)") {
nl("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
}
nl("@NonNull")
block("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.view.ViewGroup root, boolean attachToRoot, @Nullable android.databinding.DataBindingComponent bindingComponent)") {
nl("return android.databinding.DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, root, attachToRoot, bindingComponent);")
}
if (!layoutBinder.isMerge) {
nl("@NonNull")
block("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater)") {
nl("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
}
nl("@NonNull")
block("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.databinding.DataBindingComponent bindingComponent)") {
nl("return bind(inflater.inflate(${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, null, false), bindingComponent);")
}
nl("@NonNull")
block("public static $baseClassName bind(@NonNull android.view.View view)") {
nl("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
}
nl("@NonNull")
block("public static $baseClassName bind(@NonNull android.view.View view, @Nullable android.databinding.DataBindingComponent bindingComponent)") {
block("if (!\"${layoutBinder.tag}_0\".equals(view.getTag()))") {
nl("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
}
nl("return new $baseClassName(bindingComponent, view);")
}
}
}
/**
* When called for a library compilation, we do not generate real implementations
*/
public fun writeBaseClass(forLibrary: Boolean, variations: List<LayoutBinder>) : String =
kcode("package ${layoutBinder.`package`};") {
Scope.reset()
nl("import android.databinding.Bindable;")
nl("import android.databinding.DataBindingUtil;")
nl("import android.databinding.ViewDataBinding;")
nl("import android.support.annotation.NonNull;")
nl("import android.support.annotation.Nullable;")
annotateWithGenerated()
nl("public abstract class $baseClassName extends ViewDataBinding {")
layoutBinder.sortedTargets.filter{it.id != null}.forEach {
if (variations.count{lb -> lb.sortedTargets.any {bt -> bt.isUsed && bt.id == it.id && bt.includedLayout == null}} == variations.size) {
tab("@NonNull")
}
else {
tab("@Nullable")
}
tab("public final ${it.interfaceClass} ${it.fieldName};")
}
nl("")
tab("// variables") {
variables.forEach {
nl("protected ${it.resolvedType.toJavaCode()} ${it.fieldName};")
}
}
tab("protected $baseClassName(@Nullable android.databinding.DataBindingComponent bindingComponent, @Nullable android.view.View root_, int localFieldCount") {
layoutBinder.sortedTargets.filter{it.id != null}.forEach {
tab(", ${it.interfaceClass} ${it.constructorParamName}")
}
}
tab(") {") {
tab("super(bindingComponent, root_, localFieldCount);")
layoutBinder.sortedTargets.filter{it.id != null}.forEach {
tab("this.${it.fieldName} = ${it.constructorParamName};")
if (it.isBinder) {
tab("setContainedBinding(this.${it.fieldName});")
}
}
}
tab("}")
tab("//getters and abstract setters") {
variables.forEach {
val typeCode = it.resolvedType.toJavaCode()
nl("public abstract void ${it.setterName}(${if (it.resolvedType.isPrimitive) "" else "@Nullable "}$typeCode ${it.readableName});")
nl("")
if (!it.resolvedType.isPrimitive) {
nl("@Nullable")
}
block("public $typeCode ${it.getterName}()") {
nl("return ${it.fieldName};")
}
nl("")
}
}
tab("@NonNull")
tab("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.view.ViewGroup root, boolean attachToRoot) {") {
tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
}
tab("}")
tab("@NonNull")
tab("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater) {") {
tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
}
tab("}")
tab("@NonNull")
tab("public static $baseClassName bind(@NonNull android.view.View view) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
}
}
tab("}")
tab("@NonNull")
tab("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.view.ViewGroup root, boolean attachToRoot, @Nullable android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, root, attachToRoot, bindingComponent);")
}
}
tab("}")
tab("@NonNull")
tab("public static $baseClassName inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, null, false, bindingComponent);")
}
}
tab("}")
tab("@NonNull")
tab("public static $baseClassName bind(@NonNull android.view.View view, @Nullable android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return ($baseClassName)bind(bindingComponent, view, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname});")
}
}
tab("}")
nl("}")
}.generate()
}