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