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