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