LayoutBinderWriter.kt revision 05196a205cdf3e074a452ba5a191c4e2a8e0b02f
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            tab("synchronized(this) {") {
555                for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
556                    tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
557                }
558            } tab("}")
559            includedBinders.filter{it.isUsed()}.forEach { binder ->
560                tab("${binder.fieldName}.invalidateAll();")
561            }
562        }
563        nl("}")
564    }
565
566    fun declareSetVariable() = kcode("") {
567        nl("public boolean setVariable(int variableId, Object variable) {") {
568            tab("switch(variableId) {") {
569                usedVariables.forEach {
570                    tab ("case ${it.getName().br()} :") {
571                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
572                        tab("return true;")
573                    }
574                }
575            }
576            tab("}")
577            tab("return false;")
578        }
579        nl("}")
580    }
581
582    fun declareLog() = kcode("") {
583        nl("private void log(String msg, long i) {") {
584            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
585        }
586        nl("}")
587    }
588
589    fun variableSettersAndGetters() = kcode("") {
590        variables.filterNot{it.isUsed()}.forEach {
591            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
592                tab("// not used, ignore")
593            }
594            nl("}")
595            nl("")
596            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
597                tab("return ${it.getDefaultValue()};")
598            }
599            nl("}")
600        }
601        usedVariables.forEach {
602            if (it.getUserDefinedType() != null) {
603                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
604                    if (it.isObservable()) {
605                        tab("updateRegistration(${it.getId()}, ${it.readableName});");
606                    }
607                    tab("this.${it.fieldName} = ${it.readableName};")
608                    // set dirty flags!
609                    val flagSet = it.invalidateFlagSet
610                    tab("synchronized(this) {") {
611                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
612                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
613                        }
614                    } tab ("}")
615                    tab("super.requestRebind();")
616                }
617                nl("}")
618                nl("")
619                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
620                    tab("return ${it.fieldName};")
621                }
622                nl("}")
623            }
624        }
625    }
626
627    fun onFieldChange() = kcode("") {
628        nl("@Override")
629        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
630            tab("switch (localFieldId) {") {
631                model.getObservables().forEach {
632                    tab("case ${it.getId()} :") {
633                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
634                    }
635                }
636            }
637            tab("}")
638            tab("return false;")
639        }
640        nl("}")
641        nl("")
642
643        model.getObservables().forEach {
644            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
645                tab("switch (fieldId) {", {
646                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
647                    accessedFields.filter { it.canBeInvalidated() }
648                            .groupBy { it.getName() }
649                            .forEach {
650                                tab("case ${it.key.br()}:") {
651                                    val field = it.value.first()
652                                    tab("synchronized(this) {") {
653                                        mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
654                                            tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
655                                        }
656                                    } tab("}")
657                                    tab("return true;")
658                                }
659
660                            }
661                    tab("case ${"".br()}:") {
662                        val flagSet = it.invalidateFlagSet
663                        tab("synchronized(this) {") {
664                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
665                                tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
666                            }
667                        } tab("}")
668                        tab("return true;")
669                    }
670
671                })
672                tab("}")
673                tab("return false;")
674            }
675            nl("}")
676            nl("")
677        }
678    }
679
680    fun declareViews() = kcode("// views") {
681        val oneLayout = !layoutBinder.hasVariations();
682        layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
683            val access : String
684            if (oneLayout && it.getId() != null) {
685                access = "public"
686            } else {
687                access = "private"
688            }
689            nl("${access} final ${it.interfaceType} ${it.fieldName};")
690        }
691    }
692
693    fun declareVariables() = kcode("// variables") {
694        usedVariables.forEach {
695            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
696        }
697    }
698
699    fun declareDirtyFlags() = kcode("// dirty flag") {
700        model.ext.localizedFlags.forEach { flag ->
701            flag.notEmpty { suffix, value ->
702                nl("private")
703                app(" ", if(flag.isDynamic()) null else "static final");
704                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
705            }
706        }
707    }
708
709    fun flagMapping() = kcode("/* flag mapping") {
710        if (model.getFlagMapping() != null) {
711            val mapping = model.getFlagMapping()
712            for (i in mapping.indices) {
713                tab("flag $i: ${mapping[i]}")
714            }
715        }
716        nl("flag mapping end*/")
717    }
718
719    fun executePendingBindings() = kcode("") {
720        nl("@Override")
721        nl("protected void executeBindings() {") {
722            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
723            tmpDirtyFlags.setLocalName("dirtyFlags");
724            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
725                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
726            }
727            tab("synchronized(this) {") {
728                for (i in (0..mDirtyFlags.buckets.size() - 1)) {
729                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
730                    tab("${mDirtyFlags.localValue(i)} = 0;")
731                }
732            } tab("}")
733            model.getPendingExpressions().filterNot {it.isVariable()}.forEach {
734                tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${it.getDefaultValue()};")
735            }
736            Log.d {"writing executePendingBindings for $className"}
737            do {
738                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
739                Log.d {"batch: $batch"}
740                val mJustRead = arrayListOf<Expr>()
741                while (!batch.none()) {
742                    val readNow = batch.filter { it.shouldReadNow(mJustRead) }
743                    if (readNow.isEmpty()) {
744                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
745                    }
746                    Log.d { "new read now. batch size: ${batch.size()}, readNow size: ${readNow.size()}" }
747
748                    readNow.forEach {
749                        nl(readWithDependants(it, mJustRead, batch, tmpDirtyFlags))
750                    }
751                    batch.removeAll(mJustRead)
752                }
753                tab("// batch finished")
754            } while(model.markBitsRead())
755
756            //
757            layoutBinder.getSortedTargets().filter { it.isUsed() }
758                    .flatMap { it.getBindings() }
759                    .groupBy { it.getExpr() }
760                    .forEach {
761                        val flagSet = it.key.dirtyFlagSet
762                        tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
763                            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
764                        }.joinToString(" || ")
765                        }) {") {
766                            it.value.forEach { binding ->
767                                tab("// api target ${binding.getMinApi()}")
768                                val fieldName : String
769                                if (binding.getTarget().getViewClass().
770                                        equals(binding.getTarget().getInterfaceType())) {
771                                    fieldName = "this.${binding.getTarget().fieldName}"
772                                } else {
773                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
774                                }
775                                val bindingCode = binding.toJavaCode(fieldName, binding.getExpr().toCode().generate())
776                                if (binding.getMinApi() > 1) {
777                                    tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
778                                        tab("$bindingCode;")
779                                    }
780                                    tab("}")
781                                } else {
782                                    tab("$bindingCode;")
783                                }
784                            }
785                        }
786                        tab("}")
787                    }
788            includedBinders.filter{it.isUsed()}.forEach { binder ->
789                tab("${binder.fieldName}.executePendingBindings();")
790            }
791            layoutBinder.getSortedTargets().filter{
792                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
793            }.forEach {
794                tab("if (${it.fieldName}.getBinding() != null) {") {
795                    tab("${it.fieldName}.getBinding().executePendingBindings();")
796                }
797                tab("}")
798            }
799        }
800        nl("}")
801    }
802
803    fun readWithDependants(expr : Expr, mJustRead : MutableList<Expr>, batch : MutableList<Expr>,
804            tmpDirtyFlags : FlagSet, inheritedFlags : FlagSet? = null) : KCode = kcode("") {
805        mJustRead.add(expr)
806        Log.d { "$className / readWithDependants: ${expr.getUniqueKey()}" }
807        val flagSet = expr.shouldReadFlagSet
808        val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
809        Log.d { "flag set:$flagSet . inherited flags: $inheritedFlags. need another if: $needsIfWrapper"}
810        val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
811            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
812        }.joinToString(" || ")
813        })"
814
815        val readCode = kcode("") {
816            if (!expr.isVariable()) {
817                // it is not a variable read it.
818                tab("// read ${expr.getUniqueKey()}")
819                // create an if case for all dependencies that might be null
820                val nullables = expr.getDependencies().filter {
821                    it.isMandatory() && it.getOther().getResolvedType().isNullable()
822                }.map { it.getOther() }
823                if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
824                    tab ("if ( ${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}) {") {
825                        tab("${expr.executePendingLocalName}").app(" = ", expr.toCode(true)).app(";")
826                    }
827                    tab("}")
828                } else {
829                    tab("${expr.executePendingLocalName}").app(" = ", expr.toCode(true)).app(";")
830                }
831                if (expr.isObservable()) {
832                    tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
833                }
834            }
835
836            // if I am the condition for an expression, set its flag
837            val conditionals = expr.getDependants().filter { !it.isConditional()
838                    && it.getDependant() is TernaryExpr && (it.getDependant() as TernaryExpr).getPred() == expr }
839                    .map { it.getDependant() }
840            if (conditionals.isNotEmpty()) {
841                tab("// setting conditional flags")
842                tab("if (${expr.executePendingLocalName}) {") {
843                    conditionals.forEach {
844                        val set = it.getRequirementFlagSet(true)
845                        mDirtyFlags.mapOr(set) { suffix , index ->
846                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
847                        }
848                    }
849                }
850                tab("} else {") {
851                    conditionals.forEach {
852                        val set = it.getRequirementFlagSet(false)
853                        mDirtyFlags.mapOr(set) { suffix , index ->
854                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
855                        }
856                    }
857                } tab("}")
858            }
859
860            val chosen = expr.getDependants().filter {
861                val dependant = it.getDependant()
862                batch.contains(dependant) &&
863                        dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
864                        dependant.shouldReadNow(mJustRead)
865            }
866            if (chosen.isNotEmpty()) {
867                val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
868                chosen.forEach {
869                    nl(readWithDependants(it.getDependant(), mJustRead, batch, tmpDirtyFlags, nextInheritedFlags))
870                }
871            }
872        }
873        if (needsIfWrapper) {
874            tab(ifClause) {
875                app(" {")
876                nl(readCode)
877            }
878            tab("}")
879        } else {
880            nl(readCode)
881        }
882    }
883
884    fun declareFactories() = kcode("") {
885        if (!layoutBinder.isMerge()) {
886            nl("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
887                tab("return bind(android.view.LayoutInflater.from(root.getContext()).inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true));")
888            }
889            nl("}")
890            nl("public static ${baseClassName} inflate(android.content.Context context) {") {
891                tab("return bind(android.view.LayoutInflater.from(context).inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false));")
892            }
893            nl("}")
894            nl("public static ${baseClassName} bind(android.view.View view) {") {
895                tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
896                    tab("throw new RuntimeException(\"view tag isn't correct on view\");")
897                }
898                tab("}")
899                tab("return new ${baseClassName}(view);")
900            }
901            nl("}")
902        }
903    }
904
905    public fun writeBaseClass() : String =
906        kcode("package ${layoutBinder.getPackage()};") {
907            nl("import android.databinding.Bindable;")
908            nl("import android.databinding.DataBindingUtil;")
909            nl("import android.databinding.ViewDataBinding;")
910            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
911            layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
912                tab("public final ${it.interfaceType} ${it.fieldName};")
913            }
914            nl("")
915            tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") {
916                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
917                    tab(", ${it.interfaceType} ${it.constructorParamName}")
918                }
919            }
920            tab(") {") {
921                tab("super(root_, localFieldCount);")
922                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
923                    tab("this.${it.fieldName} = ${it.constructorParamName};")
924                }
925            }
926            tab("}")
927            nl("")
928            variables.forEach {
929                if (it.getUserDefinedType() != null) {
930                    //it.getExpandedUserDefinedType(ModelAnalyzer.getInstance());
931                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
932                    tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
933                }
934            }
935            tab("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
936                tab("return DataBindingUtil.<${baseClassName}>inflate(root.getContext(), ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true);")
937            }
938            tab("}")
939            tab("public static ${baseClassName} inflate(android.content.Context context) {") {
940                tab("return DataBindingUtil.<${baseClassName}>inflate(context, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false);")
941            }
942            tab("}")
943            tab("public static ${baseClassName} bind(android.view.View view) {") {
944                tab("return (${baseClassName})DataBindingUtil.bindTo(view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
945            }
946            tab("}")
947            nl("}")
948        }.generate()
949}
950