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