LayoutBinderWriter.kt revision 88ce44ccc65e74a8553244ca246cc9f4c48483e0
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *      http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13
14package android.databinding.tool.writer
15
16import android.databinding.tool.BindingTarget
17import android.databinding.tool.LayoutBinder
18import android.databinding.tool.expr.Expr
19import android.databinding.tool.expr.ExprModel
20import android.databinding.tool.expr.FieldAccessExpr
21import android.databinding.tool.expr.IdentifierExpr
22import android.databinding.tool.expr.ListenerExpr
23import android.databinding.tool.expr.TernaryExpr
24import android.databinding.tool.expr.ResourceExpr
25import android.databinding.tool.ext.androidId
26import android.databinding.tool.ext.br
27import android.databinding.tool.ext.joinToCamelCaseAsVar
28import android.databinding.tool.ext.lazy
29import android.databinding.tool.ext.versionedLazy
30import android.databinding.tool.processing.ErrorMessages
31import android.databinding.tool.reflection.ModelAnalyzer
32import android.databinding.tool.util.L
33import java.util.ArrayList
34import java.util.Arrays
35import java.util.BitSet
36import java.util.HashMap
37import kotlin.properties.Delegates
38
39fun String.stripNonJava() = this.split("[^a-zA-Z0-9]".toRegex()).map{ it.trim() }.joinToCamelCaseAsVar()
40
41enum class Scope {
42    FIELD,
43    METHOD,
44    FLAG,
45    EXECUTE_PENDING_METHOD,
46    CONSTRUCTOR_PARAM
47}
48
49class ExprModelExt {
50    val usedFieldNames = hashMapOf<Scope, MutableSet<String>>();
51    init {
52        Scope.values().forEach { usedFieldNames[it] = hashSetOf<String>() }
53    }
54    val localizedFlags = arrayListOf<FlagSet>()
55
56    fun localizeFlag(set : FlagSet, name:String) : FlagSet {
57        localizedFlags.add(set)
58        val result = getUniqueName(name, Scope.FLAG, false)
59        set.setLocalName(result)
60        return set
61    }
62
63    fun getUniqueName(base : String, scope : Scope, isPublic : kotlin.Boolean) : String {
64        var candidateBase = base
65        if (!isPublic && candidateBase.length() > 20) {
66            candidateBase = candidateBase.substring(0, 20);
67        }
68        var candidate = candidateBase
69        var i = 0
70        while (usedFieldNames[scope]!!.contains(candidate)) {
71            i ++
72            candidate = candidateBase + i
73        }
74        usedFieldNames[scope]!!.add(candidate)
75        return candidate
76    }
77}
78
79val ExprModel.ext by Delegates.lazy { target : ExprModel ->
80    ExprModelExt()
81}
82
83fun ExprModel.getUniqueFieldName(base : String, isPublic : kotlin.Boolean) : String = ext.getUniqueName(base, Scope.FIELD, isPublic)
84fun ExprModel.getUniqueMethodName(base : String, isPublic : kotlin.Boolean) : String = ext.getUniqueName(base, Scope.METHOD, isPublic)
85fun ExprModel.getUniqueFlagName(base : String) : String = ext.getUniqueName(base, Scope.FLAG, false)
86fun ExprModel.getConstructorParamName(base : String) : String = ext.getUniqueName(base, Scope.CONSTRUCTOR_PARAM, false)
87
88fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
89
90val Expr.needsLocalField by Delegates.lazy { expr : Expr ->
91    expr.canBeEvaluatedToAVariable() && !(expr.isVariable() && !expr.isUsed()) && (expr.isDynamic() || expr is ResourceExpr)
92}
93
94
95// not necessarily unique. Uniqueness is solved per scope
96val BindingTarget.readableName by Delegates.lazy { target: BindingTarget ->
97    if (target.getId() == null) {
98        "boundView" + indexFromTag(target.getTag())
99    } else {
100        target.getId().androidId().stripNonJava()
101    }
102}
103
104fun BindingTarget.superConversion(variable : String) : String {
105    if (getResolvedType() != null && getResolvedType().extendsViewStub()) {
106        return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})"
107    } else {
108        return "(${interfaceType}) ${variable}"
109    }
110}
111
112val BindingTarget.fieldName : String by Delegates.lazy { target : BindingTarget ->
113    val name : String
114    val isPublic : kotlin.Boolean
115    if (target.getId() == null) {
116        name = "m${target.readableName}"
117        isPublic = false
118    } else {
119        name = target.readableName
120        isPublic = true
121    }
122    target.getModel().getUniqueFieldName(name, isPublic)
123}
124
125val BindingTarget.androidId by Delegates.lazy { target : BindingTarget ->
126    if (target.getId().startsWith("@android:id/")) {
127        "android.R.id.${target.getId().androidId()}"
128    } else {
129        "R.id.${target.getId().androidId()}"
130    }
131}
132
133val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget ->
134    if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) {
135        "android.databinding.ViewStubProxy"
136    } else {
137        target.getInterfaceType()
138    }
139}
140
141val BindingTarget.constructorParamName by Delegates.lazy { target : BindingTarget ->
142    target.getModel().getConstructorParamName(target.readableName)
143}
144
145// not necessarily unique. Uniqueness is decided per scope
146val Expr.readableName by Delegates.lazy { expr : Expr ->
147    val stripped = "${expr.getUniqueKey().stripNonJava()}"
148    L.d("readableUniqueName for [%s] %s is %s", System.identityHashCode(expr), expr.getUniqueKey(), stripped)
149    stripped
150}
151
152val Expr.fieldName by Delegates.lazy { expr : Expr ->
153    expr.getModel().getUniqueFieldName("m${expr.readableName.capitalize()}", false)
154}
155
156val Expr.listenerClassName by Delegates.lazy { expr : Expr ->
157    expr.getModel().getUniqueFieldName("${expr.getResolvedType().getSimpleName()}Impl", false)
158}
159
160val Expr.oldValueName by Delegates.lazy { expr : Expr ->
161    expr.getModel().getUniqueFieldName("mOld${expr.readableName.capitalize()}", false)
162}
163
164val Expr.executePendingLocalName by Delegates.lazy { expr : Expr ->
165    if(expr.needsLocalField) "${expr.getModel().ext.getUniqueName(expr.readableName, Scope.EXECUTE_PENDING_METHOD, false)}"
166    else expr.toCode().generate()
167}
168
169val Expr.setterName by Delegates.lazy { expr : Expr ->
170    expr.getModel().getUniqueMethodName("set${expr.readableName.capitalize()}", true)
171}
172
173val Expr.onChangeName by Delegates.lazy { expr : Expr ->
174    expr.getModel().getUniqueMethodName("onChange${expr.readableName.capitalize()}", false)
175}
176
177val Expr.getterName by Delegates.lazy { expr : Expr ->
178    expr.getModel().getUniqueMethodName("get${expr.readableName.capitalize()}", true)
179}
180
181val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
182    expr.getModel().getUniqueFlagName("sFlag${expr.readableName.capitalize()}")
183}
184
185
186fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
187
188fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix"
189
190val Expr.dirtyFlagSet by Delegates.lazy { expr : Expr ->
191    FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount())
192}
193
194val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr ->
195    FlagSet(expr.getId())
196}
197
198val Expr.shouldReadFlagSet by Delegates.versionedLazy { expr : Expr ->
199    FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount())
200}
201
202val Expr.shouldReadWithConditionalsFlagSet by Delegates.versionedLazy { expr : Expr ->
203    FlagSet(expr.getShouldReadFlagsWithConditionals(), expr.getModel().getFlagBucketCount())
204}
205
206val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
207    arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
208            FlagSet(expr.getRequirementFlagIndex(true)))
209}
210
211val LayoutBinder.requiredComponent by Delegates.lazy { layoutBinder: LayoutBinder ->
212    val required = layoutBinder.
213            getBindingTargets().
214            flatMap { it.getBindings() }.
215            firstOrNull { it.getBindingAdapterInstanceClass() != null }
216    required?.getBindingAdapterInstanceClass()
217}
218
219fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
220
221fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
222    buckets.withIndex().forEach {
223        if (it.value != 0L) {
224            cb(getWordSuffix(it.index), buckets[it.index])
225        }
226    }
227}
228
229fun FlagSet.getWordSuffix(wordIndex : Int) : String {
230    return if(wordIndex == 0) "" else "_${wordIndex}"
231}
232
233fun FlagSet.localValue(bucketIndex : Int) =
234        if (getLocalName() == null) binaryCode(bucketIndex)
235        else "${getLocalName()}${getWordSuffix(bucketIndex)}"
236
237fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
238
239
240fun longToBinary(l : Long) =
241        "0b${java.lang.Long.toBinaryString(l)}L"
242
243fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
244    val min = Math.min(buckets.size(), other.buckets.size())
245    val result = arrayListOf<T>()
246    for (i in 0..(min - 1)) {
247        // if these two can match by any chance, call the callback
248        if (intersect(other, i)) {
249            result.add(cb(getWordSuffix(i), i))
250        }
251    }
252    return result
253}
254
255fun indexFromTag(tag : String) : kotlin.Int {
256    val startIndex : kotlin.Int
257    if (tag.startsWith("binding_")) {
258        startIndex = "binding_".length();
259    } else {
260        startIndex = tag.lastIndexOf('_') + 1
261    }
262    return Integer.parseInt(tag.substring(startIndex))
263}
264
265class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
266    val model = layoutBinder.getModel()
267    val indices = HashMap<BindingTarget, kotlin.Int>()
268    val mDirtyFlags by Delegates.lazy {
269        val fs = FlagSet(BitSet(), model.getFlagBucketCount());
270        Arrays.fill(fs.buckets, -1)
271        fs.setDynamic(true)
272        model.localizeFlag(fs, "mDirtyFlags")
273        fs
274    }
275
276    val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
277    val className = layoutBinder.getImplementationName()
278
279    val baseClassName = "${layoutBinder.getClassName()}"
280
281    val includedBinders by Delegates.lazy {
282        layoutBinder.getBindingTargets().filter { it.isBinder() }
283    }
284
285    val variables by Delegates.lazy {
286        model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
287    }
288
289    val usedVariables by Delegates.lazy {
290        variables.filter {it.isUsed()}
291    }
292
293    public fun write(minSdk : kotlin.Int) : String  {
294        layoutBinder.resolveWhichExpressionsAreUsed()
295        calculateIndices();
296        return kcode("package ${layoutBinder.getPackage()};") {
297            nl("import ${layoutBinder.getModulePackage()}.R;")
298            nl("import ${layoutBinder.getModulePackage()}.BR;")
299            nl("import android.view.View;")
300            val classDeclaration : String
301            if (layoutBinder.hasVariations()) {
302                classDeclaration = "${className} extends ${baseClassName}"
303            } else {
304                classDeclaration = "${className} extends android.databinding.ViewDataBinding"
305            }
306            nl("public class ${classDeclaration} {") {
307                tab(declareIncludeViews())
308                tab(declareViews())
309                tab(declareVariables())
310                tab(declareBoundValues())
311                tab(declareListeners())
312                tab(declareConstructor(minSdk))
313                tab(declareInvalidateAll())
314                tab(declareHasPendingBindings())
315                tab(declareSetVariable())
316                tab(variableSettersAndGetters())
317                tab(onFieldChange())
318
319                tab(executePendingBindings())
320
321                tab(declareListenerImpls())
322                tab(declareDirtyFlags())
323                if (!layoutBinder.hasVariations()) {
324                    tab(declareFactories())
325                }
326            }
327            nl("}")
328            tab(flagMapping())
329            tab("//end")
330        }.generate()
331    }
332    fun calculateIndices() : Unit {
333        val taggedViews = layoutBinder.getBindingTargets().filter{
334            it.isUsed() && it.getTag() != null && !it.isBinder()
335        }
336        taggedViews.forEach {
337            indices.put(it, indexFromTag(it.getTag()))
338        }
339        val indexStart = maxIndex() + 1
340        layoutBinder.getBindingTargets().filter{
341            it.isUsed() && !taggedViews.contains(it)
342        }.withIndex().forEach {
343            indices.put(it.value, it.index + indexStart)
344        }
345    }
346    fun declareIncludeViews() = kcode("") {
347        nl("private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;")
348        nl("private static final android.util.SparseIntArray sViewsWithIds;")
349        nl("static {") {
350            val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
351            if (!hasBinders) {
352                tab("sIncludes = null;")
353            } else {
354                val numBindings = layoutBinder.getBindingTargets().filter{ it.isUsed() }.count()
355                tab("sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts(${numBindings});")
356                val includeMap = HashMap<BindingTarget, ArrayList<BindingTarget>>()
357                layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder() }.forEach {
358                    val includeTag = it.getTag();
359                    val parent = layoutBinder.getBindingTargets().firstOrNull {
360                        it.isUsed() && !it.isBinder() && includeTag.equals(it.getTag())
361                    }
362                    if (parent == null) {
363                        throw IllegalStateException("Could not find parent of include file")
364                    }
365                    var list = includeMap.get(parent)
366                    if (list == null) {
367                        list = ArrayList<BindingTarget>()
368                        includeMap.put(parent, list)
369                    }
370                    list.add(it)
371                }
372
373                includeMap.keySet().forEach {
374                    val index = indices.get(it)
375                    tab("sIncludes.setIncludes(${index}, ") {
376                        tab ("new String[] {${
377                        includeMap.get(it)!!.map {
378                            "\"${it.getIncludedLayout()}\""
379                        }.joinToString(", ")
380                        }},")
381                        tab("new int[] {${
382                        includeMap.get(it)!!.map {
383                            "${indices.get(it)}"
384                        }.joinToString(", ")
385                        }},")
386                        tab("new int[] {${
387                        includeMap.get(it)!!.map {
388                            "R.layout.${it.getIncludedLayout()}"
389                        }.joinToString(", ")
390                        }});")
391                    }
392                }
393            }
394            val viewsWithIds = layoutBinder.getBindingTargets().filter {
395                it.isUsed() && !it.isBinder() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
396            }
397            if (viewsWithIds.isEmpty()) {
398                tab("sViewsWithIds = null;")
399            } else {
400                tab("sViewsWithIds = new android.util.SparseIntArray();")
401                viewsWithIds.forEach {
402                    tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
403                }
404            }
405        }
406        nl("}")
407    }
408
409    fun maxIndex() : kotlin.Int {
410        val maxIndex = indices.values().max()
411        if (maxIndex == null) {
412            return -1
413        } else {
414            return maxIndex
415        }
416    }
417
418    fun declareConstructor(minSdk : kotlin.Int) = kcode("") {
419        val bindingCount = maxIndex() + 1
420        val parameterType : String
421        val superParam : String
422        if (layoutBinder.isMerge()) {
423            parameterType = "View[]"
424            superParam = "root[0]"
425        } else {
426            parameterType = "View"
427            superParam = "root"
428        }
429        val rootTagsSupported = minSdk >= 14
430        if (layoutBinder.hasVariations()) {
431            nl("")
432            nl("public ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
433                tab("this(bindingComponent, ${superParam}, mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds));")
434            }
435            nl("}")
436            nl("private ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root, Object[] bindings) {") {
437                tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()}") {
438                    layoutBinder.getSortedTargets().filter { it.getId() != null }.forEach {
439                        tab(", ${fieldConversion(it)}")
440                    }
441                    tab(");")
442                }
443            }
444        } else {
445            nl("public ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
446                tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()});")
447                tab("final Object[] bindings = mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds);")
448            }
449        }
450        if (layoutBinder.requiredComponent != null) {
451            tab("ensureBindingComponentIsNotNull(${layoutBinder.requiredComponent}.class);")
452        }
453        val taggedViews = layoutBinder.getSortedTargets().filter{it.isUsed()}
454        taggedViews.forEach {
455            if (!layoutBinder.hasVariations() || it.getId() == null) {
456                tab("this.${it.fieldName} = ${fieldConversion(it)};")
457            }
458            if (!it.isBinder()) {
459                if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
460                    tab("this.${it.fieldName}.setContainingBinding(this);")
461                }
462                if (it.supportsTag() && it.getTag() != null &&
463                        (rootTagsSupported || it.getTag().startsWith("binding_"))) {
464                    val originalTag = it.getOriginalTag();
465                    var tagValue = "null"
466                    if (originalTag != null && !originalTag.startsWith("@{")) {
467                        tagValue = "\"${originalTag}\""
468                        if (originalTag.startsWith("@")) {
469                            var packageName = layoutBinder.getModulePackage()
470                            if (originalTag.startsWith("@android:")) {
471                                packageName = "android"
472                            }
473                            val slashIndex = originalTag.indexOf('/')
474                            val resourceId = originalTag.substring(slashIndex + 1)
475                            tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
476                        }
477                    }
478                    tab("this.${it.fieldName}.setTag(${tagValue});")
479                } else if (it.getTag() != null && !it.getTag().startsWith("binding_") &&
480                    it.getOriginalTag() != null) {
481                    L.e(ErrorMessages.ROOT_TAG_NOT_SUPPORTED, it.getOriginalTag())
482                }
483            }
484        }
485        tab("setRootTag(root);")
486        tab("invalidateAll();");
487        nl("}")
488    }
489
490    fun fieldConversion(target : BindingTarget) : String {
491        if (!target.isUsed()) {
492            return "null"
493        } else {
494            val index = indices.get(target)
495            if (index == null) {
496                throw IllegalStateException("Unknown binding target")
497            }
498            val variableName = "bindings[${index}]"
499            return target.superConversion(variableName)
500        }
501    }
502
503    fun declareInvalidateAll() = kcode("") {
504        nl("@Override")
505        nl("public void invalidateAll() {") {
506            val fs = FlagSet(layoutBinder.getModel().getInvalidateAnyBitSet(),
507                    layoutBinder.getModel().getFlagBucketCount());
508            tab("synchronized(this) {") {
509                for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
510                    tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
511                }
512            } tab("}")
513            includedBinders.filter{it.isUsed()}.forEach { binder ->
514                tab("${binder.fieldName}.invalidateAll();")
515            }
516            tab("requestRebind();");
517        }
518        nl("}")
519    }
520
521    fun declareHasPendingBindings()  = kcode("") {
522        nl("@Override")
523        nl("public boolean hasPendingBindings() {") {
524            if (mDirtyFlags.buckets.size() > 0) {
525                tab("synchronized(this) {") {
526                    val flagCheck = 0.rangeTo(mDirtyFlags.buckets.size() - 1).map {
527                            "${mDirtyFlags.localValue(it)} != 0"
528                    }.joinToString(" || ")
529                    tab("if (${flagCheck}) {") {
530                        tab("return true;")
531                    }
532                    tab("}")
533                }
534                tab("}")
535            }
536            includedBinders.filter{it.isUsed()}.forEach { binder ->
537                tab("if (${binder.fieldName}.hasPendingBindings()) {") {
538                    tab("return true;")
539                }
540                tab("}")
541            }
542            tab("return false;")
543        }
544        nl("}")
545    }
546
547    fun declareSetVariable() = kcode("") {
548        nl("public boolean setVariable(int variableId, Object variable) {") {
549            tab("switch(variableId) {") {
550                usedVariables.forEach {
551                    tab ("case ${it.getName().br()} :") {
552                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
553                        tab("return true;")
554                    }
555                }
556                val declaredOnly = variables.filter { !it.isUsed() && it.isDeclared() };
557                declaredOnly.forEachIndexed { i, identifierExpr ->
558                    tab ("case ${identifierExpr.getName().br()} :") {
559                        if (i == declaredOnly.size() - 1) {
560                            tab("return true;")
561                        }
562                    }
563                }
564            }
565            tab("}")
566            tab("return false;")
567        }
568        nl("}")
569    }
570
571    fun variableSettersAndGetters() = kcode("") {
572        variables.filterNot{it.isUsed()}.forEach {
573            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
574                tab("// not used, ignore")
575            }
576            nl("}")
577            nl("")
578            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
579                tab("return ${it.getDefaultValue()};")
580            }
581            nl("}")
582        }
583        usedVariables.forEach {
584            if (it.getUserDefinedType() != null) {
585                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
586                    if (it.isObservable()) {
587                        tab("updateRegistration(${it.getId()}, ${it.readableName});");
588                    }
589                    tab("this.${it.fieldName} = ${it.readableName};")
590                    // set dirty flags!
591                    val flagSet = it.invalidateFlagSet
592                    tab("synchronized(this) {") {
593                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
594                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
595                        }
596                    } tab ("}")
597                    tab("super.requestRebind();")
598                }
599                nl("}")
600                nl("")
601                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
602                    tab("return ${it.fieldName};")
603                }
604                nl("}")
605            }
606        }
607    }
608
609    fun onFieldChange() = kcode("") {
610        nl("@Override")
611        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
612            tab("switch (localFieldId) {") {
613                model.getObservables().forEach {
614                    tab("case ${it.getId()} :") {
615                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
616                    }
617                }
618            }
619            tab("}")
620            tab("return false;")
621        }
622        nl("}")
623        nl("")
624
625        model.getObservables().forEach {
626            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
627                tab("switch (fieldId) {", {
628                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
629                    accessedFields.filter { it.hasBindableAnnotations() }
630                            .groupBy { it.brName }
631                            .forEach {
632                                // If two expressions look different but resolve to the same method,
633                                // we are not yet able to merge them. This is why we merge their
634                                // flags below.
635                                tab("case ${it.key}:") {
636                                    tab("synchronized(this) {") {
637                                        val flagSet = it.value.foldRight(FlagSet()) { l, r -> l.invalidateFlagSet.or(r) }
638
639                                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
640                                            tab("${mDirtyFlags.localValue(index)} |= ${flagSet.localValue(index)};")
641                                        }
642                                    } tab("}")
643                                    tab("return true;")
644                                }
645
646                            }
647                    tab("case ${"".br()}:") {
648                        val flagSet = it.invalidateFlagSet
649                        tab("synchronized(this) {") {
650                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
651                                tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
652                            }
653                        } tab("}")
654                        tab("return true;")
655                    }
656
657                })
658                tab("}")
659                tab("return false;")
660            }
661            nl("}")
662            nl("")
663        }
664    }
665
666    fun declareViews() = kcode("// views") {
667        val oneLayout = !layoutBinder.hasVariations();
668        layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
669            val access : String
670            if (oneLayout && it.getId() != null) {
671                access = "public"
672            } else {
673                access = "private"
674            }
675            nl("${access} final ${it.interfaceType} ${it.fieldName};")
676        }
677    }
678
679    fun declareVariables() = kcode("// variables") {
680        usedVariables.forEach {
681            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
682        }
683    }
684
685    fun declareBoundValues() = kcode("// values") {
686        layoutBinder.getSortedTargets().filter { it.isUsed() }
687                .flatMap { it.getBindings() }
688                .filter { it.requiresOldValue() }
689                .flatMap{ it.getComponentExpressions().toArrayList() }
690                .groupBy { it }
691                .forEach {
692                    val expr = it.getKey()
693                    nl("private ${expr.getResolvedType().toJavaCode()} ${expr.oldValueName};")
694                }
695    }
696
697    fun declareListeners() = kcode("// listeners") {
698        model.getExprMap().values().filter {
699            it is ListenerExpr
700        }.groupBy { it }.forEach {
701            val expr = it.key as ListenerExpr
702            nl("private ${expr.listenerClassName} ${expr.fieldName};")
703        }
704    }
705
706    fun declareDirtyFlags() = kcode("// dirty flag") {
707        model.ext.localizedFlags.forEach { flag ->
708            flag.notEmpty { suffix, value ->
709                nl("private")
710                app(" ", if(flag.isDynamic()) null else "static final");
711                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
712            }
713        }
714    }
715
716    fun flagMapping() = kcode("/* flag mapping") {
717        if (model.getFlagMapping() != null) {
718            val mapping = model.getFlagMapping()
719            for (i in mapping.indices) {
720                tab("flag $i: ${mapping[i]}")
721            }
722        }
723        nl("flag mapping end*/")
724    }
725
726    fun executePendingBindings() = kcode("") {
727        nl("@Override")
728        nl("protected void executeBindings() {") {
729            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
730            tmpDirtyFlags.setLocalName("dirtyFlags");
731            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
732                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
733            }
734            tab("synchronized(this) {") {
735                for (i in (0..mDirtyFlags.buckets.size() - 1)) {
736                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
737                    tab("${mDirtyFlags.localValue(i)} = 0;")
738                }
739            } tab("}")
740            model.getPendingExpressions().filter { it.needsLocalField }.forEach {
741                tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.getDefaultValue()};")
742            }
743            L.d("writing executePendingBindings for %s", className)
744            do {
745                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
746                val justRead = arrayListOf<Expr>()
747                L.d("batch: %s", batch)
748                while (!batch.none()) {
749                    val readNow = batch.filter { it.shouldReadNow(justRead) }
750                    if (readNow.isEmpty()) {
751                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
752                    }
753                    L.d("new read now. batch size: %d, readNow size: %d", batch.size(), readNow.size())
754                    nl(readWithDependants(readNow, justRead, batch, tmpDirtyFlags))
755                    batch.removeAll(justRead)
756                }
757                tab("// batch finished")
758            } while (model.markBitsRead())
759            // verify everything is read.
760            val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
761            if (batch.isNotEmpty()) {
762                L.e("could not generate code for %s. This might be caused by circular dependencies."
763                        + "Please report on b.android.com. %d %s %s", layoutBinder.getLayoutname(),
764                        batch.size(), batch.get(0), batch.get(0).toCode().generate())
765            }
766            //
767            layoutBinder.getSortedTargets().filter { it.isUsed() }
768                    .flatMap { it.getBindings() }
769                    .groupBy {
770                        "${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
771                            "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
772                        }.joinToString(" || ") }"
773                    }.forEach {
774                tab("if (${it.key}) {") {
775                    it.value.groupBy { Math.max(1, it.getMinApi()) }.forEach {
776                        val setterValues = kcode("") {
777                            it.value.forEach { binding ->
778                                val fieldName: String
779                                if (binding.getTarget().getViewClass().
780                                        equals(binding.getTarget().getInterfaceType())) {
781                                    fieldName = "this.${binding.getTarget().fieldName}"
782                                } else {
783                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
784                                }
785                                tab(binding.toJavaCode(fieldName, "this.mBindingComponent")).app(";")
786                            }
787                        }
788                        tab("// api target ${it.key}")
789                        if (it.key > 1) {
790                            tab("if(getBuildSdkInt() >= ${it.key}) {") {
791                                app("", setterValues)
792                            }
793                            tab("}")
794                        } else {
795                            app("", setterValues)
796                        }
797                    }
798                }
799                tab("}")
800            }
801
802
803            layoutBinder.getSortedTargets().filter { it.isUsed() }
804                    .flatMap { it.getBindings() }
805                    .filter { it.requiresOldValue() }
806                    .groupBy {"${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
807                        "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
808                    }.joinToString(" || ")
809                    }"}.forEach {
810                tab("if (${it.key}) {") {
811                    it.value.groupBy { it.getExpr() }.map { it.value.first() }.forEach {
812                        it.getComponentExpressions().forEach { expr ->
813                            tab("this.${expr.oldValueName} = ${expr.toCode().generate()};")
814                        }
815                    }
816                }
817                tab("}")
818            }
819            includedBinders.filter{it.isUsed()}.forEach { binder ->
820                tab("${binder.fieldName}.executePendingBindings();")
821            }
822            layoutBinder.getSortedTargets().filter{
823                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
824            }.forEach {
825                tab("if (${it.fieldName}.getBinding() != null) {") {
826                    tab("${it.fieldName}.getBinding().executePendingBindings();")
827                }
828                tab("}")
829            }
830        }
831        nl("}")
832    }
833
834    fun readWithDependants(expressionList: List<Expr>, justRead: MutableList<Expr>,
835            batch: MutableList<Expr>, tmpDirtyFlags: FlagSet,
836            inheritedFlags: FlagSet? = null) : KCode = kcode("") {
837        expressionList.groupBy { it.shouldReadFlagSet }.forEach {
838            val flagSet = it.key
839            val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
840            val expressions = it.value
841            val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
842                "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
843            }.joinToString(" || ")
844            })"
845            val readCode = kcode("") {
846                val dependants = ArrayList<Expr>()
847                expressions.groupBy { condition(it) }.forEach {
848                    val condition = it.key
849                    val assignedValues = it.value.filter { it.needsLocalField }
850                    if (!assignedValues.isEmpty()) {
851                        val assignment = kcode("") {
852                            assignedValues.forEach { expr: Expr ->
853                                tab("// read ${expr.getUniqueKey()}")
854                                tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";")
855                            }
856                        }
857                        if (condition != null) {
858                            tab("if (${condition}) {") {
859                                app("", assignment)
860                            }
861                            tab ("}")
862                        } else {
863                            app("", assignment)
864                        }
865                        it.value.filter { it.isObservable() }.forEach { expr: Expr ->
866                            tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
867                        }
868                    }
869
870                    it.value.forEach { expr: Expr ->
871                        justRead.add(expr)
872                        L.d("%s / readWithDependants %s", className, expr.getUniqueKey());
873                        L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
874
875                        // if I am the condition for an expression, set its flag
876                        expr.getDependants().filter {
877                            !it.isConditional() && it.getDependant() is TernaryExpr &&
878                                    (it.getDependant() as TernaryExpr).getPred() == expr
879                        }.map { it.getDependant() }.groupBy {
880                            // group by when those ternaries will be evaluated (e.g. don't set conditional flags for no reason)
881                            val ternaryBitSet = it.getShouldReadFlagsWithConditionals()
882                            val isBehindTernary = ternaryBitSet.nextSetBit(model.getInvalidateAnyFlagIndex()) == -1
883                            if (!isBehindTernary) {
884                                val ternaryFlags = it.shouldReadWithConditionalsFlagSet
885                                "if(${tmpDirtyFlags.mapOr(ternaryFlags){ suffix, index ->
886                                    "(${tmpDirtyFlags.localValue(index)} & ${ternaryFlags.localValue(index)}) != 0"
887                                }.joinToString(" || ")}) {"
888                            } else {
889                                // TODO if it is behind a ternary, we should set it when its predicate is elevated
890                                // Normally, this would mean that there is another code path to re-read our current expression.
891                                // Unfortunately, this may not be true due to the coverage detection in `expr#markAsReadIfDone`, this may never happen.
892                                // for v1.0, we'll go with always setting it and suffering an unnecessary calculation for this edge case.
893                                // we can solve this by listening to elevation events from the model.
894                                ""
895                            }
896                        }.forEach {
897                            val hasAnotherIf = it.key != ""
898                            if (hasAnotherIf) {
899                                tab(it.key) {
900                                    tab("if (${expr.executePendingLocalName}) {") {
901                                        it.value.forEach {
902                                            val set = it.getRequirementFlagSet(true)
903                                            mDirtyFlags.mapOr(set) { suffix, index ->
904                                                tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
905                                            }
906                                        }
907                                    }
908                                    tab("} else {") {
909                                        it.value.forEach {
910                                            val set = it.getRequirementFlagSet(false)
911                                            mDirtyFlags.mapOr(set) { suffix, index ->
912                                                tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
913                                            }
914                                        }
915                                    }.tab("}")
916                                }.app("}")
917                            } else {
918                                tab("if (${expr.executePendingLocalName}) {") {
919                                    it.value.forEach {
920                                        val set = it.getRequirementFlagSet(true)
921                                        mDirtyFlags.mapOr(set) { suffix, index ->
922                                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
923                                        }
924                                    }
925                                }
926                                tab("} else {") {
927                                    it.value.forEach {
928                                        val set = it.getRequirementFlagSet(false)
929                                        mDirtyFlags.mapOr(set) { suffix, index ->
930                                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
931                                        }
932                                    }
933                                } app("}")
934                            }
935                        }
936                        val chosen = expr.getDependants().filter {
937                            val dependant = it.getDependant()
938                            batch.contains(dependant) &&
939                                    dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
940                                    dependant.shouldReadNow(justRead)
941                        }
942                        if (chosen.isNotEmpty()) {
943                            dependants.addAll(chosen.map { it.getDependant() })
944                        }
945                    }
946                }
947                if (dependants.isNotEmpty()) {
948                    val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
949                    nl(readWithDependants(dependants, justRead, batch, tmpDirtyFlags, nextInheritedFlags))
950                }
951            }
952
953            if (needsIfWrapper) {
954                tab(ifClause) {
955                    app(" {")
956                    app("", readCode)
957                }
958                tab("}")
959            } else {
960                app("", readCode)
961            }
962        }
963    }
964
965    fun condition(expr : Expr) : String? {
966        if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
967            // create an if case for all dependencies that might be null
968            val nullables = expr.getDependencies().filter {
969                it.isMandatory() && it.getOther().getResolvedType().isNullable()
970            }.map { it.getOther() }
971            if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
972                return "${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}"
973            } else {
974                return null
975            }
976        } else {
977            return null
978        }
979    }
980
981    fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
982        model.getExprMap().values().filter {
983            it.isUsed() && it is ListenerExpr
984        }.groupBy { it }.forEach {
985            val expr = it.key as ListenerExpr
986            val listenerType = expr.getResolvedType();
987            val extendsImplements : String
988            if (listenerType.isInterface()) {
989                extendsImplements = "implements"
990            } else {
991                extendsImplements = "extends"
992            }
993            nl("public static class ${expr.listenerClassName} ${extendsImplements} ${listenerType.getCanonicalName()}{") {
994                if (expr.getChild().isDynamic()) {
995                    tab("private ${expr.getChild().getResolvedType().toJavaCode()} value;")
996                    tab("public ${expr.listenerClassName} setValue(${expr.getChild().getResolvedType().toJavaCode()} value) {") {
997                        tab("this.value = value;")
998                        tab("return value == null ? null : this;")
999                    }
1000                    tab("}")
1001                }
1002                val listenerMethod = expr.getMethod()
1003                val parameterTypes = listenerMethod.getParameterTypes()
1004                val returnType = listenerMethod.getReturnType(parameterTypes.toArrayList())
1005                tab("@Override")
1006                tab("public ${returnType} ${listenerMethod.getName()}(${
1007                    parameterTypes.withIndex().map {
1008                        "${it.value.toJavaCode()} arg${it.index}"
1009                    }.joinToString(", ")
1010                }) {") {
1011                    val obj : String
1012                    if (expr.getChild().isDynamic()) {
1013                        obj = "this.value"
1014                    } else {
1015                        obj = expr.getChild().toCode().generate();
1016                    }
1017                    val returnStr : String
1018                    if (!returnType.isVoid()) {
1019                        returnStr = "return "
1020                    } else {
1021                        returnStr = ""
1022                    }
1023                    val args = parameterTypes.withIndex().map {
1024                        "arg${it.index}"
1025                    }.joinToString(", ")
1026                    tab("${returnStr}${obj}.${expr.getName()}(${args});")
1027                }
1028                tab("}")
1029            }
1030            nl("}")
1031        }
1032    }
1033
1034    fun declareFactories() = kcode("") {
1035        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1036            tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1037        }
1038        nl("}")
1039        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1040            tab("return android.databinding.DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
1041        }
1042        nl("}")
1043        if (!layoutBinder.isMerge()) {
1044            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
1045                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1046            }
1047            nl("}")
1048            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1049                tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false), bindingComponent);")
1050            }
1051            nl("}")
1052            nl("public static ${baseClassName} bind(android.view.View view) {") {
1053                tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1054            }
1055            nl("}")
1056            nl("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1057                tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
1058                    tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
1059                }
1060                tab("}")
1061                tab("return new ${baseClassName}(bindingComponent, view);")
1062            }
1063            nl("}")
1064        }
1065    }
1066
1067    /**
1068     * When called for a library compilation, we do not generate real implementations
1069     */
1070    public fun writeBaseClass(forLibrary : Boolean) : String =
1071        kcode("package ${layoutBinder.getPackage()};") {
1072            nl("import android.databinding.Bindable;")
1073            nl("import android.databinding.DataBindingUtil;")
1074            nl("import android.databinding.ViewDataBinding;")
1075            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
1076            layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1077                tab("public final ${it.interfaceType} ${it.fieldName};")
1078            }
1079            nl("")
1080            tab("protected ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
1081                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1082                    tab(", ${it.interfaceType} ${it.constructorParamName}")
1083                }
1084            }
1085            tab(") {") {
1086                tab("super(bindingComponent, root_, localFieldCount);")
1087                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1088                    tab("this.${it.fieldName} = ${it.constructorParamName};")
1089                }
1090            }
1091            tab("}")
1092            nl("")
1093            variables.forEach {
1094                if (it.getUserDefinedType() != null) {
1095                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
1096                    tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
1097                }
1098            }
1099            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1100                tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1101            }
1102            tab("}")
1103            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
1104                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1105            }
1106            tab("}")
1107            tab("public static ${baseClassName} bind(android.view.View view) {") {
1108                if (forLibrary) {
1109                    tab("return null;")
1110                } else {
1111                    tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1112                }
1113            }
1114            tab("}")
1115            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1116                if (forLibrary) {
1117                    tab("return null;")
1118                } else {
1119                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
1120                }
1121            }
1122            tab("}")
1123            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1124                if (forLibrary) {
1125                    tab("return null;")
1126                } else {
1127                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false, bindingComponent);")
1128                }
1129            }
1130            tab("}")
1131            tab("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1132                if (forLibrary) {
1133                    tab("return null;")
1134                } else {
1135                    tab("return (${baseClassName})bind(bindingComponent, view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
1136                }
1137            }
1138            tab("}")
1139            nl("}")
1140        }.generate()
1141}
1142