LayoutBinderWriter.kt revision f80b08430f11515ff944f89006799f21545602b9
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(declareLog())
293                tab(declareSetVariable())
294                tab(variableSettersAndGetters())
295                tab(onFieldChange())
296
297                tab(executePendingBindings())
298
299                tab(declareDirtyFlags())
300                if (!layoutBinder.hasVariations()) {
301                    tab(declareFactories())
302                }
303            }
304            nl("}")
305            tab(flagMapping())
306            tab("//end")
307        }.generate()
308    }
309    fun calculateIndices() : Unit {
310        val taggedViews = layoutBinder.getBindingTargets().filter{
311            it.isUsed() && it.getTag() != null && !it.isBinder()
312        }
313        taggedViews.forEach {
314            indices.put(it, indexFromTag(it.getTag()))
315        }
316        val indexStart = maxIndex() + 1
317        layoutBinder.getBindingTargets().filter{
318            it.isUsed() && !taggedViews.contains(it)
319        }.withIndex().forEach {
320            indices.put(it.value, it.index + indexStart)
321        }
322    }
323    fun declareIncludeViews() = kcode("") {
324        nl("private static final android.databinding.ViewDataBinding.IncludedLayoutIndex[][] sIncludes;")
325        nl("private static final android.util.SparseIntArray sViewsWithIds;")
326        nl("static {") {
327            val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
328            if (!hasBinders) {
329                tab("sIncludes = null;")
330            } else {
331                val numBindings = layoutBinder.getBindingTargets().filter{ it.isUsed() }.count()
332                tab("sIncludes = new android.databinding.ViewDataBinding.IncludedLayoutIndex[${numBindings}][];")
333                val includeMap = HashMap<BindingTarget, ArrayList<BindingTarget>>()
334                layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder() }.forEach {
335                    val includeTag = it.getTag();
336                    val parent = layoutBinder.getBindingTargets().firstOrNull {
337                        it.isUsed() && !it.isBinder() && includeTag.equals(it.getTag())
338                    }
339                    if (parent == null) {
340                        throw IllegalStateException("Could not find parent of include file")
341                    }
342                    var list = includeMap.get(parent)
343                    if (list == null) {
344                        list = ArrayList<BindingTarget>()
345                        includeMap.put(parent, list)
346                    }
347                    list.add(it)
348                }
349
350                includeMap.keySet().forEach {
351                    val index = indices.get(it)
352                    tab("sIncludes[${index}] = new android.databinding.ViewDataBinding.IncludedLayoutIndex[] {") {
353                        includeMap.get(it).forEach {
354                            val bindingIndex = indices.get(it)
355                            val layoutName = it.getIncludedLayout()
356                            tab("new android.databinding.ViewDataBinding.IncludedLayoutIndex(\"${layoutName}\", ${bindingIndex}, R.layout.${layoutName}),")
357                        }
358                    }
359                    tab("};")
360                }
361            }
362            val viewsWithIds = layoutBinder.getBindingTargets().filter {
363                it.isUsed() && !it.isBinder() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
364            }
365            if (viewsWithIds.isEmpty()) {
366                tab("sViewsWithIds = null;")
367            } else {
368                tab("sViewsWithIds = new android.util.SparseIntArray();")
369                viewsWithIds.forEach {
370                    tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
371                }
372            }
373        }
374        nl("}")
375    }
376
377    fun maxIndex() : kotlin.Int {
378        val maxIndex = indices.values().max()
379        if (maxIndex == null) {
380            return -1
381        } else {
382            return maxIndex
383        }
384    }
385
386    fun declareConstructor(minSdk : kotlin.Int) = kcode("") {
387        val bindingCount = maxIndex() + 1
388        val parameterType : String
389        val superParam : String
390        if (layoutBinder.isMerge()) {
391            parameterType = "View[]"
392            superParam = "root[0]"
393        } else {
394            parameterType = "View"
395            superParam = "root"
396        }
397        val rootTagsSupported = minSdk >= 14
398        if (layoutBinder.hasVariations()) {
399            nl("")
400            nl("public ${className}(${parameterType} root) {") {
401                tab("this(${superParam}, mapBindings(root, ${bindingCount}, sIncludes, sViewsWithIds));")
402            }
403            nl("}")
404            nl("private ${className}(${parameterType} root, Object[] bindings) {") {
405                tab("super(${superParam}, ${model.getObservables().size()}") {
406                    layoutBinder.getSortedTargets().filter { it.getId() != null }.forEach {
407                        tab(", ${fieldConversion(it)}")
408                    }
409                    tab(");")
410                }
411            }
412        } else {
413            nl("public ${baseClassName}(${parameterType} root) {") {
414                tab("super(${superParam}, ${model.getObservables().size()});")
415                tab("final Object[] bindings = mapBindings(root, ${bindingCount}, sIncludes, sViewsWithIds);")
416            }
417        }
418        tab("setRootTag(root);")
419        val taggedViews = layoutBinder.getSortedTargets().filter{it.isUsed()}
420        taggedViews.forEach {
421            if (!layoutBinder.hasVariations() || it.getId() == null) {
422                tab("this.${it.fieldName} = ${fieldConversion(it)};")
423            }
424            if (!it.isBinder()) {
425                if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
426                    tab("this.${it.fieldName}.setContainingBinding(this);")
427                }
428                if (it.supportsTag() && it.getTag() != null &&
429                        (rootTagsSupported || it.getTag().startsWith("binding_"))) {
430                    val originalTag = it.getOriginalTag();
431                    var tagValue = "null"
432                    if (originalTag != null) {
433                        tagValue = "\"${originalTag}\""
434                        if (originalTag.startsWith("@")) {
435                            var packageName = layoutBinder.getModulePackage()
436                            if (originalTag.startsWith("@android:")) {
437                                packageName = "android"
438                            }
439                            val slashIndex = originalTag.indexOf('/')
440                            val resourceId = originalTag.substring(slashIndex + 1)
441                            tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
442                        }
443                    }
444                    tab("this.${it.fieldName}.setTag(${tagValue});")
445                }
446            }
447        }
448        tab("invalidateAll();");
449        nl("}")
450    }
451
452    fun fieldConversion(target : BindingTarget) : String {
453        if (!target.isUsed()) {
454            return "null"
455        } else {
456            val index = indices.get(target)
457            if (index == null) {
458                throw IllegalStateException("Unknown binding target")
459            }
460            val variableName = "bindings[${index}]"
461            return target.superConversion(variableName)
462        }
463    }
464
465    fun declareInvalidateAll() = kcode("") {
466        nl("@Override")
467        nl("public void invalidateAll() {") {
468            val fs = FlagSet(layoutBinder.getModel().getInvalidateAnyBitSet(),
469                    layoutBinder.getModel().getFlagBucketCount());
470            tab("synchronized(this) {") {
471                for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
472                    tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
473                }
474            } tab("}")
475            includedBinders.filter{it.isUsed()}.forEach { binder ->
476                tab("${binder.fieldName}.invalidateAll();")
477            }
478        }
479        nl("}")
480    }
481
482    fun declareSetVariable() = kcode("") {
483        nl("public boolean setVariable(int variableId, Object variable) {") {
484            tab("switch(variableId) {") {
485                usedVariables.forEach {
486                    tab ("case ${it.getName().br()} :") {
487                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
488                        tab("return true;")
489                    }
490                }
491            }
492            tab("}")
493            tab("return false;")
494        }
495        nl("}")
496    }
497
498    fun declareLog() = kcode("") {
499        nl("private void log(String msg, long i) {") {
500            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
501        }
502        nl("}")
503    }
504
505    fun variableSettersAndGetters() = kcode("") {
506        variables.filterNot{it.isUsed()}.forEach {
507            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
508                tab("// not used, ignore")
509            }
510            nl("}")
511            nl("")
512            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
513                tab("return ${it.getDefaultValue()};")
514            }
515            nl("}")
516        }
517        usedVariables.forEach {
518            if (it.getUserDefinedType() != null) {
519                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
520                    if (it.isObservable()) {
521                        tab("updateRegistration(${it.getId()}, ${it.readableName});");
522                    }
523                    tab("this.${it.fieldName} = ${it.readableName};")
524                    // set dirty flags!
525                    val flagSet = it.invalidateFlagSet
526                    tab("synchronized(this) {") {
527                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
528                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
529                        }
530                    } tab ("}")
531                    tab("super.requestRebind();")
532                }
533                nl("}")
534                nl("")
535                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
536                    tab("return ${it.fieldName};")
537                }
538                nl("}")
539            }
540        }
541    }
542
543    fun onFieldChange() = kcode("") {
544        nl("@Override")
545        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
546            tab("switch (localFieldId) {") {
547                model.getObservables().forEach {
548                    tab("case ${it.getId()} :") {
549                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
550                    }
551                }
552            }
553            tab("}")
554            tab("return false;")
555        }
556        nl("}")
557        nl("")
558
559        model.getObservables().forEach {
560            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
561                tab("switch (fieldId) {", {
562                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
563                    accessedFields.filter { it.canBeInvalidated() }
564                            .groupBy { it.getName() }
565                            .forEach {
566                                tab("case ${it.key.br()}:") {
567                                    val field = it.value.first()
568                                    tab("synchronized(this) {") {
569                                        mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
570                                            tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
571                                        }
572                                    } tab("}")
573                                    tab("return true;")
574                                }
575
576                            }
577                    tab("case ${"".br()}:") {
578                        val flagSet = it.invalidateFlagSet
579                        tab("synchronized(this) {") {
580                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
581                                tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
582                            }
583                        } tab("}")
584                        tab("return true;")
585                    }
586
587                })
588                tab("}")
589                tab("return false;")
590            }
591            nl("}")
592            nl("")
593        }
594    }
595
596    fun declareViews() = kcode("// views") {
597        val oneLayout = !layoutBinder.hasVariations();
598        layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
599            val access : String
600            if (oneLayout && it.getId() != null) {
601                access = "public"
602            } else {
603                access = "private"
604            }
605            nl("${access} final ${it.interfaceType} ${it.fieldName};")
606        }
607    }
608
609    fun declareVariables() = kcode("// variables") {
610        usedVariables.forEach {
611            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
612        }
613    }
614
615    fun declareDirtyFlags() = kcode("// dirty flag") {
616        model.ext.localizedFlags.forEach { flag ->
617            flag.notEmpty { suffix, value ->
618                nl("private")
619                app(" ", if(flag.isDynamic()) null else "static final");
620                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
621            }
622        }
623    }
624
625    fun flagMapping() = kcode("/* flag mapping") {
626        if (model.getFlagMapping() != null) {
627            val mapping = model.getFlagMapping()
628            for (i in mapping.indices) {
629                tab("flag $i: ${mapping[i]}")
630            }
631        }
632        nl("flag mapping end*/")
633    }
634
635    fun executePendingBindings() = kcode("") {
636        nl("@Override")
637        nl("protected void executeBindings() {") {
638            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
639            tmpDirtyFlags.setLocalName("dirtyFlags");
640            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
641                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
642            }
643            tab("synchronized(this) {") {
644                for (i in (0..mDirtyFlags.buckets.size() - 1)) {
645                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
646                    tab("${mDirtyFlags.localValue(i)} = 0;")
647                }
648            } tab("}")
649            model.getPendingExpressions().filterNot {!it.canBeEvaluatedToAVariable() || it.isVariable()}.forEach {
650                tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${it.getDefaultValue()};")
651            }
652            Log.d {"writing executePendingBindings for $className"}
653            do {
654                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
655                Log.d {"batch: $batch"}
656                val mJustRead = arrayListOf<Expr>()
657                while (!batch.none()) {
658                    val readNow = batch.filter { it.shouldReadNow(mJustRead) }
659                    if (readNow.isEmpty()) {
660                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
661                    }
662                    Log.d { "new read now. batch size: ${batch.size()}, readNow size: ${readNow.size()}" }
663
664                    readNow.forEach {
665                        nl(readWithDependants(it, mJustRead, batch, tmpDirtyFlags))
666                    }
667                    batch.removeAll(mJustRead)
668                }
669                tab("// batch finished")
670            } while(model.markBitsRead())
671
672            //
673            layoutBinder.getSortedTargets().filter { it.isUsed() }
674                    .flatMap { it.getBindings() }
675                    .groupBy { it.getExpr() }
676                    .forEach {
677                        val flagSet = it.key.dirtyFlagSet
678                        tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
679                            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
680                        }.joinToString(" || ")
681                        }) {") {
682                            it.value.forEach { binding ->
683                                tab("// api target ${binding.getMinApi()}")
684                                val fieldName : String
685                                if (binding.getTarget().getViewClass().
686                                        equals(binding.getTarget().getInterfaceType())) {
687                                    fieldName = "this.${binding.getTarget().fieldName}"
688                                } else {
689                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
690                                }
691                                val bindingCode = binding.toJavaCode(fieldName)
692                                if (binding.getMinApi() > 1) {
693                                    tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
694                                        tab("$bindingCode;")
695                                    }
696                                    tab("}")
697                                } else {
698                                    tab("$bindingCode;")
699                                }
700                            }
701                        }
702                        tab("}")
703                    }
704            includedBinders.filter{it.isUsed()}.forEach { binder ->
705                tab("${binder.fieldName}.executePendingBindings();")
706            }
707            layoutBinder.getSortedTargets().filter{
708                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
709            }.forEach {
710                tab("if (${it.fieldName}.getBinding() != null) {") {
711                    tab("${it.fieldName}.getBinding().executePendingBindings();")
712                }
713                tab("}")
714            }
715        }
716        nl("}")
717    }
718
719    fun readWithDependants(expr : Expr, mJustRead : MutableList<Expr>, batch : MutableList<Expr>,
720            tmpDirtyFlags : FlagSet, inheritedFlags : FlagSet? = null) : KCode = kcode("") {
721        mJustRead.add(expr)
722        Log.d { "$className / readWithDependants: ${expr.getUniqueKey()}" }
723        val flagSet = expr.shouldReadFlagSet
724        val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
725        Log.d { "flag set:$flagSet . inherited flags: $inheritedFlags. need another if: $needsIfWrapper"}
726        val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
727            "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
728        }.joinToString(" || ")
729        })"
730
731        val readCode = kcode("") {
732            if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
733                // it is not a variable read it.
734                tab("// read ${expr.getUniqueKey()}")
735                // create an if case for all dependencies that might be null
736                val nullables = expr.getDependencies().filter {
737                    it.isMandatory() && it.getOther().getResolvedType().isNullable()
738                }.map { it.getOther() }
739                if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
740                    tab ("if ( ${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}) {") {
741                        tab("${expr.executePendingLocalName}").app(" = ", expr.toCode(true)).app(";")
742                    }
743                    tab("}")
744                } else {
745                    tab("${expr.executePendingLocalName}").app(" = ", expr.toCode(true)).app(";")
746                }
747                if (expr.isObservable()) {
748                    tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
749                }
750            }
751
752            // if I am the condition for an expression, set its flag
753            val conditionals = expr.getDependants().filter { !it.isConditional()
754                    && it.getDependant() is TernaryExpr && (it.getDependant() as TernaryExpr).getPred() == expr }
755                    .map { it.getDependant() }
756            if (conditionals.isNotEmpty()) {
757                tab("// setting conditional flags")
758                tab("if (${expr.executePendingLocalName}) {") {
759                    conditionals.forEach {
760                        val set = it.getRequirementFlagSet(true)
761                        mDirtyFlags.mapOr(set) { suffix , index ->
762                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
763                        }
764                    }
765                }
766                tab("} else {") {
767                    conditionals.forEach {
768                        val set = it.getRequirementFlagSet(false)
769                        mDirtyFlags.mapOr(set) { suffix , index ->
770                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
771                        }
772                    }
773                } tab("}")
774            }
775
776            val chosen = expr.getDependants().filter {
777                val dependant = it.getDependant()
778                batch.contains(dependant) &&
779                        dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
780                        dependant.shouldReadNow(mJustRead)
781            }
782            if (chosen.isNotEmpty()) {
783                val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
784                chosen.forEach {
785                    nl(readWithDependants(it.getDependant(), mJustRead, batch, tmpDirtyFlags, nextInheritedFlags))
786                }
787            }
788        }
789        if (needsIfWrapper) {
790            tab(ifClause) {
791                app(" {")
792                nl(readCode)
793            }
794            tab("}")
795        } else {
796            nl(readCode)
797        }
798    }
799
800    fun declareFactories() = kcode("") {
801        if (!layoutBinder.isMerge()) {
802            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
803                tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot));")
804            }
805            nl("}")
806            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
807                tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false));")
808            }
809            nl("}")
810            nl("public static ${baseClassName} bind(android.view.View view) {") {
811                tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
812                    tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
813                }
814                tab("}")
815                tab("return new ${baseClassName}(view);")
816            }
817            nl("}")
818        }
819    }
820
821    public fun writeBaseClass() : String =
822        kcode("package ${layoutBinder.getPackage()};") {
823            nl("import android.databinding.Bindable;")
824            nl("import android.databinding.DataBindingUtil;")
825            nl("import android.databinding.ViewDataBinding;")
826            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
827            layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
828                tab("public final ${it.interfaceType} ${it.fieldName};")
829            }
830            nl("")
831            tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") {
832                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
833                    tab(", ${it.interfaceType} ${it.constructorParamName}")
834                }
835            }
836            tab(") {") {
837                tab("super(root_, localFieldCount);")
838                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
839                    tab("this.${it.fieldName} = ${it.constructorParamName};")
840                }
841            }
842            tab("}")
843            nl("")
844            variables.forEach {
845                if (it.getUserDefinedType() != null) {
846                    //it.getExpandedUserDefinedType(ModelAnalyzer.getInstance());
847                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
848                    tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
849                }
850            }
851            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
852                tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true);")
853            }
854            tab("}")
855            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
856                tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false);")
857            }
858            tab("}")
859            tab("public static ${baseClassName} bind(android.view.View view) {") {
860                tab("return (${baseClassName})bind(view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
861            }
862            tab("}")
863            nl("}")
864        }.generate()
865}
866