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