LayoutBinderWriter.kt revision 92a428505b9102bc0560d2d5be1768da097909c2
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.BindingTarget
17import android.databinding.tool.LayoutBinder
18import android.databinding.tool.expr.Expr
19import android.databinding.tool.expr.ExprModel
20import android.databinding.tool.expr.FieldAccessExpr
21import android.databinding.tool.expr.IdentifierExpr
22import android.databinding.tool.expr.ListenerExpr
23import android.databinding.tool.expr.TernaryExpr
24import android.databinding.tool.ext.androidId
25import android.databinding.tool.ext.br
26import android.databinding.tool.ext.joinToCamelCaseAsVar
27import android.databinding.tool.ext.lazy
28import android.databinding.tool.ext.versionedLazy
29import android.databinding.tool.processing.ErrorMessages
30import android.databinding.tool.reflection.ModelAnalyzer
31import android.databinding.tool.util.L
32import java.util.ArrayList
33import java.util.Arrays
34import java.util.BitSet
35import java.util.HashMap
36import kotlin.properties.Delegates
37
38fun String.stripNonJava() = this.split("[^a-zA-Z0-9]".toRegex()).map{ it.trim() }.joinToCamelCaseAsVar()
39
40enum class Scope {
41    FIELD,
42    METHOD,
43    FLAG,
44    EXECUTE_PENDING_METHOD,
45    CONSTRUCTOR_PARAM
46}
47
48class ExprModelExt {
49    val usedFieldNames = hashMapOf<Scope, MutableSet<String>>();
50    init {
51        Scope.values().forEach { usedFieldNames[it] = hashSetOf<String>() }
52    }
53    val localizedFlags = arrayListOf<FlagSet>()
54
55    fun localizeFlag(set : FlagSet, name:String) : FlagSet {
56        localizedFlags.add(set)
57        val result = getUniqueName(name, Scope.FLAG, false)
58        set.setLocalName(result)
59        return set
60    }
61
62    fun getUniqueName(base : String, scope : Scope, isPublic : kotlin.Boolean) : String {
63        var candidateBase = base
64        if (!isPublic && candidateBase.length() > 20) {
65            candidateBase = candidateBase.substring(0, 20);
66        }
67        var candidate = candidateBase
68        var i = 0
69        while (usedFieldNames[scope].contains(candidate)) {
70            i ++
71            candidate = candidateBase + i
72        }
73        usedFieldNames[scope].add(candidate)
74        return candidate
75    }
76}
77
78val ExprModel.ext by Delegates.lazy { target : ExprModel ->
79    ExprModelExt()
80}
81
82fun ExprModel.getUniqueFieldName(base : String, isPublic : kotlin.Boolean) : String = ext.getUniqueName(base, Scope.FIELD, isPublic)
83fun ExprModel.getUniqueMethodName(base : String, isPublic : kotlin.Boolean) : String = ext.getUniqueName(base, Scope.METHOD, isPublic)
84fun ExprModel.getUniqueFlagName(base : String) : String = ext.getUniqueName(base, Scope.FLAG, false)
85fun ExprModel.getConstructorParamName(base : String) : String = ext.getUniqueName(base, Scope.CONSTRUCTOR_PARAM, false)
86
87fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
88
89// not necessarily unique. Uniqueness is solved per scope
90val BindingTarget.readableName by Delegates.lazy { target: BindingTarget ->
91    if (target.getId() == null) {
92        "boundView" + indexFromTag(target.getTag())
93    } else {
94        target.getId().androidId().stripNonJava()
95    }
96}
97
98fun BindingTarget.superConversion(variable : String) : String {
99    if (getResolvedType() != null && getResolvedType().extendsViewStub()) {
100        return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})"
101    } else {
102        return "(${interfaceType}) ${variable}"
103    }
104}
105
106val BindingTarget.fieldName : String by Delegates.lazy { target : BindingTarget ->
107    val name : String
108    val isPublic : kotlin.Boolean
109    if (target.getId() == null) {
110        name = "m${target.readableName}"
111        isPublic = false
112    } else {
113        name = target.readableName
114        isPublic = true
115    }
116    target.getModel().getUniqueFieldName(name, isPublic)
117}
118
119val BindingTarget.androidId by Delegates.lazy { target : BindingTarget ->
120    if (target.getId().startsWith("@android:id/")) {
121        "android.R.id.${target.getId().androidId()}"
122    } else {
123        "R.id.${target.getId().androidId()}"
124    }
125}
126
127val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget ->
128    if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) {
129        "android.databinding.ViewStubProxy"
130    } else {
131        target.getInterfaceType()
132    }
133}
134
135val BindingTarget.constructorParamName by Delegates.lazy { target : BindingTarget ->
136    target.getModel().getConstructorParamName(target.readableName)
137}
138
139// not necessarily unique. Uniqueness is decided per scope
140val Expr.readableName by Delegates.lazy { expr : Expr ->
141    val stripped = "${expr.getUniqueKey().stripNonJava()}"
142    L.d("readableUniqueName for [%s] %s is %s", System.identityHashCode(expr), expr.getUniqueKey(), stripped)
143    stripped
144}
145
146val Expr.fieldName by Delegates.lazy { expr : Expr ->
147    expr.getModel().getUniqueFieldName("m${expr.readableName.capitalize()}", false)
148}
149
150val Expr.listenerClassName by Delegates.lazy { expr : Expr ->
151    expr.getModel().getUniqueFieldName("${expr.getResolvedType().getSimpleName()}Impl", false)
152}
153
154val Expr.oldValueName by Delegates.lazy { expr : Expr ->
155    expr.getModel().getUniqueFieldName("mOld${expr.readableName.capitalize()}", false)
156}
157
158val Expr.executePendingLocalName by Delegates.lazy { expr : Expr ->
159    "${expr.getModel().ext.getUniqueName(expr.readableName, Scope.EXECUTE_PENDING_METHOD, false)}"
160}
161
162val Expr.setterName by Delegates.lazy { expr : Expr ->
163    expr.getModel().getUniqueMethodName("set${expr.readableName.capitalize()}", true)
164}
165
166val Expr.onChangeName by Delegates.lazy { expr : Expr ->
167    expr.getModel().getUniqueMethodName("onChange${expr.readableName.capitalize()}", false)
168}
169
170val Expr.getterName by Delegates.lazy { expr : Expr ->
171    expr.getModel().getUniqueMethodName("get${expr.readableName.capitalize()}", true)
172}
173
174val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
175    expr.getModel().getUniqueFlagName("sFlag${expr.readableName.capitalize()}")
176}
177
178
179fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
180
181fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix"
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
200val LayoutBinder.requiredComponent by Delegates.lazy { layoutBinder: LayoutBinder ->
201    val required = layoutBinder.
202            getBindingTargets().
203            flatMap { it.getBindings() }.
204            firstOrNull { it.getBindingAdapterInstanceClass() != null }
205    required?.getBindingAdapterInstanceClass()
206}
207
208fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
209
210fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
211    buckets.withIndex().forEach {
212        if (it.value != 0L) {
213            cb(getWordSuffix(it.index), buckets[it.index])
214        }
215    }
216}
217
218fun FlagSet.getWordSuffix(wordIndex : Int) : String {
219    return if(wordIndex == 0) "" else "_${wordIndex}"
220}
221
222fun FlagSet.localValue(bucketIndex : Int) =
223        if (getLocalName() == null) binaryCode(bucketIndex)
224        else "${getLocalName()}${getWordSuffix(bucketIndex)}"
225
226fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
227
228
229fun longToBinary(l : Long) =
230        "0b${java.lang.Long.toBinaryString(l)}L"
231
232fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
233    val min = Math.min(buckets.size(), other.buckets.size())
234    val result = arrayListOf<T>()
235    for (i in 0..(min - 1)) {
236        // if these two can match by any chance, call the callback
237        if (intersect(other, i)) {
238            result.add(cb(getWordSuffix(i), i))
239        }
240    }
241    return result
242}
243
244fun indexFromTag(tag : String) : kotlin.Int {
245    val startIndex : kotlin.Int
246    if (tag.startsWith("binding_")) {
247        startIndex = "binding_".length();
248    } else {
249        startIndex = tag.lastIndexOf('_') + 1
250    }
251    return Integer.parseInt(tag.substring(startIndex))
252}
253
254class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
255    val model = layoutBinder.getModel()
256    val indices = HashMap<BindingTarget, kotlin.Int>()
257    val mDirtyFlags by Delegates.lazy {
258        val fs = FlagSet(BitSet(), model.getFlagBucketCount());
259        Arrays.fill(fs.buckets, -1)
260        fs.setDynamic(true)
261        model.localizeFlag(fs, "mDirtyFlags")
262        fs
263    }
264
265    val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
266    val className = layoutBinder.getImplementationName()
267
268    val baseClassName = "${layoutBinder.getClassName()}"
269
270    val includedBinders by Delegates.lazy {
271        layoutBinder.getBindingTargets().filter { it.isBinder() }
272    }
273
274    val variables by Delegates.lazy {
275        model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
276    }
277
278    val usedVariables by Delegates.lazy {
279        variables.filter {it.isUsed()}
280    }
281
282    public fun write(minSdk : kotlin.Int) : String  {
283        layoutBinder.resolveWhichExpressionsAreUsed()
284        calculateIndices();
285        return kcode("package ${layoutBinder.getPackage()};") {
286            nl("import ${layoutBinder.getModulePackage()}.R;")
287            nl("import ${layoutBinder.getModulePackage()}.BR;")
288            nl("import android.view.View;")
289            val classDeclaration : String
290            if (layoutBinder.hasVariations()) {
291                classDeclaration = "${className} extends ${baseClassName}"
292            } else {
293                classDeclaration = "${className} extends android.databinding.ViewDataBinding"
294            }
295            nl("public class ${classDeclaration} {") {
296                tab(declareIncludeViews())
297                tab(declareViews())
298                tab(declareVariables())
299                tab(declareBoundValues())
300                tab(declareListeners())
301                tab(declareConstructor(minSdk))
302                tab(declareInvalidateAll())
303                tab(declareHasPendingBindings())
304                tab(declareLog())
305                tab(declareSetVariable())
306                tab(variableSettersAndGetters())
307                tab(onFieldChange())
308
309                tab(executePendingBindings())
310
311                tab(declareListenerImpls())
312                tab(declareDirtyFlags())
313                if (!layoutBinder.hasVariations()) {
314                    tab(declareFactories())
315                }
316            }
317            nl("}")
318            tab(flagMapping())
319            tab("//end")
320        }.generate()
321    }
322    fun calculateIndices() : Unit {
323        val taggedViews = layoutBinder.getBindingTargets().filter{
324            it.isUsed() && it.getTag() != null && !it.isBinder()
325        }
326        taggedViews.forEach {
327            indices.put(it, indexFromTag(it.getTag()))
328        }
329        val indexStart = maxIndex() + 1
330        layoutBinder.getBindingTargets().filter{
331            it.isUsed() && !taggedViews.contains(it)
332        }.withIndex().forEach {
333            indices.put(it.value, it.index + indexStart)
334        }
335    }
336    fun declareIncludeViews() = kcode("") {
337        nl("private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;")
338        nl("private static final android.util.SparseIntArray sViewsWithIds;")
339        nl("static {") {
340            val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
341            if (!hasBinders) {
342                tab("sIncludes = null;")
343            } else {
344                val numBindings = layoutBinder.getBindingTargets().filter{ it.isUsed() }.count()
345                tab("sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts(${numBindings});")
346                val includeMap = HashMap<BindingTarget, ArrayList<BindingTarget>>()
347                layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder() }.forEach {
348                    val includeTag = it.getTag();
349                    val parent = layoutBinder.getBindingTargets().firstOrNull {
350                        it.isUsed() && !it.isBinder() && includeTag.equals(it.getTag())
351                    }
352                    if (parent == null) {
353                        throw IllegalStateException("Could not find parent of include file")
354                    }
355                    var list = includeMap.get(parent)
356                    if (list == null) {
357                        list = ArrayList<BindingTarget>()
358                        includeMap.put(parent, list)
359                    }
360                    list.add(it)
361                }
362
363                includeMap.keySet().forEach {
364                    val index = indices.get(it)
365                    tab("sIncludes.setIncludes(${index}, ") {
366                        tab ("new String[] {${
367                        includeMap.get(it).map {
368                            "\"${it.getIncludedLayout()}\""
369                        }.joinToString(", ")
370                        }},")
371                        tab("new int[] {${
372                        includeMap.get(it).map {
373                            "${indices.get(it)}"
374                        }.joinToString(", ")
375                        }},")
376                        tab("new int[] {${
377                        includeMap.get(it).map {
378                            "R.layout.${it.getIncludedLayout()}"
379                        }.joinToString(", ")
380                        }});")
381                    }
382                }
383            }
384            val viewsWithIds = layoutBinder.getBindingTargets().filter {
385                it.isUsed() && !it.isBinder() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
386            }
387            if (viewsWithIds.isEmpty()) {
388                tab("sViewsWithIds = null;")
389            } else {
390                tab("sViewsWithIds = new android.util.SparseIntArray();")
391                viewsWithIds.forEach {
392                    tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
393                }
394            }
395        }
396        nl("}")
397    }
398
399    fun maxIndex() : kotlin.Int {
400        val maxIndex = indices.values().max()
401        if (maxIndex == null) {
402            return -1
403        } else {
404            return maxIndex
405        }
406    }
407
408    fun declareConstructor(minSdk : kotlin.Int) = kcode("") {
409        val bindingCount = maxIndex() + 1
410        val parameterType : String
411        val superParam : String
412        if (layoutBinder.isMerge()) {
413            parameterType = "View[]"
414            superParam = "root[0]"
415        } else {
416            parameterType = "View"
417            superParam = "root"
418        }
419        val rootTagsSupported = minSdk >= 14
420        if (layoutBinder.hasVariations()) {
421            nl("")
422            nl("public ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
423                tab("this(bindingComponent, ${superParam}, mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds));")
424            }
425            nl("}")
426            nl("private ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root, Object[] bindings) {") {
427                tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()}") {
428                    layoutBinder.getSortedTargets().filter { it.getId() != null }.forEach {
429                        tab(", ${fieldConversion(it)}")
430                    }
431                    tab(");")
432                }
433            }
434        } else {
435            nl("public ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
436                tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()});")
437                tab("final Object[] bindings = mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds);")
438            }
439        }
440        if (layoutBinder.requiredComponent != null) {
441            tab("ensureBindingComponentIsNotNull(${layoutBinder.requiredComponent}.class);")
442        }
443        val taggedViews = layoutBinder.getSortedTargets().filter{it.isUsed()}
444        taggedViews.forEach {
445            if (!layoutBinder.hasVariations() || it.getId() == null) {
446                tab("this.${it.fieldName} = ${fieldConversion(it)};")
447            }
448            if (!it.isBinder()) {
449                if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
450                    tab("this.${it.fieldName}.setContainingBinding(this);")
451                }
452                if (it.supportsTag() && it.getTag() != null &&
453                        (rootTagsSupported || it.getTag().startsWith("binding_"))) {
454                    val originalTag = it.getOriginalTag();
455                    var tagValue = "null"
456                    if (originalTag != null && !originalTag.startsWith("@{")) {
457                        tagValue = "\"${originalTag}\""
458                        if (originalTag.startsWith("@")) {
459                            var packageName = layoutBinder.getModulePackage()
460                            if (originalTag.startsWith("@android:")) {
461                                packageName = "android"
462                            }
463                            val slashIndex = originalTag.indexOf('/')
464                            val resourceId = originalTag.substring(slashIndex + 1)
465                            tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
466                        }
467                    }
468                    tab("this.${it.fieldName}.setTag(${tagValue});")
469                } else if (it.getTag() != null && !it.getTag().startsWith("binding_") &&
470                    it.getOriginalTag() != null) {
471                    L.e(ErrorMessages.ROOT_TAG_NOT_SUPPORTED, it.getOriginalTag())
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            tab("requestRebind();");
507        }
508        nl("}")
509    }
510
511    fun declareHasPendingBindings()  = kcode("") {
512        nl("@Override")
513        nl("public boolean hasPendingBindings() {") {
514            if (mDirtyFlags.buckets.size() > 0) {
515                tab("synchronized(this) {") {
516                    val flagCheck = 0.rangeTo(mDirtyFlags.buckets.size() - 1).map {
517                            "${mDirtyFlags.localValue(it)} != 0"
518                    }.joinToString(" || ")
519                    tab("if (${flagCheck}) {") {
520                        tab("return true;")
521                    }
522                    tab("}")
523                }
524                tab("}")
525            }
526            includedBinders.filter{it.isUsed()}.forEach { binder ->
527                tab("if (${binder.fieldName}.hasPendingBindings()) {") {
528                    tab("return true;")
529                }
530                tab("}")
531            }
532            tab("return false;")
533        }
534        nl("}")
535    }
536
537    fun declareSetVariable() = kcode("") {
538        nl("public boolean setVariable(int variableId, Object variable) {") {
539            tab("switch(variableId) {") {
540                usedVariables.forEach {
541                    tab ("case ${it.getName().br()} :") {
542                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
543                        tab("return true;")
544                    }
545                }
546            }
547            tab("}")
548            tab("return false;")
549        }
550        nl("}")
551    }
552
553    fun declareLog() = kcode("") {
554        nl("private void log(String msg, long i) {") {
555            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
556        }
557        nl("}")
558    }
559
560    fun variableSettersAndGetters() = kcode("") {
561        variables.filterNot{it.isUsed()}.forEach {
562            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
563                tab("// not used, ignore")
564            }
565            nl("}")
566            nl("")
567            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
568                tab("return ${it.getDefaultValue()};")
569            }
570            nl("}")
571        }
572        usedVariables.forEach {
573            if (it.getUserDefinedType() != null) {
574                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
575                    if (it.isObservable()) {
576                        tab("updateRegistration(${it.getId()}, ${it.readableName});");
577                    }
578                    tab("this.${it.fieldName} = ${it.readableName};")
579                    // set dirty flags!
580                    val flagSet = it.invalidateFlagSet
581                    tab("synchronized(this) {") {
582                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
583                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
584                        }
585                    } tab ("}")
586                    tab("super.requestRebind();")
587                }
588                nl("}")
589                nl("")
590                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
591                    tab("return ${it.fieldName};")
592                }
593                nl("}")
594            }
595        }
596    }
597
598    fun onFieldChange() = kcode("") {
599        nl("@Override")
600        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
601            tab("switch (localFieldId) {") {
602                model.getObservables().forEach {
603                    tab("case ${it.getId()} :") {
604                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
605                    }
606                }
607            }
608            tab("}")
609            tab("return false;")
610        }
611        nl("}")
612        nl("")
613
614        model.getObservables().forEach {
615            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
616                tab("switch (fieldId) {", {
617                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
618                    accessedFields.filter { it.hasBindableAnnotations() }
619                            .groupBy { it.getName() }
620                            .forEach {
621                                tab("case ${it.key.br()}:") {
622                                    val field = it.value.first()
623                                    tab("synchronized(this) {") {
624                                        mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
625                                            tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
626                                        }
627                                    } tab("}")
628                                    tab("return true;")
629                                }
630
631                            }
632                    tab("case ${"".br()}:") {
633                        val flagSet = it.invalidateFlagSet
634                        tab("synchronized(this) {") {
635                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
636                                tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
637                            }
638                        } tab("}")
639                        tab("return true;")
640                    }
641
642                })
643                tab("}")
644                tab("return false;")
645            }
646            nl("}")
647            nl("")
648        }
649    }
650
651    fun declareViews() = kcode("// views") {
652        val oneLayout = !layoutBinder.hasVariations();
653        layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
654            val access : String
655            if (oneLayout && it.getId() != null) {
656                access = "public"
657            } else {
658                access = "private"
659            }
660            nl("${access} final ${it.interfaceType} ${it.fieldName};")
661        }
662    }
663
664    fun declareVariables() = kcode("// variables") {
665        usedVariables.forEach {
666            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
667        }
668    }
669
670    fun declareBoundValues() = kcode("// values") {
671        layoutBinder.getSortedTargets().filter { it.isUsed() }
672                .flatMap { it.getBindings() }
673                .filter { it.requiresOldValue() }
674                .flatMap{ it.getComponentExpressions().toArrayList() }
675                .groupBy { it }
676                .forEach {
677                    val expr = it.getKey()
678                    nl("private ${expr.getResolvedType().toJavaCode()} ${expr.oldValueName};")
679                }
680    }
681
682    fun declareListeners() = kcode("// listeners") {
683        model.getExprMap().values().filter {
684            it is ListenerExpr
685        }.groupBy { it }.forEach {
686            val expr = it.key as ListenerExpr
687            nl("private ${expr.listenerClassName} ${expr.fieldName};")
688        }
689    }
690
691    fun declareDirtyFlags() = kcode("// dirty flag") {
692        model.ext.localizedFlags.forEach { flag ->
693            flag.notEmpty { suffix, value ->
694                nl("private")
695                app(" ", if(flag.isDynamic()) null else "static final");
696                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
697            }
698        }
699    }
700
701    fun flagMapping() = kcode("/* flag mapping") {
702        if (model.getFlagMapping() != null) {
703            val mapping = model.getFlagMapping()
704            for (i in mapping.indices) {
705                tab("flag $i: ${mapping[i]}")
706            }
707        }
708        nl("flag mapping end*/")
709    }
710
711    fun executePendingBindings() = kcode("") {
712        nl("@Override")
713        nl("protected void executeBindings() {") {
714            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
715            tmpDirtyFlags.setLocalName("dirtyFlags");
716            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
717                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
718            }
719            tab("synchronized(this) {") {
720                for (i in (0..mDirtyFlags.buckets.size() - 1)) {
721                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
722                    tab("${mDirtyFlags.localValue(i)} = 0;")
723                }
724            } tab("}")
725            model.getPendingExpressions().filterNot { !it.canBeEvaluatedToAVariable() || (it.isVariable() && !it.isUsed()) }.forEach {
726                tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.getDefaultValue()};")
727            }
728            L.d("writing executePendingBindings for %s", className)
729            do {
730                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
731                val justRead = arrayListOf<Expr>()
732                L.d("batch: %s", batch)
733                while (!batch.none()) {
734                    val readNow = batch.filter { it.shouldReadNow(justRead) }
735                    if (readNow.isEmpty()) {
736                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
737                    }
738                    L.d("new read now. batch size: %d, readNow size: %d", batch.size(), readNow.size())
739                    nl(readWithDependants(readNow, justRead, batch, tmpDirtyFlags))
740                    batch.removeAll(justRead)
741                }
742                tab("// batch finished")
743            } while (model.markBitsRead())
744            // verify everything is read.
745            val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
746            if (batch.isNotEmpty()) {
747                L.e("could not generate code for %s. This might be caused by circular dependencies."
748                        + "Please report on b.android.com. %d %s %s", layoutBinder.getLayoutname(),
749                        batch.size(), batch.get(0), batch.get(0).toCode().generate())
750            }
751            //
752            layoutBinder.getSortedTargets().filter { it.isUsed() }
753                    .flatMap { it.getBindings() }
754                    .groupBy {
755                        "${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
756                            "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
757                        }.joinToString(" || ") }"
758                    }.forEach {
759                tab("if (${it.key}) {") {
760                    it.value.groupBy { Math.max(1, it.getMinApi()) }.forEach {
761                        val setterValues = kcode("") {
762                            it.value.forEach { binding ->
763                                val fieldName: String
764                                if (binding.getTarget().getViewClass().
765                                        equals(binding.getTarget().getInterfaceType())) {
766                                    fieldName = "this.${binding.getTarget().fieldName}"
767                                } else {
768                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
769                                }
770                                tab(binding.toJavaCode(fieldName, "this.mBindingComponent")).app(";")
771                            }
772                        }
773                        tab("// api target ${it.key}")
774                        if (it.key > 1) {
775                            tab("if(getBuildSdkInt() >= ${it.key}) {") {
776                                app("", setterValues)
777                            }
778                            tab("}")
779                        } else {
780                            app("", setterValues)
781                        }
782                    }
783                }
784                tab("}")
785            }
786
787
788            layoutBinder.getSortedTargets().filter { it.isUsed() }
789                    .flatMap { it.getBindings() }
790                    .filter { it.requiresOldValue() }
791                    .groupBy {"${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
792                        "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
793                    }.joinToString(" || ")
794                    }"}.forEach {
795                tab("if (${it.key}) {") {
796                    it.value.groupBy { it.getExpr() }.map { it.value.first() }.forEach {
797                        it.getComponentExpressions().forEach { expr ->
798                            tab("this.${expr.oldValueName} = ${expr.toCode().generate()};")
799                        }
800                    }
801                }
802                tab("}")
803            }
804            includedBinders.filter{it.isUsed()}.forEach { binder ->
805                tab("${binder.fieldName}.executePendingBindings();")
806            }
807            layoutBinder.getSortedTargets().filter{
808                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
809            }.forEach {
810                tab("if (${it.fieldName}.getBinding() != null) {") {
811                    tab("${it.fieldName}.getBinding().executePendingBindings();")
812                }
813                tab("}")
814            }
815        }
816        nl("}")
817    }
818
819    fun readWithDependants(expressionList: List<Expr>, justRead: MutableList<Expr>,
820            batch: MutableList<Expr>, tmpDirtyFlags: FlagSet,
821            inheritedFlags: FlagSet? = null) : KCode = kcode("") {
822        expressionList.groupBy { it.shouldReadFlagSet }.forEach {
823            val flagSet = it.key
824            val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
825            val expressions = it.value
826            val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
827                "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
828            }.joinToString(" || ")
829            })"
830            val readCode = kcode("") {
831                val dependants = ArrayList<Expr>()
832                expressions.groupBy { condition(it) }.forEach {
833                    val condition = it.key
834                    val assignedValues = it.value.filter {
835                        it.canBeEvaluatedToAVariable() && !it.isVariable()
836                    }
837                    if (!assignedValues.isEmpty()) {
838                        val assignment = kcode("") {
839                            assignedValues.forEach { expr: Expr ->
840                                tab("// read ${expr.getUniqueKey()}")
841                                tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";")
842                            }
843                        }
844                        if (condition != null) {
845                            tab("if (${condition}) {") {
846                                app("", assignment)
847                            }
848                            tab ("}")
849                        } else {
850                            app("", assignment)
851                        }
852                    }
853                    it.value.filter { it.isObservable() }.forEach { expr: Expr ->
854                        tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
855                    }
856
857                    it.value.forEach { expr: Expr ->
858                        justRead.add(expr)
859                        L.d("%s / readWithDependants %s", className, expr.getUniqueKey());
860                        L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
861
862                        // if I am the condition for an expression, set its flag
863                        val conditionals = expr.getDependants().filter {
864                            !it.isConditional() && it.getDependant() is TernaryExpr &&
865                                    (it.getDependant() as TernaryExpr).getPred() == expr
866                        }.map { it.getDependant() }
867                        if (conditionals.isNotEmpty()) {
868                            tab("// setting conditional flags")
869                            tab("if (${expr.executePendingLocalName}) {") {
870                                conditionals.forEach {
871                                    val set = it.getRequirementFlagSet(true)
872                                    mDirtyFlags.mapOr(set) { suffix, index ->
873                                        tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
874                                    }
875                                }
876                            }
877                            tab("} else {") {
878                                conditionals.forEach {
879                                    val set = it.getRequirementFlagSet(false)
880                                    mDirtyFlags.mapOr(set) { suffix, index ->
881                                        tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
882                                    }
883                                }
884                            }
885                            tab("}")
886                        }
887
888                        val chosen = expr.getDependants().filter {
889                            val dependant = it.getDependant()
890                            batch.contains(dependant) &&
891                                    dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
892                                    dependant.shouldReadNow(justRead)
893                        }
894                        if (chosen.isNotEmpty()) {
895                            dependants.addAll(chosen.map { it.getDependant() })
896                        }
897                    }
898                }
899                if (dependants.isNotEmpty()) {
900                    val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
901                    nl(readWithDependants(dependants, justRead, batch, tmpDirtyFlags, nextInheritedFlags))
902                }
903            }
904
905            if (needsIfWrapper) {
906                tab(ifClause) {
907                    app(" {")
908                    app("", readCode)
909                }
910                tab("}")
911            } else {
912                app("", readCode)
913            }
914        }
915    }
916
917    fun condition(expr : Expr) : String? {
918        if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
919            // create an if case for all dependencies that might be null
920            val nullables = expr.getDependencies().filter {
921                it.isMandatory() && it.getOther().getResolvedType().isNullable()
922            }.map { it.getOther() }
923            if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
924                return "${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}"
925            } else {
926                return null
927            }
928        } else {
929            return null
930        }
931    }
932
933    fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
934        model.getExprMap().values().filter {
935            it.isUsed() && it is ListenerExpr
936        }.groupBy { it }.forEach {
937            val expr = it.key as ListenerExpr
938            val listenerType = expr.getResolvedType();
939            val extendsImplements : String
940            if (listenerType.isInterface()) {
941                extendsImplements = "implements"
942            } else {
943                extendsImplements = "extends"
944            }
945            nl("public static class ${expr.listenerClassName} ${extendsImplements} ${listenerType.getCanonicalName()}{") {
946                if (expr.getChild().isDynamic()) {
947                    tab("private ${expr.getChild().getResolvedType().toJavaCode()} value;")
948                    tab("public ${expr.listenerClassName} setValue(${expr.getChild().getResolvedType().toJavaCode()} value) {") {
949                        tab("this.value = value;")
950                        tab("return value == null ? null : this;")
951                    }
952                    tab("}")
953                }
954                val listenerMethod = expr.getMethod()
955                val parameterTypes = listenerMethod.getParameterTypes()
956                val returnType = listenerMethod.getReturnType(parameterTypes.toArrayList())
957                tab("@Override")
958                tab("public ${returnType} ${listenerMethod.getName()}(${
959                    parameterTypes.withIndex().map {
960                        "${it.value.toJavaCode()} arg${it.index}"
961                    }.joinToString(", ")
962                }) {") {
963                    val obj : String
964                    if (expr.getChild().isDynamic()) {
965                        obj = "this.value"
966                    } else {
967                        obj = expr.getChild().toCode().generate();
968                    }
969                    val returnStr : String
970                    if (!returnType.isVoid()) {
971                        returnStr = "return "
972                    } else {
973                        returnStr = ""
974                    }
975                    val args = parameterTypes.withIndex().map {
976                        "arg${it.index}"
977                    }.joinToString(", ")
978                    tab("${returnStr}${obj}.${expr.getName()}(${args});")
979                }
980                tab("}")
981            }
982            nl("}")
983        }
984    }
985
986    fun declareFactories() = kcode("") {
987        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
988            tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
989        }
990        nl("}")
991        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
992            tab("return android.databinding.DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
993        }
994        nl("}")
995        if (!layoutBinder.isMerge()) {
996            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
997                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
998            }
999            nl("}")
1000            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1001                tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false), bindingComponent);")
1002            }
1003            nl("}")
1004            nl("public static ${baseClassName} bind(android.view.View view) {") {
1005                tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1006            }
1007            nl("}")
1008            nl("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1009                tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
1010                    tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
1011                }
1012                tab("}")
1013                tab("return new ${baseClassName}(bindingComponent, view);")
1014            }
1015            nl("}")
1016        }
1017    }
1018
1019    /**
1020     * When called for a library compilation, we do not generate real implementations
1021     */
1022    public fun writeBaseClass(forLibrary : Boolean) : String =
1023        kcode("package ${layoutBinder.getPackage()};") {
1024            nl("import android.databinding.Bindable;")
1025            nl("import android.databinding.DataBindingUtil;")
1026            nl("import android.databinding.ViewDataBinding;")
1027            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
1028            layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1029                tab("public final ${it.interfaceType} ${it.fieldName};")
1030            }
1031            nl("")
1032            tab("protected ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
1033                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1034                    tab(", ${it.interfaceType} ${it.constructorParamName}")
1035                }
1036            }
1037            tab(") {") {
1038                tab("super(bindingComponent, root_, localFieldCount);")
1039                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1040                    tab("this.${it.fieldName} = ${it.constructorParamName};")
1041                }
1042            }
1043            tab("}")
1044            nl("")
1045            variables.forEach {
1046                if (it.getUserDefinedType() != null) {
1047                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
1048                    tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
1049                }
1050            }
1051            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1052                tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1053            }
1054            tab("}")
1055            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
1056                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1057            }
1058            tab("}")
1059            tab("public static ${baseClassName} bind(android.view.View view) {") {
1060                if (forLibrary) {
1061                    tab("return null;")
1062                } else {
1063                    tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1064                }
1065            }
1066            tab("}")
1067            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1068                if (forLibrary) {
1069                    tab("return null;")
1070                } else {
1071                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
1072                }
1073            }
1074            tab("}")
1075            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1076                if (forLibrary) {
1077                    tab("return null;")
1078                } else {
1079                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false, bindingComponent);")
1080                }
1081            }
1082            tab("}")
1083            tab("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1084                if (forLibrary) {
1085                    tab("return null;")
1086                } else {
1087                    tab("return (${baseClassName})bind(bindingComponent, view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
1088                }
1089            }
1090            tab("}")
1091            nl("}")
1092        }.generate()
1093}
1094