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