LayoutBinderWriter.kt revision d8c8ec27ed2ec0b11fa37f476395ce27834471f0
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    FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount())
262}
263
264val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr ->
265    FlagSet(expr.getId())
266}
267
268val Expr.shouldReadFlagSet by Delegates.lazy { expr : Expr ->
269    FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount())
270}
271
272val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
273    arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
274            FlagSet(expr.getRequirementFlagIndex(true)))
275}
276
277fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
278
279fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
280    buckets.withIndex().forEach {
281        if (it.value != 0L) {
282            cb(getWordSuffix(it.index), buckets[it.index])
283        }
284    }
285}
286
287fun FlagSet.getBitSuffix(bitIndex : Int) : String {
288    val word = bitIndex / FlagSet.sBucketSize
289    return getWordSuffix(word)
290}
291
292fun FlagSet.getWordSuffix(wordIndex : Int) : String {
293    return if(wordIndex == 0) "" else "_${wordIndex}"
294}
295
296fun FlagSet.localValue(bucketIndex : Int) =
297        if (getLocalName() == null) binaryCode(bucketIndex)
298        else "${getLocalName()}${getWordSuffix(bucketIndex)}"
299
300fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
301
302
303fun longToBinary(l : Long) =
304        "0b${java.lang.Long.toBinaryString(l)}L"
305
306fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
307    val min = Math.min(buckets.size(), other.buckets.size())
308    val result = arrayListOf<T>()
309    for (i in 0..(min - 1)) {
310        // if these two can match by any chance, call the callback
311        if (intersect(other, i)) {
312            result.add(cb(getWordSuffix(i), i))
313        }
314    }
315    return result
316}
317
318class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
319    val model = layoutBinder.getModel()
320    val indices = HashMap<BindingTarget, kotlin.Int>()
321    val mDirtyFlags by Delegates.lazy {
322        val fs = FlagSet(BitSet(), model.getFlagBucketCount());
323        Arrays.fill(fs.buckets, -1)
324        fs.setDynamic(true)
325        model.localizeFlag(fs, "mDirtyFlags")
326        fs
327    }
328
329    val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
330    val className = layoutBinder.getImplementationName()
331
332    val identifiers by Delegates.lazy {
333        dynamics.filter { it is IdentifierExpr }
334    }
335
336    val baseClassName = "${layoutBinder.getClassName()}"
337
338    val includedBinders by Delegates.lazy {
339        layoutBinder.getBindingTargets().filter { it.isBinder() }
340    }
341
342    val variables by Delegates.lazy {
343        model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
344    }
345
346    val usedVariables by Delegates.lazy {
347        variables.filter {it.isUsed()}
348    }
349
350    public fun write() : String  {
351        layoutBinder.resolveWhichExpressionsAreUsed()
352        calculateIndices();
353        return kcode("package ${layoutBinder.getPackage()};") {
354            nl("import ${layoutBinder.getModulePackage()}.R;")
355            nl("import ${layoutBinder.getModulePackage()}.BR;")
356            nl("import android.view.View;")
357            val classDeclaration : String
358            if (layoutBinder.hasVariations()) {
359                classDeclaration = "${className} extends ${baseClassName}"
360            } else {
361                classDeclaration = "${className} extends android.databinding.ViewDataBinding"
362            }
363            nl("public class ${classDeclaration} {") {
364                tab(declareIncludeViews())
365                tab(declareViews())
366                tab(declareVariables())
367                tab(declareConstructor())
368                tab(declareInvalidateAll())
369                tab(declareLog())
370                tab(declareSetVariable())
371                tab(variableSettersAndGetters())
372                tab(onFieldChange())
373
374                tab(executePendingBindings())
375
376                tab(declareDirtyFlags())
377                if (!layoutBinder.hasVariations()) {
378                    tab(declareFactories())
379                }
380            }
381            nl("}")
382            tab(flagMapping())
383            tab("//end")
384        }.generate()
385    }
386    fun calculateIndices() : Unit {
387        val numTaggedViews = layoutBinder.getBindingTargets().
388                filter{it.isUsed() && it.getTag() != null}.count()
389        layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() != null }.forEach {
390            indices.put(it, Integer.parseInt(it.getTag()));
391        }
392        layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() == null && it.getId() != null }.withIndex().forEach {
393            indices.put(it.value, it.index + numTaggedViews);
394        }
395    }
396    fun declareIncludeViews() = kcode("") {
397        nl("private static final android.util.SparseIntArray sIncludes;")
398        nl("private static final android.util.SparseIntArray sViewsWithIds;")
399        nl("static {") {
400            val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
401            if (!hasBinders) {
402                tab("sIncludes = null;")
403            } else {
404                tab("sIncludes = new android.util.SparseIntArray();")
405                layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder()}.forEach {
406                    tab("sIncludes.put(${it.androidId}, ${indices.get(it)});")
407                }
408            }
409            val hasViewsWithIds = layoutBinder.getBindingTargets().firstOrNull{
410                it.isUsed() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
411            } != null
412            if (!hasViewsWithIds) {
413                tab("sViewsWithIds = null;")
414            } else {
415                tab("sViewsWithIds = new android.util.SparseIntArray();")
416                layoutBinder.getBindingTargets().filter{
417                    it.isUsed() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
418                }.forEach {
419                    tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
420                }
421            }
422        }
423        nl("}")
424    }
425    fun declareConstructor() = kcode("") {
426        val viewCount = layoutBinder.getBindingTargets().filter{it.isUsed()}.count()
427        if (layoutBinder.hasVariations()) {
428            nl("")
429            nl("public ${className}(View root) {") {
430                tab("this(root, mapChildViews(root, ${viewCount}, sIncludes, sViewsWithIds));")
431            }
432            nl("}")
433            nl("private ${className}(View root, View[] views) {") {
434                tab("super(root, ${model.getObservables().size()}") {
435                    layoutBinder.getBindingTargets().filter { it.getId() != null }.forEach {
436                        tab(", ${fieldConversion(it)}")
437                    }
438                    tab(");")
439                }
440            }
441        } else {
442            nl("${baseClassName}(View root) {") {
443                tab("super(root, ${model.getObservables().size()});")
444                tab("final View[] views = mapChildViews(root, ${viewCount}, sIncludes, sViewsWithIds);")
445            }
446        }
447        val taggedViews = layoutBinder.getBindingTargets().filter{it.isUsed()}
448        taggedViews.forEach {
449            if (!layoutBinder.hasVariations() || it.getId() == null) {
450                tab("this.${it.fieldName} = ${fieldConversion(it)};")
451            }
452            if (!it.isBinder()) {
453                if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
454                    tab("this.${it.fieldName}.setContainingBinding(this);")
455                }
456                if (it.supportsTag() && it.getTag() != null) {
457                    val originalTag = it.getOriginalTag();
458                    var tagValue = "null"
459                    if (originalTag != null) {
460                        tagValue = "\"${originalTag}\""
461                        if (originalTag.startsWith("@")) {
462                            var packageName = layoutBinder.getModulePackage()
463                            if (originalTag.startsWith("@android:")) {
464                                packageName = "android"
465                            }
466                            val slashIndex = originalTag.indexOf('/')
467                            val resourceId = originalTag.substring(slashIndex + 1)
468                            tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
469                        }
470                    }
471                    tab("this.${it.fieldName}.setTag(${tagValue});")
472                }
473            }
474        }
475        tab("invalidateAll();");
476        nl("}")
477    }
478
479    fun fieldConversion(target : BindingTarget) : String {
480        val index = indices.get(target)
481        if (!target.isUsed()) {
482            return "null"
483        } else {
484            val variableName: String
485            if (index == null) {
486                variableName = "root";
487            } else {
488                variableName = "views[${index}]"
489            }
490            return target.superConversion(variableName)
491        }
492    }
493
494    fun declareInvalidateAll() = kcode("") {
495        nl("@Override")
496        nl("public void invalidateAll() {") {
497            val bs = BitSet()
498            bs.set(0, model.getInvalidateableFieldLimit())
499            val fs = FlagSet(bs, mDirtyFlags.buckets.size())
500            for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
501                tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
502            }
503            includedBinders.filter{it.isUsed()}.forEach { binder ->
504                tab("${binder.fieldName}.invalidateAll();")
505            }
506        }
507        nl("}")
508    }
509
510    fun declareSetVariable() = kcode("") {
511        nl("public boolean setVariable(int variableId, Object variable) {") {
512            tab("switch(variableId) {") {
513                usedVariables.forEach {
514                    tab ("case ${it.getName().br()} :") {
515                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
516                        tab("return true;")
517                    }
518                }
519            }
520            tab("}")
521            tab("return false;")
522        }
523        nl("}")
524    }
525
526    fun declareLog() = kcode("") {
527        nl("private void log(String msg, long i) {") {
528            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
529        }
530        nl("}")
531    }
532
533    fun variableSettersAndGetters() = kcode("") {
534        variables.filterNot{it.isUsed()}.forEach {
535            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
536                tab("// not used, ignore")
537            }
538            nl("}")
539            nl("")
540            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
541                tab("return ${it.getDefaultValue()};")
542            }
543            nl("}")
544        }
545        usedVariables.forEach {
546            if (it.getUserDefinedType() != null) {
547                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
548                    if (it.isObservable()) {
549                        tab("updateRegistration(${it.getId()}, ${it.readableUniqueName});");
550                    }
551                    tab("this.${it.fieldName} = ${it.readableUniqueName};")
552                    // set dirty flags!
553                    val flagSet = it.invalidateFlagSet
554                    mDirtyFlags.mapOr(flagSet) { suffix, index ->
555                        tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
556                    }
557                    tab("super.requestRebind();")
558                }
559                nl("}")
560                nl("")
561                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
562                    tab("return ${it.fieldName};")
563                }
564                nl("}")
565            }
566        }
567    }
568
569    fun onFieldChange() = kcode("") {
570        nl("@Override")
571        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
572            tab("switch (localFieldId) {") {
573                model.getObservables().forEach {
574                    tab("case ${it.getId()} :") {
575                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
576                    }
577                }
578            }
579            tab("}")
580            tab("return false;")
581        }
582        nl("}")
583        nl("")
584
585        model.getObservables().forEach {
586            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}, int fieldId) {") {
587                tab("switch (fieldId) {", {
588                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
589                    accessedFields.filter { it.canBeInvalidated() }
590                            .groupBy { it.getName() }
591                            .forEach {
592                                tab("case ${it.key.br()}:") {
593                                    val field = it.value.first()
594                                    mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
595                                        tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
596                                    }
597                                    tab("return true;")
598                                }
599
600                            }
601                    tab("case ${"".br()}:") {
602                        val flagSet = it.invalidateFlagSet
603                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
604                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
605                        }
606                        tab("return true;")
607                    }
608
609                })
610                tab("}")
611                tab("return false;")
612            }
613            nl("}")
614            nl("")
615        }
616    }
617
618    fun declareViews() = kcode("// views") {
619        val oneLayout = !layoutBinder.hasVariations();
620        layoutBinder.getBindingTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
621            val access : String
622            if (oneLayout && it.getId() != null) {
623                access = "public"
624            } else {
625                access = "private"
626            }
627            nl("${access} final ${it.interfaceType} ${it.fieldName};")
628        }
629    }
630
631    fun declareVariables() = kcode("// variables") {
632        usedVariables.forEach {
633            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
634        }
635    }
636
637    fun declareDirtyFlags() = kcode("// dirty flag") {
638        model.ext.localizedFlags.forEach { flag ->
639            flag.notEmpty { suffix, value ->
640                nl("private")
641                app(" ", if(flag.isDynamic()) null else "static final");
642                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
643            }
644        }
645    }
646
647    fun flagMapping() = kcode("/* flag mapping") {
648        if (model.getFlagMapping() != null) {
649            val mapping = model.getFlagMapping()
650            for (i in mapping.indices) {
651                tab("flag $i: ${mapping[i]}")
652            }
653        }
654        nl("flag mapping end*/")
655    }
656
657    fun executePendingBindings() = kcode("") {
658        nl("@Override")
659        nl("public void executePendingBindings() {") {
660            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
661            tmpDirtyFlags.setLocalName("dirtyFlags");
662            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
663                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
664                tab("${mDirtyFlags.localValue(i)} = 0;")
665            }
666            model.getPendingExpressions().filterNot {it.isVariable()}.forEach {
667                tab("${it.getResolvedType().toJavaCode()} ${it.localName} = ${it.getDefaultValue()};")
668            }
669
670            do {
671                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
672                val mJustRead = arrayListOf<Expr>()
673                while (!batch.none()) {
674                    val readNow = batch.filter { it.shouldReadNow(mJustRead) }
675                    if (readNow.isEmpty()) {
676                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
677                    }
678                    Log.d { "new read now. batch size: ${batch.size()}, readNow size: ${readNow.size()}" }
679
680                    readNow.forEach {
681                        nl(readWithDependants(it, mJustRead, batch, tmpDirtyFlags))
682                    }
683                    batch.removeAll(mJustRead)
684                }
685                tab("// batch finished")
686            } while(model.markBitsRead())
687
688            //
689            layoutBinder.getBindingTargets().filter { it.isUsed() }
690                    .flatMap { it.getBindings() }
691                    .groupBy { it.getExpr() }
692                    .forEach {
693                        val flagSet = it.key.dirtyFlagSet
694                        tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
695                            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
696                        }.joinToString(" || ")
697                        }) {") {
698                            it.value.forEach { binding ->
699                                tab("// api target ${binding.getMinApi()}")
700                                val fieldName : String
701                                if (binding.getTarget().getViewClass().
702                                        equals(binding.getTarget().getInterfaceType())) {
703                                    fieldName = "this.${binding.getTarget().fieldName}"
704                                } else {
705                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
706                                }
707                                val bindingCode = binding.toJavaCode(fieldName, binding.getExpr().toCode().generate())
708                                if (binding.getMinApi() > 1) {
709                                    tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
710                                        tab("$bindingCode;")
711                                    }
712                                    tab("}")
713                                } else {
714                                    tab("$bindingCode;")
715                                }
716                            }
717                        }
718                        tab("}")
719                    }
720            includedBinders.filter{it.isUsed()}.forEach { binder ->
721                tab("${binder.fieldName}.executePendingBindings();")
722            }
723            layoutBinder.getBindingTargets().filter{
724                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
725            }.forEach {
726                tab("if (${it.fieldName}.getBinding() != null) {") {
727                    tab("${it.fieldName}.getBinding().executePendingBindings();")
728                }
729                tab("}")
730            }
731        }
732        nl("}")
733    }
734
735    fun readWithDependants(expr : Expr, mJustRead : MutableList<Expr>, batch : MutableList<Expr>,
736            tmpDirtyFlags : FlagSet, inheritedFlags : FlagSet? = null) : KCode = kcode("") {
737        mJustRead.add(expr)
738        Log.d { expr.getUniqueKey() }
739        val flagSet = expr.shouldReadFlagSet
740        val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
741        val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
742            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
743        }.joinToString(" || ")
744        })"
745
746        val readCode = kcode("") {
747            if (!expr.isVariable()) {
748                // it is not a variable read it.
749                tab("// read ${expr.getUniqueKey()}")
750                // create an if case for all dependencies that might be null
751                val nullables = expr.getDependencies().filter {
752                    it.isMandatory() && it.getOther().getResolvedType().isNullable()
753                }.map { it.getOther() }
754                if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
755                    tab ("if ( ${nullables.map { "${it.localName} != null" }.joinToString(" && ")}) {") {
756                        tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";")
757                    }
758                    tab("}")
759                } else {
760                    tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";")
761                }
762                if (expr.isObservable()) {
763                    tab("updateRegistration(${expr.getId()}, ${expr.localName});")
764                }
765            }
766
767            // if I am the condition for an expression, set its flag
768            val conditionals = expr.getDependants().filter { !it.isConditional()
769                    && it.getDependant() is TernaryExpr && (it.getDependant() as TernaryExpr).getPred() == expr }
770                    .map { it.getDependant() }
771            if (conditionals.isNotEmpty()) {
772                tab("// setting conditional flags")
773                tab("if (${expr.localName}) {") {
774                    conditionals.forEach {
775                        val set = it.getRequirementFlagSet(true)
776                        mDirtyFlags.mapOr(set) { suffix , index ->
777                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
778                        }
779                    }
780                }
781                tab("} else {") {
782                    conditionals.forEach {
783                        val set = it.getRequirementFlagSet(false)
784                        mDirtyFlags.mapOr(set) { suffix , index ->
785                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
786                        }
787                    }
788                } tab("}")
789            }
790
791            val chosen = expr.getDependants().filter {
792                val dependant = it.getDependant()
793                batch.contains(dependant) &&
794                        dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
795                        dependant.shouldReadNow(mJustRead)
796            }
797            if (chosen.isNotEmpty()) {
798                val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
799                chosen.forEach {
800                    nl(readWithDependants(it.getDependant(), mJustRead, batch, tmpDirtyFlags, nextInheritedFlags))
801                }
802            }
803        }
804        if (needsIfWrapper) {
805            tab(ifClause) {
806                app(" {")
807                nl(readCode)
808            }
809            tab("}")
810        } else {
811            nl(readCode)
812        }
813    }
814
815    fun declareFactories() = kcode("") {
816        nl("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
817            tab("return bind(android.view.LayoutInflater.from(root.getContext()).inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true));")
818        }
819        nl("}")
820        nl("public static ${baseClassName} inflate(android.content.Context context) {") {
821            tab("return bind(android.view.LayoutInflater.from(context).inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false));")
822        }
823        nl("}")
824        nl("public static ${baseClassName} bind(android.view.View view) {") {
825            tab("if (!\"${layoutBinder.getId()}\".equals(view.getTag())) {") {
826                tab("throw new RuntimeException(\"view tag isn't correct on view\");")
827            }
828            tab("}")
829            tab("return new ${baseClassName}(view);")
830        }
831        nl("}")
832    }
833
834    public fun writeBaseClass() : String =
835        kcode("package ${layoutBinder.getPackage()};") {
836            nl("import android.databinding.Bindable;")
837            nl("import android.databinding.DataBindingUtil;")
838            nl("import android.databinding.ViewDataBinding;")
839            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
840            layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
841                tab("public final ${it.interfaceType} ${it.fieldName};")
842            }
843            nl("")
844            tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") {
845                layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
846                    tab(", ${it.interfaceType} ${it.readableUniqueName}")
847                }
848            }
849            tab(") {") {
850                tab("super(root_, localFieldCount);")
851                layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
852                    tab("this.${it.fieldName} = ${it.readableUniqueName};")
853                }
854            }
855            tab("}")
856            nl("")
857            variables.forEach {
858                if (it.getUserDefinedType() != null) {
859                    //it.getExpandedUserDefinedType(ModelAnalyzer.getInstance());
860                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
861                    tab("public abstract void ${it.setterName}(${type} ${it.readableUniqueName});")
862                }
863            }
864            tab("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
865                tab("return DataBindingUtil.<${baseClassName}>inflate(root.getContext(), ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true);")
866            }
867            tab("}")
868            tab("public static ${baseClassName} inflate(android.content.Context context) {") {
869                tab("return DataBindingUtil.<${baseClassName}>inflate(context, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false);")
870            }
871            tab("}")
872            tab("public static ${baseClassName} bind(android.view.View view) {") {
873                tab("return (${baseClassName})DataBindingUtil.bindTo(view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
874            }
875            tab("}")
876            nl("}")
877        }.generate()
878}
879