blob: d87d8d44089f6cb4bff743a540e130df3a4ab5ee [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.BindingTarget
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.TernaryExpr
import android.databinding.tool.ext.androidId
import android.databinding.tool.ext.br
import android.databinding.tool.ext.joinToCamelCaseAsVar
import android.databinding.tool.ext.lazy
import android.databinding.tool.ext.versionedLazy
import android.databinding.tool.reflection.ModelAnalyzer
import android.databinding.tool.util.L
import java.util.ArrayList
import java.util.Arrays
import java.util.BitSet
import java.util.HashMap
import java.util.HashSet
import kotlin.properties.Delegates
fun String.stripNonJava() = this.split("[^a-zA-Z0-9]".toRegex()).map{ it.trim() }.joinToCamelCaseAsVar()
enum class Scope {
FIELD,
METHOD,
FLAG,
EXECUTE_PENDING_METHOD,
CONSTRUCTOR_PARAM
}
class ExprModelExt {
val usedFieldNames = hashMapOf<Scope, MutableSet<String>>();
init {
Scope.values().forEach { usedFieldNames[it] = hashSetOf<String>() }
}
val localizedFlags = arrayListOf<FlagSet>()
fun localizeFlag(set : FlagSet, name:String) : FlagSet {
localizedFlags.add(set)
val result = getUniqueName(name, Scope.FLAG)
set.setLocalName(result)
return set
}
fun getUniqueName(base : String, scope : Scope) : String {
var candidate = base
var i = 0
while (usedFieldNames[scope].contains(candidate)) {
i ++
candidate = base + i
}
usedFieldNames[scope].add(candidate)
return candidate
}
}
val ExprModel.ext by Delegates.lazy { target : ExprModel ->
ExprModelExt()
}
fun ExprModel.getUniqueFieldName(base : String) : String = ext.getUniqueName(base, Scope.FIELD)
fun ExprModel.getUniqueMethodName(base : String) : String = ext.getUniqueName(base, Scope.METHOD)
fun ExprModel.getUniqueFlagName(base : String) : String = ext.getUniqueName(base, Scope.FLAG)
fun ExprModel.getConstructorParamName(base : String) : String = ext.getUniqueName(base, Scope.CONSTRUCTOR_PARAM)
fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
// not necessarily unique. Uniqueness is solved per scope
val BindingTarget.readableName by Delegates.lazy { target: BindingTarget ->
if (target.getId() == null) {
"boundView" + indexFromTag(target.getTag())
} else {
target.getId().androidId().stripNonJava()
}
}
fun BindingTarget.superConversion(variable : String) : String {
if (getResolvedType() != null && getResolvedType().extendsViewStub()) {
return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})"
} else {
return "(${interfaceType}) ${variable}"
}
}
val BindingTarget.fieldName : String by Delegates.lazy { target : BindingTarget ->
val name : String
if (target.getId() == null) {
name = "m${target.readableName}"
} else {
name = target.readableName
}
target.getModel().getUniqueFieldName(name)
}
val BindingTarget.androidId by Delegates.lazy { target : BindingTarget ->
if (target.getId().startsWith("@android:id/")) {
"android.R.id.${target.getId().androidId()}"
} else {
"R.id.${target.getId().androidId()}"
}
}
val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget ->
if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) {
"android.databinding.ViewStubProxy"
} else {
target.getInterfaceType()
}
}
val BindingTarget.constructorParamName by Delegates.lazy { target : BindingTarget ->
target.getModel().getConstructorParamName(target.readableName)
}
// not necessarily unique. Uniqueness is decided per scope
val Expr.readableName by Delegates.lazy { expr : Expr ->
val stripped = "${expr.getUniqueKey().stripNonJava()}"
L.d("readableUniqueName for [%s] %s is %s", System.identityHashCode(expr), expr.getUniqueKey(), stripped)
stripped
}
val Expr.fieldName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueFieldName("m${expr.readableName.capitalize()}")
}
val Expr.listenerClassName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueFieldName("${expr.readableName.capitalize()}Impl")
}
val Expr.oldValueName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueFieldName("mOld${expr.readableName.capitalize()}")
}
val Expr.executePendingLocalName by Delegates.lazy { expr : Expr ->
"${expr.getModel().ext.getUniqueName(expr.readableName, Scope.EXECUTE_PENDING_METHOD)}"
}
val Expr.setterName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueMethodName("set${expr.readableName.capitalize()}")
}
val Expr.onChangeName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueMethodName("onChange${expr.readableName.capitalize()}")
}
val Expr.getterName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueMethodName("get${expr.readableName.capitalize()}")
}
val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
expr.getModel().getUniqueFlagName("sFlag${expr.readableName.capitalize()}")
}
fun Expr.toCode(full : Boolean = false) : KCode = CodeGenUtil.toCode(this, full)
fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix"
val Expr.dirtyFlagSet by Delegates.lazy { expr : Expr ->
FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount())
}
val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr ->
FlagSet(expr.getId())
}
val Expr.shouldReadFlagSet by Delegates.versionedLazy { expr : Expr ->
FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount())
}
val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
FlagSet(expr.getRequirementFlagIndex(true)))
}
val LayoutBinder.requiredComponent by Delegates.lazy { layoutBinder: LayoutBinder ->
val required = layoutBinder.
getBindingTargets().
flatMap { it.getBindings() }.
firstOrNull { it.getBindingAdapterInstanceClass() != null }
required?.getBindingAdapterInstanceClass()
}
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 FlagSet.getWordSuffix(wordIndex : Int) : String {
return if(wordIndex == 0) "" else "_${wordIndex}"
}
fun FlagSet.localValue(bucketIndex : Int) =
if (getLocalName() == null) binaryCode(bucketIndex)
else "${getLocalName()}${getWordSuffix(bucketIndex)}"
fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
fun longToBinary(l : Long) =
"0b${java.lang.Long.toBinaryString(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.getModel()
val indices = HashMap<BindingTarget, kotlin.Int>()
val mDirtyFlags by Delegates.lazy {
val fs = FlagSet(BitSet(), model.getFlagBucketCount());
Arrays.fill(fs.buckets, -1)
fs.setDynamic(true)
model.localizeFlag(fs, "mDirtyFlags")
fs
}
val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
val className = layoutBinder.getImplementationName()
val baseClassName = "${layoutBinder.getClassName()}"
val includedBinders by Delegates.lazy {
layoutBinder.getBindingTargets().filter { it.isBinder() }
}
val variables by Delegates.lazy {
model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
}
val usedVariables by Delegates.lazy {
variables.filter {it.isUsed()}
}
public fun write(minSdk : kotlin.Int) : String {
layoutBinder.resolveWhichExpressionsAreUsed()
calculateIndices();
return kcode("package ${layoutBinder.getPackage()};") {
nl("import ${layoutBinder.getModulePackage()}.R;")
nl("import ${layoutBinder.getModulePackage()}.BR;")
nl("import android.view.View;")
val classDeclaration : String
if (layoutBinder.hasVariations()) {
classDeclaration = "${className} extends ${baseClassName}"
} else {
classDeclaration = "${className} extends android.databinding.ViewDataBinding"
}
nl("public class ${classDeclaration} {") {
tab(declareIncludeViews())
tab(declareViews())
tab(declareVariables())
tab(declareBoundValues())
tab(declareListeners())
tab(declareConstructor(minSdk))
tab(declareInvalidateAll())
tab(declareHasPendingBindings())
tab(declareLog())
tab(declareSetVariable())
tab(variableSettersAndGetters())
tab(onFieldChange())
tab(executePendingBindings())
tab(declareListenerImpls())
tab(declareDirtyFlags())
if (!layoutBinder.hasVariations()) {
tab(declareFactories())
}
}
nl("}")
tab(flagMapping())
tab("//end")
}.generate()
}
fun calculateIndices() : Unit {
val taggedViews = layoutBinder.getBindingTargets().filter{
it.isUsed() && it.getTag() != null && !it.isBinder()
}
taggedViews.forEach {
indices.put(it, indexFromTag(it.getTag()))
}
val indexStart = maxIndex() + 1
layoutBinder.getBindingTargets().filter{
it.isUsed() && !taggedViews.contains(it)
}.withIndex().forEach {
indices.put(it.value, it.index + indexStart)
}
}
fun declareIncludeViews() = kcode("") {
nl("private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;")
nl("private static final android.util.SparseIntArray sViewsWithIds;")
nl("static {") {
val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
if (!hasBinders) {
tab("sIncludes = null;")
} else {
val numBindings = layoutBinder.getBindingTargets().filter{ it.isUsed() }.count()
tab("sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts(${numBindings});")
val includeMap = HashMap<BindingTarget, ArrayList<BindingTarget>>()
layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder() }.forEach {
val includeTag = it.getTag();
val parent = layoutBinder.getBindingTargets().firstOrNull {
it.isUsed() && !it.isBinder() && includeTag.equals(it.getTag())
}
if (parent == null) {
throw IllegalStateException("Could not find parent of include file")
}
var list = includeMap.get(parent)
if (list == null) {
list = ArrayList<BindingTarget>()
includeMap.put(parent, list)
}
list.add(it)
}
includeMap.keySet().forEach {
val index = indices.get(it)
tab("sIncludes.setIncludes(${index}, ") {
tab ("new String[] {${
includeMap.get(it).map {
"\"${it.getIncludedLayout()}\""
}.joinToString(", ")
}},")
tab("new int[] {${
includeMap.get(it).map {
"${indices.get(it)}"
}.joinToString(", ")
}},")
tab("new int[] {${
includeMap.get(it).map {
"R.layout.${it.getIncludedLayout()}"
}.joinToString(", ")
}});")
}
}
}
val viewsWithIds = layoutBinder.getBindingTargets().filter {
it.isUsed() && !it.isBinder() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
}
if (viewsWithIds.isEmpty()) {
tab("sViewsWithIds = null;")
} else {
tab("sViewsWithIds = new android.util.SparseIntArray();")
viewsWithIds.forEach {
tab("sViewsWithIds.put(${it.androidId}, ${indices.get(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}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
tab("this(bindingComponent, ${superParam}, mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds));")
}
nl("}")
nl("private ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root, Object[] bindings) {") {
tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()}") {
layoutBinder.getSortedTargets().filter { it.getId() != null }.forEach {
tab(", ${fieldConversion(it)}")
}
tab(");")
}
}
} else {
nl("public ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()});")
tab("final Object[] bindings = mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds);")
}
}
if (layoutBinder.requiredComponent != null) {
tab("ensureBindingComponentIsNotNull(${layoutBinder.requiredComponent}.class);")
}
val taggedViews = layoutBinder.getSortedTargets().filter{it.isUsed()}
taggedViews.forEach {
if (!layoutBinder.hasVariations() || it.getId() == null) {
tab("this.${it.fieldName} = ${fieldConversion(it)};")
}
if (!it.isBinder()) {
if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
tab("this.${it.fieldName}.setContainingBinding(this);")
}
if (it.supportsTag() && it.getTag() != null &&
(rootTagsSupported || it.getTag().startsWith("binding_"))) {
val originalTag = it.getOriginalTag();
var tagValue = "null"
if (originalTag != null) {
tagValue = "\"${originalTag}\""
if (originalTag.startsWith("@")) {
var packageName = layoutBinder.getModulePackage()
if (originalTag.startsWith("@android:")) {
packageName = "android"
}
val slashIndex = originalTag.indexOf('/')
val resourceId = originalTag.substring(slashIndex + 1)
tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
}
}
tab("this.${it.fieldName}.setTag(${tagValue});")
}
}
}
tab("setRootTag(root);")
tab("invalidateAll();");
nl("}")
}
fun fieldConversion(target : BindingTarget) : String {
if (!target.isUsed()) {
return "null"
} else {
val index = indices.get(target)
if (index == null) {
throw IllegalStateException("Unknown binding target")
}
val variableName = "bindings[${index}]"
return target.superConversion(variableName)
}
}
fun declareInvalidateAll() = kcode("") {
nl("@Override")
nl("public void invalidateAll() {") {
val fs = FlagSet(layoutBinder.getModel().getInvalidateAnyBitSet(),
layoutBinder.getModel().getFlagBucketCount());
tab("synchronized(this) {") {
for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
}
} tab("}")
includedBinders.filter{it.isUsed()}.forEach { binder ->
tab("${binder.fieldName}.invalidateAll();")
}
tab("requestRebind();");
}
nl("}")
}
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("public boolean setVariable(int variableId, Object variable) {") {
tab("switch(variableId) {") {
usedVariables.forEach {
tab ("case ${it.getName().br()} :") {
tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
tab("return true;")
}
}
}
tab("}")
tab("return false;")
}
nl("}")
}
fun declareLog() = kcode("") {
nl("private void log(String msg, long i) {") {
tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
}
nl("}")
}
fun variableSettersAndGetters() = kcode("") {
variables.filterNot{it.isUsed()}.forEach {
nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
tab("// not used, ignore")
}
nl("}")
nl("")
nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
tab("return ${it.getDefaultValue()};")
}
nl("}")
}
usedVariables.forEach {
if (it.getUserDefinedType() != null) {
nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
if (it.isObservable()) {
tab("updateRegistration(${it.getId()}, ${it.readableName});");
}
tab("this.${it.fieldName} = ${it.readableName};")
// set dirty flags!
val flagSet = it.invalidateFlagSet
tab("synchronized(this) {") {
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
}
} tab ("}")
tab("super.requestRebind();")
}
nl("}")
nl("")
nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
tab("return ${it.fieldName};")
}
nl("}")
}
}
}
fun onFieldChange() = kcode("") {
nl("@Override")
nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
tab("switch (localFieldId) {") {
model.getObservables().forEach {
tab("case ${it.getId()} :") {
tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
}
}
}
tab("}")
tab("return false;")
}
nl("}")
nl("")
model.getObservables().forEach {
nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
tab("switch (fieldId) {", {
val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
accessedFields.filter { it.hasBindableAnnotations() }
.groupBy { it.getName() }
.forEach {
tab("case ${it.key.br()}:") {
val field = it.value.first()
tab("synchronized(this) {") {
mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
}
} tab("}")
tab("return true;")
}
}
tab("case ${"".br()}:") {
val flagSet = it.invalidateFlagSet
tab("synchronized(this) {") {
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
}
} tab("}")
tab("return true;")
}
})
tab("}")
tab("return false;")
}
nl("}")
nl("")
}
}
fun declareViews() = kcode("// views") {
val oneLayout = !layoutBinder.hasVariations();
layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
val access : String
if (oneLayout && it.getId() != null) {
access = "public"
} else {
access = "private"
}
nl("${access} final ${it.interfaceType} ${it.fieldName};")
}
}
fun declareVariables() = kcode("// variables") {
usedVariables.forEach {
nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
}
}
fun declareBoundValues() = kcode("// values") {
layoutBinder.getSortedTargets().filter { it.isUsed() }
.flatMap { it.getBindings() }
.filter { it.requiresOldValue() }
.flatMap{ it.getComponentExpressions().toArrayList() }
.groupBy { it }
.forEach {
val expr = it.getKey()
nl("private ${expr.getResolvedType().toJavaCode()} ${expr.oldValueName};")
}
}
fun declareListeners() = kcode("// listeners") {
model.getExprMap().values().filter {
it is FieldAccessExpr && it.isListener()
}.groupBy { it }.forEach {
val expr = it.key as FieldAccessExpr
nl("private ${expr.listenerClassName} ${expr.fieldName};")
}
}
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.getLocalName()}$suffix = ${longToBinary(value)};")
}
}
}
fun flagMapping() = kcode("/* flag mapping") {
if (model.getFlagMapping() != null) {
val mapping = model.getFlagMapping()
for (i in mapping.indices) {
tab("flag $i: ${mapping[i]}")
}
}
nl("flag mapping end*/")
}
fun executePendingBindings() = kcode("") {
nl("@Override")
nl("protected void executeBindings() {") {
val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
tmpDirtyFlags.setLocalName("dirtyFlags");
for (i in (0..mDirtyFlags.buckets.size() - 1)) {
tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
}
tab("synchronized(this) {") {
for (i in (0..mDirtyFlags.buckets.size() - 1)) {
tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
tab("${mDirtyFlags.localValue(i)} = 0;")
}
} tab("}")
model.getPendingExpressions().filterNot {!it.canBeEvaluatedToAVariable() || (it.isVariable() && !it.isUsed())}.forEach {
tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${if(it.isVariable()) it.fieldName else it.getDefaultValue()};")
}
L.d("writing executePendingBindings for %s", className)
do {
val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
L.d("batch: %s", batch)
val mJustRead = arrayListOf<Expr>()
while (!batch.none()) {
val readNow = batch.filter { it.shouldReadNow(mJustRead) }
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())
readNow.forEach {
nl(readWithDependants(it, mJustRead, batch, tmpDirtyFlags))
}
batch.removeAll(mJustRead)
}
tab("// batch finished")
} while(model.markBitsRead())
// verify everything is read.
val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
if (batch.isNotEmpty()) {
L.e("could not generate code for %s. This might be caused by circular dependencies."
+ "Please report on b.android.com", layoutBinder.getLayoutname())
}
//
layoutBinder.getSortedTargets().filter { it.isUsed() }
.flatMap { it.getBindings() }
.groupBy { it.getExpr() }
.forEach {
val flagSet = it.key.dirtyFlagSet
tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
}.joinToString(" || ")
}) {") {
it.value.forEach { binding ->
tab("// api target ${binding.getMinApi()}")
val fieldName : String
if (binding.getTarget().getViewClass().
equals(binding.getTarget().getInterfaceType())) {
fieldName = "this.${binding.getTarget().fieldName}"
} else {
fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
}
val bindingCode = binding.toJavaCode(fieldName, "this.mBindingComponent")
if (binding.getMinApi() > 1) {
tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
tab("$bindingCode;")
}
tab("}")
} else {
tab("$bindingCode;")
}
}
}
tab("}")
}
layoutBinder.getSortedTargets().filter { it.isUsed() }
.flatMap { it.getBindings() }
.filter { it.requiresOldValue() }
.groupBy { it.getExpr() }
.forEach {
val flagSet = it.key.dirtyFlagSet
tab("if (${tmpDirtyFlags.mapOr(flagSet) { suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
}.joinToString(" || ")
}) {") {
it.value.first().getComponentExpressions().forEach { expr ->
tab("this.${expr.oldValueName} = ${expr.toCode(false).generate()};")
}
}
tab("}")
}
includedBinders.filter{it.isUsed()}.forEach { binder ->
tab("${binder.fieldName}.executePendingBindings();")
}
layoutBinder.getSortedTargets().filter{
it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
}.forEach {
tab("if (${it.fieldName}.getBinding() != null) {") {
tab("${it.fieldName}.getBinding().executePendingBindings();")
}
tab("}")
}
}
nl("}")
}
fun readWithDependants(expr : Expr, mJustRead : MutableList<Expr>, batch : MutableList<Expr>,
tmpDirtyFlags : FlagSet, inheritedFlags : FlagSet? = null) : KCode = kcode("") {
mJustRead.add(expr)
L.d("%s / readWithDependants %s", className, expr.getUniqueKey());
val flagSet = expr.shouldReadFlagSet
val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
"(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
}.joinToString(" || ")
})"
val readCode = kcode("") {
if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
// it is not a variable read it.
tab("// read ${expr.getUniqueKey()}")
// create an if case for all dependencies that might be null
val nullables = expr.getDependencies().filter {
it.isMandatory() && it.getOther().getResolvedType().isNullable()
}.map { it.getOther() }
if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
tab ("if ( ${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}) {") {
tab("${expr.executePendingLocalName}").app(" = ", expr.toCode(true)).app(";")
}
tab("}")
} else {
tab("${expr.executePendingLocalName}").app(" = ", expr.toCode(true)).app(";")
}
if (expr.isObservable()) {
tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
}
}
// if I am the condition for an expression, set its flag
val conditionals = expr.getDependants().filter { !it.isConditional()
&& it.getDependant() is TernaryExpr && (it.getDependant() as TernaryExpr).getPred() == expr }
.map { it.getDependant() }
if (conditionals.isNotEmpty()) {
tab("// setting conditional flags")
tab("if (${expr.executePendingLocalName}) {") {
conditionals.forEach {
val set = it.getRequirementFlagSet(true)
mDirtyFlags.mapOr(set) { suffix , index ->
tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
}
}
}
tab("} else {") {
conditionals.forEach {
val set = it.getRequirementFlagSet(false)
mDirtyFlags.mapOr(set) { suffix , index ->
tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
}
}
} tab("}")
}
val chosen = expr.getDependants().filter {
val dependant = it.getDependant()
batch.contains(dependant) &&
dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
dependant.shouldReadNow(mJustRead)
}
if (chosen.isNotEmpty()) {
val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
chosen.forEach {
nl(readWithDependants(it.getDependant(), mJustRead, batch, tmpDirtyFlags, nextInheritedFlags))
}
}
}
if (needsIfWrapper) {
tab(ifClause) {
app(" {")
nl(readCode)
}
tab("}")
} else {
nl(readCode)
}
}
fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
model.getExprMap().values().filter {
it.isUsed() && it is FieldAccessExpr && it.isListener()
}.groupBy { it }.forEach {
val expr = it.key as FieldAccessExpr
val listeners = expr.getListenerTypes()
val extends = listeners.firstOrNull{ !it.isInterface() }
val extendsImplements = StringBuilder()
if (extends != null) {
extendsImplements.append("extends ${extends.toJavaCode()} ");
}
val implements = expr.getListenerTypes().filter{ it.isInterface() }.map {
it.toJavaCode()
}.joinToString(", ")
if (!implements.isEmpty()) {
extendsImplements.append("implements ${implements}")
}
nl("public static class ${expr.listenerClassName} ${extendsImplements} {") {
tab("public ${expr.listenerClassName}() {}")
if (expr.getChild().isDynamic()) {
tab("private ${expr.getChild().getResolvedType().toJavaCode()} value;")
tab("public ${expr.listenerClassName} setValue(${expr.getChild().getResolvedType().toJavaCode()} value) {") {
tab("this.value = value;")
tab("return value == null ? null : this;")
}
tab("}")
}
val signatures = HashSet<String>()
expr.getListenerMethods().withIndex().forEach {
val listener = it.value
val calledMethod = expr.getCalledMethods().get(it.index)
val parameterTypes = listener.getParameterTypes()
val returnType = listener.getReturnType(parameterTypes.toArrayList())
val signature = "public ${returnType} ${listener.getName()}(${
parameterTypes.withIndex().map {
"${it.value.toJavaCode()} arg${it.index}"
}.joinToString(", ")
}) {"
if (!signatures.contains(signature)) {
signatures.add(signature)
tab("@Override")
tab(signature) {
val obj : String
if (expr.getChild().isDynamic()) {
obj = "this.value"
} else {
obj = expr.getChild().toCode(false).generate();
}
val returnStr : String
if (!returnType.isVoid()) {
returnStr = "return "
} else {
returnStr = ""
}
val args = parameterTypes.withIndex().map {
"arg${it.index}"
}.joinToString(", ")
tab("${returnStr}${obj}.${calledMethod.getName()}(${args});")
}
tab("}")
}
}
}
nl("}")
}
}
fun declareFactories() = kcode("") {
nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
}
nl("}")
nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
tab("return android.databinding.DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
}
nl("}")
if (!layoutBinder.isMerge()) {
nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
}
nl("}")
nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false), bindingComponent);")
}
nl("}")
nl("public static ${baseClassName} bind(android.view.View view) {") {
tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
}
nl("}")
nl("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
}
tab("}")
tab("return new ${baseClassName}(bindingComponent, view);")
}
nl("}")
}
}
/**
* When called for a library compilation, we do not generate real implementations
*/
public fun writeBaseClass(forLibrary : Boolean) : String =
kcode("package ${layoutBinder.getPackage()};") {
nl("import android.databinding.Bindable;")
nl("import android.databinding.DataBindingUtil;")
nl("import android.databinding.ViewDataBinding;")
nl("public abstract class ${baseClassName} extends ViewDataBinding {")
layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
tab("public final ${it.interfaceType} ${it.fieldName};")
}
nl("")
tab("protected ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
tab(", ${it.interfaceType} ${it.constructorParamName}")
}
}
tab(") {") {
tab("super(bindingComponent, root_, localFieldCount);")
layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
tab("this.${it.fieldName} = ${it.constructorParamName};")
}
}
tab("}")
nl("")
variables.forEach {
if (it.getUserDefinedType() != null) {
val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
}
}
tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
}
tab("}")
tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
}
tab("}")
tab("public static ${baseClassName} bind(android.view.View view) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
}
}
tab("}")
tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
}
}
tab("}")
tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false, bindingComponent);")
}
}
tab("}")
tab("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return (${baseClassName})bind(bindingComponent, view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
}
}
tab("}")
nl("}")
}.generate()
}