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