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