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