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