LayoutBinderWriter.kt revision 61630faa88ee4817834d47294a0e17f19d8e1c51
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.LayoutBinder
17import android.databinding.tool.expr.Expr
18import kotlin.properties.Delegates
19import android.databinding.tool.ext.joinToCamelCaseAsVar
20import android.databinding.tool.BindingTarget
21import android.databinding.tool.expr.IdentifierExpr
22import android.databinding.tool.util.Log
23import java.util.BitSet
24import android.databinding.tool.expr.ExprModel
25import java.util.Arrays
26import android.databinding.tool.expr.TernaryExpr
27import android.databinding.tool.expr.FieldAccessExpr
28import android.databinding.tool.expr.ComparisonExpr
29import android.databinding.tool.expr.GroupExpr
30import android.databinding.tool.expr.MathExpr
31import android.databinding.tool.expr.MethodCallExpr
32import android.databinding.tool.expr.StaticIdentifierExpr
33import android.databinding.tool.expr.SymbolExpr
34import android.databinding.tool.ext.androidId
35import android.databinding.tool.ext.lazy
36import android.databinding.tool.ext.br
37import android.databinding.tool.expr.ResourceExpr
38import android.databinding.tool.expr.BracketExpr
39import android.databinding.tool.reflection.Callable
40import android.databinding.tool.expr.CastExpr
41import android.databinding.tool.reflection.ModelAnalyzer
42import java.util.HashMap
43
44fun String.stripNonJava() = this.split("[^a-zA-Z0-9]").map{ it.trim() }.joinToCamelCaseAsVar()
45
46class ExprModelExt {
47    val usedFieldNames = hashSetOf<String>()
48    val localizedFlags = arrayListOf<FlagSet>()
49
50    fun localizeFlag(set : FlagSet, name:String) : FlagSet {
51        localizedFlags.add(set)
52        val result = getUniqueFieldName(name)
53        set.setLocalName(result)
54        return set
55    }
56
57    fun getUniqueFieldName(base : String) : String {
58        var candidate = base
59        var i = 0
60        while (usedFieldNames.contains(candidate)) {
61            i ++
62            candidate = base + i
63        }
64        usedFieldNames.add(candidate)
65        return candidate
66    }
67}
68
69val ExprModel.ext by Delegates.lazy { target : ExprModel ->
70    ExprModelExt()
71}
72
73fun ExprModel.getUniqueFieldName(base : String) : String = ext.getUniqueFieldName(base)
74
75fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
76
77val BindingTarget.readableUniqueName by Delegates.lazy { target: BindingTarget ->
78    val variableName : String
79    if (target.getId() == null) {
80        variableName = "boundView" + target.getTag()
81    } else {
82        variableName = target.getId().androidId().stripNonJava()
83    }
84    target.getModel().ext.getUniqueFieldName(variableName)
85}
86
87fun BindingTarget.superConversion(variable : String) : String {
88    if (isBinder()) {
89        return "${getViewClass()}.bind(${variable})"
90    } else if (getResolvedType() != null && getResolvedType().extendsViewStub()) {
91        return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})"
92    } else {
93        return "(${interfaceType}) ${variable}"
94    }
95}
96
97val BindingTarget.fieldName by Delegates.lazy { target : BindingTarget ->
98    if (target.getFieldName() == null) {
99        if (target.getId() == null) {
100            target.setFieldName("m${target.readableUniqueName.capitalize()}")
101        } else {
102            target.androidId.stripNonJava();
103            target.setFieldName(target.readableUniqueName);
104        }
105    }
106    target.getFieldName();
107}
108
109val BindingTarget.getterName by Delegates.lazy { target : BindingTarget ->
110    "get${target.readableUniqueName.capitalize()}"
111}
112
113val BindingTarget.androidId by Delegates.lazy { target : BindingTarget ->
114    "R.id.${target.getId().androidId()}"
115}
116
117val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget ->
118    if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) {
119        "android.databinding.ViewStubProxy"
120    } else {
121        target.getInterfaceType()
122    }
123}
124
125val Expr.readableUniqueName by Delegates.lazy { expr : Expr ->
126    Log.d { "readableUniqueName for ${expr.getUniqueKey()}" }
127    val stripped = "${expr.getUniqueKey().stripNonJava()}"
128    expr.getModel().ext.getUniqueFieldName(stripped)
129}
130
131val Expr.readableName by Delegates.lazy { expr : Expr ->
132    Log.d { "readableUniqueName for ${expr.getUniqueKey()}" }
133    "${expr.getUniqueKey().stripNonJava()}"
134}
135
136val Expr.fieldName by Delegates.lazy { expr : Expr ->
137    "m${expr.readableName.capitalize()}"
138}
139
140val Expr.hasFlag by Delegates.lazy { expr : Expr ->
141    expr.getId() < expr.getModel().getInvalidateableFieldLimit()
142}
143
144val Expr.localName by Delegates.lazy { expr : Expr ->
145    if(expr.isVariable()) expr.fieldName else "${expr.readableUniqueName}"
146}
147
148val Expr.setterName by Delegates.lazy { expr : Expr ->
149    "set${expr.readableName.capitalize()}"
150}
151
152val Expr.onChangeName by Delegates.lazy { expr : Expr ->
153    "onChange${expr.readableUniqueName.capitalize()}"
154}
155
156val Expr.getterName by Delegates.lazy { expr : Expr ->
157    "get${expr.readableName.capitalize()}"
158}
159
160val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
161    "sFlag${expr.readableUniqueName.capitalize()}"
162}
163
164val Expr.shouldReadFlagName by Delegates.lazy { expr : Expr ->
165    "sFlagRead${expr.readableUniqueName.capitalize()}"
166}
167
168val Expr.invalidateFlagName by Delegates.lazy { expr : Expr ->
169    "sFlag${expr.readableUniqueName.capitalize()}Invalid"
170}
171
172val Expr.conditionalFlagPrefix by Delegates.lazy { expr : Expr ->
173    "sFlag${expr.readableUniqueName.capitalize()}Is"
174}
175
176
177fun Expr.toCode(full : Boolean = false) : KCode {
178    val it = this
179    if (isDynamic() && !full) {
180        return kcode(localName)
181    }
182    return when (it) {
183        is ComparisonExpr -> kcode("") {
184            app("", it.getLeft().toCode())
185            app(it.getOp())
186            app("", it.getRight().toCode())
187        }
188        is FieldAccessExpr -> kcode("") {
189            app("", it.getChild().toCode())
190            if (it.getGetter().type == Callable.Type.FIELD) {
191                app(".", it.getGetter().name)
192            } else {
193                app(".", it.getGetter().name).app("()")
194            }
195        }
196        is GroupExpr -> kcode("(").app("", it.getWrapped().toCode()).app(")")
197        is StaticIdentifierExpr -> kcode(it.getResolvedType().toJavaCode())
198        is IdentifierExpr -> kcode(it.localName)
199        is MathExpr -> kcode("") {
200            app("", it.getLeft().toCode())
201            app(it.getOp())
202            app("", it.getRight().toCode())
203        }
204        is MethodCallExpr -> kcode("") {
205            app("", it.getTarget().toCode())
206            app(".", it.getGetter().name)
207            app("(")
208            var first = true
209            it.getArgs().forEach {
210                apps(if (first) "" else ",", it.toCode())
211                first = false
212            }
213            app(")")
214        }
215        is SymbolExpr -> kcode(it.getText()) // TODO
216        is TernaryExpr -> kcode("") {
217            app("", it.getPred().toCode())
218            app("?", it.getIfTrue().toCode())
219            app(":", it.getIfFalse().toCode())
220        }
221        is ResourceExpr -> kcode("") {
222            app("", it.toJava())
223        }
224        is BracketExpr -> kcode("") {
225            app("", it.getTarget().toCode())
226            val bracketType = it.getAccessor();
227            when (bracketType) {
228                BracketExpr.BracketAccessor.ARRAY -> {
229                    app("[", it.getArg().toCode())
230                    app("]")
231                }
232                BracketExpr.BracketAccessor.LIST -> {
233                    app(".get(")
234                    if (it.argCastsInteger()) {
235                        app("(Integer)")
236                    }
237                    app("", it.getArg().toCode())
238                    app(")")
239                }
240                BracketExpr.BracketAccessor.MAP -> {
241                    app(".get(", it.getArg().toCode())
242                    app(")")
243                }
244            }
245        }
246        is CastExpr -> kcode("") {
247            app("(", it.getCastType())
248            app(") ", it.getCastExpr().toCode())
249        }
250        else -> kcode("//NOT IMPLEMENTED YET")
251    }
252
253}
254
255fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
256
257fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix"
258
259
260val Expr.dirtyFlagSet by Delegates.lazy { expr : Expr ->
261    val fs = FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount())
262    expr.getModel().localizeFlag(fs, expr.dirtyFlagName)
263}
264
265val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr ->
266    val fs = FlagSet(expr.getId())
267    expr.getModel().localizeFlag(fs, expr.invalidateFlagName)
268}
269
270val Expr.shouldReadFlagSet by Delegates.lazy { expr : Expr ->
271    val fs = FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount())
272    expr.getModel().localizeFlag(fs, expr.shouldReadFlagName)
273}
274
275val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
276    val model = expr.getModel()
277    arrayListOf(model.localizeFlag(FlagSet(expr.getRequirementFlagIndex(false)),
278            "${expr.conditionalFlagPrefix}False"),
279            model.localizeFlag(FlagSet(expr.getRequirementFlagIndex(true)),
280                    "${expr.conditionalFlagPrefix}True"))
281}
282
283fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
284
285fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
286    buckets.withIndex().forEach {
287        if (it.value != 0L) {
288            cb(getWordSuffix(it.index), buckets[it.index])
289        }
290    }
291}
292
293fun FlagSet.getBitSuffix(bitIndex : Int) : String {
294    val word = bitIndex / FlagSet.sBucketSize
295    return getWordSuffix(word)
296}
297
298fun FlagSet.getWordSuffix(wordIndex : Int) : String {
299    return if(wordIndex == 0) "" else "_${wordIndex}"
300}
301
302fun FlagSet.localValue(bucketIndex : Int) =
303        if (getLocalName() == null) buckets[bucketIndex]
304        else "${getLocalName()}${getWordSuffix(bucketIndex)}"
305
306fun FlagSet.or(other : FlagSet, cb : (suffix : String) -> Unit) {
307    val min = Math.min(buckets.size(), other.buckets.size())
308    for (i in 0..(min - 1)) {
309        // if these two can match by any chance, call the callback
310        if (intersect(other, i)) {
311            cb(getWordSuffix(i))
312        }
313    }
314}
315
316fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
317    val min = Math.min(buckets.size(), other.buckets.size())
318    val result = arrayListOf<T>()
319    for (i in 0..(min - 1)) {
320        // if these two can match by any chance, call the callback
321        if (intersect(other, i)) {
322            result.add(cb(getWordSuffix(i), i))
323        }
324    }
325    return result
326}
327
328class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
329    val model = layoutBinder.getModel()
330    val indices = HashMap<BindingTarget, kotlin.Int>()
331    val mDirtyFlags by Delegates.lazy {
332        val fs = FlagSet(BitSet(), model.getFlagBucketCount());
333        Arrays.fill(fs.buckets, -1)
334        fs.setDynamic(true)
335        model.localizeFlag(fs, "mDirtyFlags")
336        fs
337    }
338
339    val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
340    val className = layoutBinder.getClassName()
341
342    val identifiers by Delegates.lazy {
343        dynamics.filter { it is IdentifierExpr }
344    }
345
346    val baseClassName = "${layoutBinder.getInterfaceName()}"
347
348    val includedBinders by Delegates.lazy {
349        layoutBinder.getBindingTargets().filter { it.isBinder() }
350    }
351
352    val variables by Delegates.lazy {
353        model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
354    }
355
356    val usedVariables by Delegates.lazy {
357        variables.filter {it.isUsed()}
358    }
359
360    public fun write() : String  {
361        layoutBinder.resolveWhichExpressionsAreUsed()
362        calculateIndices();
363        return kcode("package ${layoutBinder.getPackage()};") {
364            nl("import ${layoutBinder.getModulePackage()}.R;")
365            nl("import ${layoutBinder.getModulePackage()}.BR;")
366            nl("import android.view.View;")
367            nl("public class ${className} extends ${baseClassName} {") {
368                tab(declareIncludeViews())
369                tab(declareViews())
370                tab(declareVariables())
371                tab(declareConstructor())
372                tab(declareInvalidateAll())
373                tab(declareLog())
374                tab(declareSetVariable())
375                tab(variableSettersAndGetters())
376                tab(onFieldChange())
377
378                tab(executePendingBindings())
379
380                tab(declareDirtyFlags())
381            }
382            nl("}")
383            tab(flagMapping())
384            tab("//end")
385        }.generate()
386    }
387    fun calculateIndices() : Unit {
388        val numTaggedViews = layoutBinder.getBindingTargets().
389                filter{it.isUsed() && !it.isBinder()}.count()
390        layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() != null }.forEach {
391            indices.put(it, Integer.parseInt(it.getTag()));
392        }
393        layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder()}.withIndex().forEach {
394            indices.put(it.value, it.index + numTaggedViews);
395        }
396    }
397    fun declareIncludeViews() = kcode("") {
398        nl("private static final android.util.SparseIntArray sIncludes;")
399        nl("private static final android.util.SparseIntArray sViewsWithIds;")
400        nl("static {") {
401            val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
402            if (!hasBinders) {
403                tab("sIncludes = null;")
404            } else {
405                tab("sIncludes = new android.util.SparseIntArray();")
406                layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder()}.forEach {
407                    tab("sIncludes.put(${it.androidId}, ${indices.get(it)});")
408                }
409            }
410            val hasViewsWithIds = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && !it.supportsTag()} != null
411            if (!hasViewsWithIds) {
412                tab("sViewsWithIds = null;")
413            } else {
414                tab("sViewsWithIds = new android.util.SparseIntArray();")
415                layoutBinder.getBindingTargets().filter{ it.isUsed() && !it.supportsTag() }.
416                        forEach {
417                    tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
418                }
419            }
420        }
421        nl("}")
422    }
423    fun declareConstructor() = kcode("") {
424        nl("private ${className}(View root, View[] views) {") {
425            tab("super(root, ${model.getObservables().size()}") {
426                layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
427                    val index = indices.get(it)
428                    if (!it.isUsed()) {
429                        tab(", null")
430                    } else{
431                        val variableName : String
432                        if (index == null) {
433                            variableName = "root";
434                        } else {
435                            variableName = "views[${index}]"
436                        }
437                        tab(", ${it.superConversion(variableName)}")
438                    }
439                }
440                tab(");")
441            }
442            val taggedViews = layoutBinder.getBindingTargets().filter{it.isUsed() && !it.isBinder()}
443            taggedViews.forEach {
444                if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
445                    tab("this.${it.fieldName}.setContainingBinding(this);")
446                }
447                if (it.getTag() == null) {
448                    if (it.getId() == null) {
449                        tab("this.${it.fieldName} = (${it.getViewClass()}) root;")
450                    }
451                } else {
452                    if (it.getId() == null) {
453                        tab("this.${it.fieldName} = (${it.getViewClass()}) views[${it.getTag()}];")
454                    }
455                    if (it.supportsTag()) {
456                        val originalTag = it.getOriginalTag();
457                        var tagValue = "null"
458                        if (originalTag != null) {
459                            tagValue = "\"${originalTag}\""
460                            if (originalTag.startsWith("@")) {
461                                var packageName = layoutBinder.getModulePackage()
462                                if (originalTag.startsWith("@android:")) {
463                                    packageName = "android"
464                                }
465                                val slashIndex = originalTag.indexOf('/')
466                                val resourceId = originalTag.substring(slashIndex + 1)
467                                tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
468                            }
469                        }
470                        tab("this.${it.fieldName}.setTag(${tagValue});")
471                    }
472                }
473            }
474            tab("invalidateAll();");
475            nl("}")
476        }
477        nl("")
478        nl("public ${className}(View root) {") {
479            val viewCount = layoutBinder.getBindingTargets().filter{it.isUsed()}.count()
480            tab("this(root, mapChildViews(root, ${viewCount}, sIncludes, sViewsWithIds));")
481        }
482        nl("}")
483    }
484
485    fun declareInvalidateAll() = kcode("") {
486        nl("@Override")
487        nl("public void invalidateAll() {") {
488            val bs = BitSet()
489            bs.set(0, model.getInvalidateableFieldLimit())
490            val fs = FlagSet(bs, mDirtyFlags.buckets.size())
491            for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
492                tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
493            }
494            includedBinders.filter{it.isUsed()}.forEach { binder ->
495                tab("${binder.fieldName}.invalidateAll();")
496            }
497        }
498        nl("}")
499    }
500
501    fun declareSetVariable() = kcode("") {
502        nl("public boolean setVariable(int variableId, Object variable) {") {
503            tab("switch(variableId) {") {
504                usedVariables.forEach {
505                    tab ("case ${it.getName().br()} :") {
506                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
507                        tab("return true;")
508                    }
509                }
510            }
511            tab("}")
512            tab("return false;")
513        }
514        nl("}")
515    }
516
517    fun declareLog() = kcode("") {
518        nl("private void log(String msg, long i) {") {
519            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
520        }
521        nl("}")
522    }
523
524    fun variableSettersAndGetters() = kcode("") {
525        variables.filterNot{it.isUsed()}.forEach {
526            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
527                tab("// not used, ignore")
528            }
529            nl("}")
530            nl("")
531            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
532                tab("return ${it.getDefaultValue()};")
533            }
534            nl("}")
535        }
536        usedVariables.forEach {
537            if (it.getUserDefinedType() != null) {
538                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
539                    if (it.isObservable()) {
540                        tab("updateRegistration(${it.getId()}, ${it.readableUniqueName});");
541                    }
542                    tab("this.${it.fieldName} = ${it.readableUniqueName};")
543                    // set dirty flags!
544                    val flagSet = it.invalidateFlagSet
545                    mDirtyFlags.mapOr(flagSet) { suffix, index ->
546                        tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
547                    }
548                    tab("super.requestRebind();")
549                }
550                nl("}")
551                nl("")
552                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
553                    tab("return ${it.fieldName};")
554                }
555                nl("}")
556            }
557        }
558    }
559
560    fun onFieldChange() = kcode("") {
561        nl("@Override")
562        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
563            tab("switch (localFieldId) {") {
564                model.getObservables().forEach {
565                    tab("case ${it.getId()} :") {
566                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
567                    }
568                }
569            }
570            tab("}")
571            tab("return false;")
572        }
573        nl("}")
574        nl("")
575
576        model.getObservables().forEach {
577            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}, int fieldId) {") {
578                tab("switch (fieldId) {", {
579                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
580                    accessedFields.filter { it.canBeInvalidated() }
581                            .groupBy { it.getName() }
582                            .forEach {
583                                tab("case ${it.key.br()}:") {
584                                    val field = it.value.first()
585                                    mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
586                                        tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
587                                    }
588                                    tab("return true;")
589                                }
590
591                            }
592                    tab("case ${"".br()}:") {
593                        val flagSet = it.invalidateFlagSet
594                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
595                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
596                        }
597                        tab("return true;")
598                    }
599
600                })
601                tab("}")
602                tab("return false;")
603            }
604            nl("}")
605            nl("")
606        }
607    }
608
609    fun declareViews() = kcode("// views") {
610        layoutBinder.getBindingTargets().filter {it.isUsed() && (it.getId() == null)}.forEach {
611            nl("private final ${it.interfaceType} ${it.fieldName};")
612        }
613    }
614
615    fun declareVariables() = kcode("// variables") {
616        usedVariables.forEach {
617            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
618        }
619    }
620
621    fun declareDirtyFlags() = kcode("// dirty flag") {
622        model.ext.localizedFlags.forEach { flag ->
623            flag.notEmpty { suffix, value ->
624                nl("private")
625                app(" ", if(flag.isDynamic()) null else "static final");
626                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = $value;")
627            }
628        }
629    }
630
631    fun flagMapping() = kcode("/* flag mapping") {
632        if (model.getFlagMapping() != null) {
633            val mapping = model.getFlagMapping()
634            for (i in mapping.indices) {
635                tab("flag $i: ${mapping[i]}")
636            }
637        }
638        nl("flag mapping end*/")
639    }
640
641    fun executePendingBindings() = kcode("") {
642        nl("@Override")
643        nl("public void executePendingBindings() {") {
644            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
645            tmpDirtyFlags.setLocalName("dirtyFlags");
646            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
647                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
648                tab("${mDirtyFlags.localValue(i)} = 0;")
649            }
650            //tab("""log("dirty flags", mDirtyFlags);""")
651            model.getPendingExpressions().filterNot {it.isVariable()}.forEach {
652                tab("${it.getResolvedType().toJavaCode()} ${it.localName} = ${it.getDefaultValue()};")
653            }
654
655            do {
656                val batch = model.filterShouldRead(model.getPendingExpressions()).toArrayList()
657                val mJustRead = arrayListOf<Expr>()
658                while (!batch.none()) {
659                    val readNow = batch.filter { it.shouldReadNow(mJustRead) }
660                    if (readNow.isEmpty()) {
661                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
662                    }
663                    Log.d { "new read now. batch size: ${batch.size()}, readNow size: ${readNow.size()}" }
664
665                    readNow.forEach {
666                        nl(readWithDependants(it, mJustRead, batch, tmpDirtyFlags))
667                    }
668                    batch.removeAll(mJustRead)
669                }
670                tab("// batch finished")
671            } while(model.markBitsRead())
672
673            //
674            layoutBinder.getBindingTargets().filter { it.isUsed() }
675                    .flatMap { it.getBindings() }
676                    .groupBy { it.getExpr() }
677                    .forEach {
678                        val flagSet = it.key.dirtyFlagSet
679                        tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
680                            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
681                        }.joinToString(" || ")
682                        }) {") {
683                            it.value.forEach { binding ->
684                                tab("// api target ${binding.getMinApi()}")
685                                val fieldName : String
686                                if (binding.getTarget().getViewClass().
687                                        equals(binding.getTarget().getInterfaceType())) {
688                                    fieldName = "this.${binding.getTarget().fieldName}"
689                                } else {
690                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
691                                }
692                                val bindingCode = binding.toJavaCode(fieldName, binding.getExpr().toCode().generate())
693                                if (binding.getMinApi() > 1) {
694                                    tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
695                                        tab("$bindingCode;")
696                                    }
697                                    tab("}")
698                                } else {
699                                    tab("$bindingCode;")
700                                }
701                            }
702                        }
703                        tab("}")
704                    }
705            //
706            includedBinders.filter{it.isUsed()}.forEach { binder ->
707                tab("${binder.fieldName}.executePendingBindings();")
708            }
709            layoutBinder.getBindingTargets().filter{
710                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
711            }.forEach {
712                tab("if (${it.fieldName}.getBinding() != null) {") {
713                    tab("${it.fieldName}.getBinding().executePendingBindings();")
714                }
715                tab("}")
716            }
717        }
718        nl("}")
719    }
720
721    fun readWithDependants(expr : Expr, mJustRead : MutableList<Expr>, batch : MutableList<Expr>, tmpDirtyFlags : FlagSet) : KCode = kcode("") {
722        mJustRead.add(expr)
723        Log.d { expr.getUniqueKey() }
724        val flagSet = expr.shouldReadFlagSet
725        tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
726            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
727        }.joinToString(" || ")
728        }) {") {
729            if (!expr.isVariable()) {
730                // it is not a variable read it.
731                tab("// read ${expr.getUniqueKey()}")
732                // create an if case for all dependencies that might be null
733                val nullables = expr.getDependencies().filter {
734                    it.isMandatory() && it.getOther().getResolvedType().isNullable()
735                }
736                        .map { it.getOther() }
737                if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
738                    tab ("if ( ${nullables.map { "${it.localName} != null" }.joinToString(" && ")}) {") {
739                        tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";")
740                        //tab("""log("${expr}" + ${expr.localName},0);""")
741                    }
742                    tab("}")
743                } else {
744                    tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";")
745                    //tab("""log("${expr}" + ${expr.localName},0);""")
746                }
747                if (expr.isObservable()) {
748                    tab("updateRegistration(${expr.getId()}, ${expr.localName});")
749                }
750            }
751
752            // if I am the condition for an expression, set its flag
753            val conditionals = expr.getDependants().filter { !it.isConditional()
754                    && it.getDependant() is TernaryExpr && (it.getDependant() as TernaryExpr).getPred() == expr }
755                    .map { it.getDependant() }
756            if (conditionals.isNotEmpty()) {
757                tab("// setting conditional flags")
758                tab("if (${expr.localName}) {") {
759                    conditionals.forEach {
760                        val set = it.getRequirementFlagSet(true)
761                        mDirtyFlags.mapOr(set) { suffix , index ->
762                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
763                        }
764                    }
765                }
766                tab("} else {") {
767                    conditionals.forEach {
768                        val set = it.getRequirementFlagSet(false)
769                        mDirtyFlags.mapOr(set) { suffix , index ->
770                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
771                        }
772                    }
773                } tab("}")
774            }
775
776            val chosen = expr.getDependants().filter {
777                batch.contains(it.getDependant()) && it.getDependant().shouldReadNow(mJustRead)
778            }
779            if (chosen.isNotEmpty()) {
780                chosen.forEach {
781                    nl(readWithDependants(it.getDependant(), mJustRead, batch, tmpDirtyFlags))
782                }
783            }
784        }
785        tab("}")
786    }
787
788    public fun writeBaseClass() : String =
789        kcode("package ${layoutBinder.getPackage()};") {
790            nl("import android.databinding.Bindable;")
791            nl("import android.databinding.DataBindingUtil;")
792            nl("import android.databinding.ViewDataBinding;")
793            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
794            layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
795                tab("public final ${it.interfaceType} ${it.fieldName};")
796            }
797            nl("")
798            tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") {
799                layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
800                    tab(", ${it.interfaceType} ${it.readableUniqueName}")
801                }
802            }
803            tab(") {") {
804                tab("super(root_, localFieldCount);")
805                layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
806                    tab("this.${it.fieldName} = ${it.readableUniqueName};")
807                }
808            }
809            tab("}")
810            nl("")
811            variables.forEach {
812                if (it.getUserDefinedType() != null) {
813                    //it.getExpandedUserDefinedType(ModelAnalyzer.getInstance());
814                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
815                    tab("public abstract void ${it.setterName}(${type} ${it.readableUniqueName});")
816                }
817            }
818            tab("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
819                tab("return DataBindingUtil.<${baseClassName}>inflate(root.getContext(), ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true);")
820            }
821            tab("}")
822            tab("public static ${baseClassName} inflate(android.content.Context context) {") {
823                tab("return DataBindingUtil.<${baseClassName}>inflate(context, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false);")
824            }
825            tab("}")
826            tab("public static ${baseClassName} bind(android.view.View view) {") {
827                tab("return (${baseClassName})DataBindingUtil.bindTo(view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
828            }
829            tab("}")
830            nl("}")
831        }.generate()
832}