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