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