LayoutBinderWriter.kt revision 5d454e5f2397a3b160f081ce123b4ca7ff0fc356
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.shouldReadWithConditionalsFlagSet by Delegates.versionedLazy { expr : Expr ->
196    FlagSet(expr.getShouldReadFlagsWithConditionals(), expr.getModel().getFlagBucketCount())
197}
198
199val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
200    arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
201            FlagSet(expr.getRequirementFlagIndex(true)))
202}
203
204val LayoutBinder.requiredComponent by Delegates.lazy { layoutBinder: LayoutBinder ->
205    val required = layoutBinder.
206            getBindingTargets().
207            flatMap { it.getBindings() }.
208            firstOrNull { it.getBindingAdapterInstanceClass() != null }
209    required?.getBindingAdapterInstanceClass()
210}
211
212fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
213
214fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
215    buckets.withIndex().forEach {
216        if (it.value != 0L) {
217            cb(getWordSuffix(it.index), buckets[it.index])
218        }
219    }
220}
221
222fun FlagSet.getWordSuffix(wordIndex : Int) : String {
223    return if(wordIndex == 0) "" else "_${wordIndex}"
224}
225
226fun FlagSet.localValue(bucketIndex : Int) =
227        if (getLocalName() == null) binaryCode(bucketIndex)
228        else "${getLocalName()}${getWordSuffix(bucketIndex)}"
229
230fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
231
232
233fun longToBinary(l : Long) =
234        "0b${java.lang.Long.toBinaryString(l)}L"
235
236fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
237    val min = Math.min(buckets.size(), other.buckets.size())
238    val result = arrayListOf<T>()
239    for (i in 0..(min - 1)) {
240        // if these two can match by any chance, call the callback
241        if (intersect(other, i)) {
242            result.add(cb(getWordSuffix(i), i))
243        }
244    }
245    return result
246}
247
248fun indexFromTag(tag : String) : kotlin.Int {
249    val startIndex : kotlin.Int
250    if (tag.startsWith("binding_")) {
251        startIndex = "binding_".length();
252    } else {
253        startIndex = tag.lastIndexOf('_') + 1
254    }
255    return Integer.parseInt(tag.substring(startIndex))
256}
257
258class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
259    val model = layoutBinder.getModel()
260    val indices = HashMap<BindingTarget, kotlin.Int>()
261    val mDirtyFlags by Delegates.lazy {
262        val fs = FlagSet(BitSet(), model.getFlagBucketCount());
263        Arrays.fill(fs.buckets, -1)
264        fs.setDynamic(true)
265        model.localizeFlag(fs, "mDirtyFlags")
266        fs
267    }
268
269    val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
270    val className = layoutBinder.getImplementationName()
271
272    val baseClassName = "${layoutBinder.getClassName()}"
273
274    val includedBinders by Delegates.lazy {
275        layoutBinder.getBindingTargets().filter { it.isBinder() }
276    }
277
278    val variables by Delegates.lazy {
279        model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
280    }
281
282    val usedVariables by Delegates.lazy {
283        variables.filter {it.isUsed()}
284    }
285
286    public fun write(minSdk : kotlin.Int) : String  {
287        layoutBinder.resolveWhichExpressionsAreUsed()
288        calculateIndices();
289        return kcode("package ${layoutBinder.getPackage()};") {
290            nl("import ${layoutBinder.getModulePackage()}.R;")
291            nl("import ${layoutBinder.getModulePackage()}.BR;")
292            nl("import android.view.View;")
293            val classDeclaration : String
294            if (layoutBinder.hasVariations()) {
295                classDeclaration = "${className} extends ${baseClassName}"
296            } else {
297                classDeclaration = "${className} extends android.databinding.ViewDataBinding"
298            }
299            nl("public class ${classDeclaration} {") {
300                tab(declareIncludeViews())
301                tab(declareViews())
302                tab(declareVariables())
303                tab(declareBoundValues())
304                tab(declareListeners())
305                tab(declareConstructor(minSdk))
306                tab(declareInvalidateAll())
307                tab(declareHasPendingBindings())
308                tab(declareLog())
309                tab(declareSetVariable())
310                tab(variableSettersAndGetters())
311                tab(onFieldChange())
312
313                tab(executePendingBindings())
314
315                tab(declareListenerImpls())
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()
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.IncludedLayouts 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.IncludedLayouts(${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.setIncludes(${index}, ") {
370                        tab ("new String[] {${
371                        includeMap.get(it)!!.map {
372                            "\"${it.getIncludedLayout()}\""
373                        }.joinToString(", ")
374                        }},")
375                        tab("new int[] {${
376                        includeMap.get(it)!!.map {
377                            "${indices.get(it)}"
378                        }.joinToString(", ")
379                        }},")
380                        tab("new int[] {${
381                        includeMap.get(it)!!.map {
382                            "R.layout.${it.getIncludedLayout()}"
383                        }.joinToString(", ")
384                        }});")
385                    }
386                }
387            }
388            val viewsWithIds = layoutBinder.getBindingTargets().filter {
389                it.isUsed() && !it.isBinder() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
390            }
391            if (viewsWithIds.isEmpty()) {
392                tab("sViewsWithIds = null;")
393            } else {
394                tab("sViewsWithIds = new android.util.SparseIntArray();")
395                viewsWithIds.forEach {
396                    tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
397                }
398            }
399        }
400        nl("}")
401    }
402
403    fun maxIndex() : kotlin.Int {
404        val maxIndex = indices.values().max()
405        if (maxIndex == null) {
406            return -1
407        } else {
408            return maxIndex
409        }
410    }
411
412    fun declareConstructor(minSdk : kotlin.Int) = kcode("") {
413        val bindingCount = maxIndex() + 1
414        val parameterType : String
415        val superParam : String
416        if (layoutBinder.isMerge()) {
417            parameterType = "View[]"
418            superParam = "root[0]"
419        } else {
420            parameterType = "View"
421            superParam = "root"
422        }
423        val rootTagsSupported = minSdk >= 14
424        if (layoutBinder.hasVariations()) {
425            nl("")
426            nl("public ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
427                tab("this(bindingComponent, ${superParam}, mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds));")
428            }
429            nl("}")
430            nl("private ${className}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root, Object[] bindings) {") {
431                tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()}") {
432                    layoutBinder.getSortedTargets().filter { it.getId() != null }.forEach {
433                        tab(", ${fieldConversion(it)}")
434                    }
435                    tab(");")
436                }
437            }
438        } else {
439            nl("public ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, ${parameterType} root) {") {
440                tab("super(bindingComponent, ${superParam}, ${model.getObservables().size()});")
441                tab("final Object[] bindings = mapBindings(bindingComponent, root, ${bindingCount}, sIncludes, sViewsWithIds);")
442            }
443        }
444        if (layoutBinder.requiredComponent != null) {
445            tab("ensureBindingComponentIsNotNull(${layoutBinder.requiredComponent}.class);")
446        }
447        val taggedViews = layoutBinder.getSortedTargets().filter{it.isUsed()}
448        taggedViews.forEach {
449            if (!layoutBinder.hasVariations() || it.getId() == null) {
450                tab("this.${it.fieldName} = ${fieldConversion(it)};")
451            }
452            if (!it.isBinder()) {
453                if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
454                    tab("this.${it.fieldName}.setContainingBinding(this);")
455                }
456                if (it.supportsTag() && it.getTag() != null &&
457                        (rootTagsSupported || it.getTag().startsWith("binding_"))) {
458                    val originalTag = it.getOriginalTag();
459                    var tagValue = "null"
460                    if (originalTag != null && !originalTag.startsWith("@{")) {
461                        tagValue = "\"${originalTag}\""
462                        if (originalTag.startsWith("@")) {
463                            var packageName = layoutBinder.getModulePackage()
464                            if (originalTag.startsWith("@android:")) {
465                                packageName = "android"
466                            }
467                            val slashIndex = originalTag.indexOf('/')
468                            val resourceId = originalTag.substring(slashIndex + 1)
469                            tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
470                        }
471                    }
472                    tab("this.${it.fieldName}.setTag(${tagValue});")
473                } else if (it.getTag() != null && !it.getTag().startsWith("binding_") &&
474                    it.getOriginalTag() != null) {
475                    L.e(ErrorMessages.ROOT_TAG_NOT_SUPPORTED, it.getOriginalTag())
476                }
477            }
478        }
479        tab("setRootTag(root);")
480        tab("invalidateAll();");
481        nl("}")
482    }
483
484    fun fieldConversion(target : BindingTarget) : String {
485        if (!target.isUsed()) {
486            return "null"
487        } else {
488            val index = indices.get(target)
489            if (index == null) {
490                throw IllegalStateException("Unknown binding target")
491            }
492            val variableName = "bindings[${index}]"
493            return target.superConversion(variableName)
494        }
495    }
496
497    fun declareInvalidateAll() = kcode("") {
498        nl("@Override")
499        nl("public void invalidateAll() {") {
500            val fs = FlagSet(layoutBinder.getModel().getInvalidateAnyBitSet(),
501                    layoutBinder.getModel().getFlagBucketCount());
502            tab("synchronized(this) {") {
503                for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
504                    tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
505                }
506            } tab("}")
507            includedBinders.filter{it.isUsed()}.forEach { binder ->
508                tab("${binder.fieldName}.invalidateAll();")
509            }
510            tab("requestRebind();");
511        }
512        nl("}")
513    }
514
515    fun declareHasPendingBindings()  = kcode("") {
516        nl("@Override")
517        nl("public boolean hasPendingBindings() {") {
518            if (mDirtyFlags.buckets.size() > 0) {
519                tab("synchronized(this) {") {
520                    val flagCheck = 0.rangeTo(mDirtyFlags.buckets.size() - 1).map {
521                            "${mDirtyFlags.localValue(it)} != 0"
522                    }.joinToString(" || ")
523                    tab("if (${flagCheck}) {") {
524                        tab("return true;")
525                    }
526                    tab("}")
527                }
528                tab("}")
529            }
530            includedBinders.filter{it.isUsed()}.forEach { binder ->
531                tab("if (${binder.fieldName}.hasPendingBindings()) {") {
532                    tab("return true;")
533                }
534                tab("}")
535            }
536            tab("return false;")
537        }
538        nl("}")
539    }
540
541    fun declareSetVariable() = kcode("") {
542        nl("public boolean setVariable(int variableId, Object variable) {") {
543            tab("switch(variableId) {") {
544                usedVariables.forEach {
545                    tab ("case ${it.getName().br()} :") {
546                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
547                        tab("return true;")
548                    }
549                }
550                val declaredOnly = variables.filter { !it.isUsed() && it.isDeclared() };
551                declaredOnly.forEachIndexed { i, identifierExpr ->
552                    tab ("case ${identifierExpr.getName().br()} :") {
553                        if (i == declaredOnly.size() - 1) {
554                            tab("return true;")
555                        }
556                    }
557                }
558            }
559            tab("}")
560            tab("return false;")
561        }
562        nl("}")
563    }
564
565    fun declareLog() = kcode("") {
566        nl("private void log(String msg, long i) {") {
567            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
568        }
569        nl("}")
570    }
571
572    fun variableSettersAndGetters() = kcode("") {
573        variables.filterNot{it.isUsed()}.forEach {
574            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
575                tab("// not used, ignore")
576            }
577            nl("}")
578            nl("")
579            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
580                tab("return ${it.getDefaultValue()};")
581            }
582            nl("}")
583        }
584        usedVariables.forEach {
585            if (it.getUserDefinedType() != null) {
586                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
587                    if (it.isObservable()) {
588                        tab("updateRegistration(${it.getId()}, ${it.readableName});");
589                    }
590                    tab("this.${it.fieldName} = ${it.readableName};")
591                    // set dirty flags!
592                    val flagSet = it.invalidateFlagSet
593                    tab("synchronized(this) {") {
594                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
595                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
596                        }
597                    } tab ("}")
598                    tab("super.requestRebind();")
599                }
600                nl("}")
601                nl("")
602                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
603                    tab("return ${it.fieldName};")
604                }
605                nl("}")
606            }
607        }
608    }
609
610    fun onFieldChange() = kcode("") {
611        nl("@Override")
612        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
613            tab("switch (localFieldId) {") {
614                model.getObservables().forEach {
615                    tab("case ${it.getId()} :") {
616                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
617                    }
618                }
619            }
620            tab("}")
621            tab("return false;")
622        }
623        nl("}")
624        nl("")
625
626        model.getObservables().forEach {
627            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
628                tab("switch (fieldId) {", {
629                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
630                    accessedFields.filter { it.hasBindableAnnotations() }
631                            .groupBy { it.getName() }
632                            .forEach {
633                                tab("case ${it.key.br()}:") {
634                                    val field = it.value.first()
635                                    tab("synchronized(this) {") {
636                                        mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
637                                            tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
638                                        }
639                                    } tab("}")
640                                    tab("return true;")
641                                }
642
643                            }
644                    tab("case ${"".br()}:") {
645                        val flagSet = it.invalidateFlagSet
646                        tab("synchronized(this) {") {
647                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
648                                tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
649                            }
650                        } tab("}")
651                        tab("return true;")
652                    }
653
654                })
655                tab("}")
656                tab("return false;")
657            }
658            nl("}")
659            nl("")
660        }
661    }
662
663    fun declareViews() = kcode("// views") {
664        val oneLayout = !layoutBinder.hasVariations();
665        layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
666            val access : String
667            if (oneLayout && it.getId() != null) {
668                access = "public"
669            } else {
670                access = "private"
671            }
672            nl("${access} final ${it.interfaceType} ${it.fieldName};")
673        }
674    }
675
676    fun declareVariables() = kcode("// variables") {
677        usedVariables.forEach {
678            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
679        }
680    }
681
682    fun declareBoundValues() = kcode("// values") {
683        layoutBinder.getSortedTargets().filter { it.isUsed() }
684                .flatMap { it.getBindings() }
685                .filter { it.requiresOldValue() }
686                .flatMap{ it.getComponentExpressions().toArrayList() }
687                .groupBy { it }
688                .forEach {
689                    val expr = it.getKey()
690                    nl("private ${expr.getResolvedType().toJavaCode()} ${expr.oldValueName};")
691                }
692    }
693
694    fun declareListeners() = kcode("// listeners") {
695        model.getExprMap().values().filter {
696            it is ListenerExpr
697        }.groupBy { it }.forEach {
698            val expr = it.key as ListenerExpr
699            nl("private ${expr.listenerClassName} ${expr.fieldName};")
700        }
701    }
702
703    fun declareDirtyFlags() = kcode("// dirty flag") {
704        model.ext.localizedFlags.forEach { flag ->
705            flag.notEmpty { suffix, value ->
706                nl("private")
707                app(" ", if(flag.isDynamic()) null else "static final");
708                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
709            }
710        }
711    }
712
713    fun flagMapping() = kcode("/* flag mapping") {
714        if (model.getFlagMapping() != null) {
715            val mapping = model.getFlagMapping()
716            for (i in mapping.indices) {
717                tab("flag $i: ${mapping[i]}")
718            }
719        }
720        nl("flag mapping end*/")
721    }
722
723    fun executePendingBindings() = kcode("") {
724        nl("@Override")
725        nl("protected void executeBindings() {") {
726            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
727            tmpDirtyFlags.setLocalName("dirtyFlags");
728            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
729                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
730            }
731            tab("synchronized(this) {") {
732                for (i in (0..mDirtyFlags.buckets.size() - 1)) {
733                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
734                    tab("${mDirtyFlags.localValue(i)} = 0;")
735                }
736            } tab("}")
737            model.getPendingExpressions().filterNot { !it.canBeEvaluatedToAVariable() || (it.isVariable() && !it.isUsed()) }.forEach {
738                tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.getDefaultValue()};")
739            }
740            L.d("writing executePendingBindings for %s", className)
741            do {
742                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
743                val justRead = arrayListOf<Expr>()
744                L.d("batch: %s", batch)
745                while (!batch.none()) {
746                    val readNow = batch.filter { it.shouldReadNow(justRead) }
747                    if (readNow.isEmpty()) {
748                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
749                    }
750                    L.d("new read now. batch size: %d, readNow size: %d", batch.size(), readNow.size())
751                    nl(readWithDependants(readNow, justRead, batch, tmpDirtyFlags))
752                    batch.removeAll(justRead)
753                }
754                tab("// batch finished")
755            } while (model.markBitsRead())
756            // verify everything is read.
757            val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
758            if (batch.isNotEmpty()) {
759                L.e("could not generate code for %s. This might be caused by circular dependencies."
760                        + "Please report on b.android.com. %d %s %s", layoutBinder.getLayoutname(),
761                        batch.size(), batch.get(0), batch.get(0).toCode().generate())
762            }
763            //
764            layoutBinder.getSortedTargets().filter { it.isUsed() }
765                    .flatMap { it.getBindings() }
766                    .groupBy {
767                        "${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
768                            "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
769                        }.joinToString(" || ") }"
770                    }.forEach {
771                tab("if (${it.key}) {") {
772                    it.value.groupBy { Math.max(1, it.getMinApi()) }.forEach {
773                        val setterValues = kcode("") {
774                            it.value.forEach { binding ->
775                                val fieldName: String
776                                if (binding.getTarget().getViewClass().
777                                        equals(binding.getTarget().getInterfaceType())) {
778                                    fieldName = "this.${binding.getTarget().fieldName}"
779                                } else {
780                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
781                                }
782                                tab(binding.toJavaCode(fieldName, "this.mBindingComponent")).app(";")
783                            }
784                        }
785                        tab("// api target ${it.key}")
786                        if (it.key > 1) {
787                            tab("if(getBuildSdkInt() >= ${it.key}) {") {
788                                app("", setterValues)
789                            }
790                            tab("}")
791                        } else {
792                            app("", setterValues)
793                        }
794                    }
795                }
796                tab("}")
797            }
798
799
800            layoutBinder.getSortedTargets().filter { it.isUsed() }
801                    .flatMap { it.getBindings() }
802                    .filter { it.requiresOldValue() }
803                    .groupBy {"${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
804                        "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
805                    }.joinToString(" || ")
806                    }"}.forEach {
807                tab("if (${it.key}) {") {
808                    it.value.groupBy { it.getExpr() }.map { it.value.first() }.forEach {
809                        it.getComponentExpressions().forEach { expr ->
810                            tab("this.${expr.oldValueName} = ${expr.toCode().generate()};")
811                        }
812                    }
813                }
814                tab("}")
815            }
816            includedBinders.filter{it.isUsed()}.forEach { binder ->
817                tab("${binder.fieldName}.executePendingBindings();")
818            }
819            layoutBinder.getSortedTargets().filter{
820                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
821            }.forEach {
822                tab("if (${it.fieldName}.getBinding() != null) {") {
823                    tab("${it.fieldName}.getBinding().executePendingBindings();")
824                }
825                tab("}")
826            }
827        }
828        nl("}")
829    }
830
831    fun readWithDependants(expressionList: List<Expr>, justRead: MutableList<Expr>,
832            batch: MutableList<Expr>, tmpDirtyFlags: FlagSet,
833            inheritedFlags: FlagSet? = null) : KCode = kcode("") {
834        expressionList.groupBy { it.shouldReadFlagSet }.forEach {
835            val flagSet = it.key
836            val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
837            val expressions = it.value
838            val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
839                "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
840            }.joinToString(" || ")
841            })"
842            val readCode = kcode("") {
843                val dependants = ArrayList<Expr>()
844                expressions.groupBy { condition(it) }.forEach {
845                    val condition = it.key
846                    val assignedValues = it.value.filter {
847                        it.canBeEvaluatedToAVariable() && !it.isVariable()
848                    }
849                    if (!assignedValues.isEmpty()) {
850                        val assignment = kcode("") {
851                            assignedValues.forEach { expr: Expr ->
852                                tab("// read ${expr.getUniqueKey()}")
853                                tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";")
854                            }
855                        }
856                        if (condition != null) {
857                            tab("if (${condition}) {") {
858                                app("", assignment)
859                            }
860                            tab ("}")
861                        } else {
862                            app("", assignment)
863                        }
864                        it.value.filter { it.isObservable() }.forEach { expr: Expr ->
865                            tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
866                        }
867                    }
868
869                    it.value.forEach { expr: Expr ->
870                        justRead.add(expr)
871                        L.d("%s / readWithDependants %s", className, expr.getUniqueKey());
872                        L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
873
874                        // if I am the condition for an expression, set its flag
875                        expr.getDependants().filter {
876                            !it.isConditional() && it.getDependant() is TernaryExpr &&
877                                    (it.getDependant() as TernaryExpr).getPred() == expr
878                        }.map { it.getDependant() }.groupBy {
879                            // group by when those ternaries will be evaluated (e.g. don't set conditional flags for no reason)
880                            val ternaryBitSet = it.getShouldReadFlagsWithConditionals()
881                            val isBehindTernary = ternaryBitSet.nextSetBit(model.getInvalidateAnyFlagIndex()) == -1
882                            if (!isBehindTernary) {
883                                val ternaryFlags = it.shouldReadWithConditionalsFlagSet
884                                "if(${tmpDirtyFlags.mapOr(ternaryFlags){ suffix, index ->
885                                    "(${tmpDirtyFlags.localValue(index)} & ${ternaryFlags.localValue(index)}) != 0"
886                                }.joinToString(" || ")}) {"
887                            } else {
888                                // TODO if it is behind a ternary, we should set it when its predicate is elevated
889                                // Normally, this would mean that there is another code path to re-read our current expression.
890                                // Unfortunately, this may not be true due to the coverage detection in `expr#markAsReadIfDone`, this may never happen.
891                                // for v1.0, we'll go with always setting it and suffering an unnecessary calculation for this edge case.
892                                // we can solve this by listening to elevation events from the model.
893                                ""
894                            }
895                        }.forEach {
896                            val hasAnotherIf = it.key != ""
897                            if (hasAnotherIf) {
898                                tab(it.key) {
899                                    tab("if (${expr.executePendingLocalName}) {") {
900                                        it.value.forEach {
901                                            val set = it.getRequirementFlagSet(true)
902                                            mDirtyFlags.mapOr(set) { suffix, index ->
903                                                tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
904                                            }
905                                        }
906                                    }
907                                    tab("} else {") {
908                                        it.value.forEach {
909                                            val set = it.getRequirementFlagSet(false)
910                                            mDirtyFlags.mapOr(set) { suffix, index ->
911                                                tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
912                                            }
913                                        }
914                                    }.tab("}")
915                                }.app("}")
916                            } else {
917                                tab("if (${expr.executePendingLocalName}) {") {
918                                    it.value.forEach {
919                                        val set = it.getRequirementFlagSet(true)
920                                        mDirtyFlags.mapOr(set) { suffix, index ->
921                                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
922                                        }
923                                    }
924                                }
925                                tab("} else {") {
926                                    it.value.forEach {
927                                        val set = it.getRequirementFlagSet(false)
928                                        mDirtyFlags.mapOr(set) { suffix, index ->
929                                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
930                                        }
931                                    }
932                                } app("}")
933                            }
934                        }
935                        val chosen = expr.getDependants().filter {
936                            val dependant = it.getDependant()
937                            batch.contains(dependant) &&
938                                    dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
939                                    dependant.shouldReadNow(justRead)
940                        }
941                        if (chosen.isNotEmpty()) {
942                            dependants.addAll(chosen.map { it.getDependant() })
943                        }
944                    }
945                }
946                if (dependants.isNotEmpty()) {
947                    val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
948                    nl(readWithDependants(dependants, justRead, batch, tmpDirtyFlags, nextInheritedFlags))
949                }
950            }
951
952            if (needsIfWrapper) {
953                tab(ifClause) {
954                    app(" {")
955                    app("", readCode)
956                }
957                tab("}")
958            } else {
959                app("", readCode)
960            }
961        }
962    }
963
964    fun condition(expr : Expr) : String? {
965        if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
966            // create an if case for all dependencies that might be null
967            val nullables = expr.getDependencies().filter {
968                it.isMandatory() && it.getOther().getResolvedType().isNullable()
969            }.map { it.getOther() }
970            if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
971                return "${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}"
972            } else {
973                return null
974            }
975        } else {
976            return null
977        }
978    }
979
980    fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
981        model.getExprMap().values().filter {
982            it.isUsed() && it is ListenerExpr
983        }.groupBy { it }.forEach {
984            val expr = it.key as ListenerExpr
985            val listenerType = expr.getResolvedType();
986            val extendsImplements : String
987            if (listenerType.isInterface()) {
988                extendsImplements = "implements"
989            } else {
990                extendsImplements = "extends"
991            }
992            nl("public static class ${expr.listenerClassName} ${extendsImplements} ${listenerType.getCanonicalName()}{") {
993                if (expr.getChild().isDynamic()) {
994                    tab("private ${expr.getChild().getResolvedType().toJavaCode()} value;")
995                    tab("public ${expr.listenerClassName} setValue(${expr.getChild().getResolvedType().toJavaCode()} value) {") {
996                        tab("this.value = value;")
997                        tab("return value == null ? null : this;")
998                    }
999                    tab("}")
1000                }
1001                val listenerMethod = expr.getMethod()
1002                val parameterTypes = listenerMethod.getParameterTypes()
1003                val returnType = listenerMethod.getReturnType(parameterTypes.toArrayList())
1004                tab("@Override")
1005                tab("public ${returnType} ${listenerMethod.getName()}(${
1006                    parameterTypes.withIndex().map {
1007                        "${it.value.toJavaCode()} arg${it.index}"
1008                    }.joinToString(", ")
1009                }) {") {
1010                    val obj : String
1011                    if (expr.getChild().isDynamic()) {
1012                        obj = "this.value"
1013                    } else {
1014                        obj = expr.getChild().toCode().generate();
1015                    }
1016                    val returnStr : String
1017                    if (!returnType.isVoid()) {
1018                        returnStr = "return "
1019                    } else {
1020                        returnStr = ""
1021                    }
1022                    val args = parameterTypes.withIndex().map {
1023                        "arg${it.index}"
1024                    }.joinToString(", ")
1025                    tab("${returnStr}${obj}.${expr.getName()}(${args});")
1026                }
1027                tab("}")
1028            }
1029            nl("}")
1030        }
1031    }
1032
1033    fun declareFactories() = kcode("") {
1034        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1035            tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1036        }
1037        nl("}")
1038        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1039            tab("return android.databinding.DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
1040        }
1041        nl("}")
1042        if (!layoutBinder.isMerge()) {
1043            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
1044                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1045            }
1046            nl("}")
1047            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1048                tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false), bindingComponent);")
1049            }
1050            nl("}")
1051            nl("public static ${baseClassName} bind(android.view.View view) {") {
1052                tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1053            }
1054            nl("}")
1055            nl("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1056                tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
1057                    tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
1058                }
1059                tab("}")
1060                tab("return new ${baseClassName}(bindingComponent, view);")
1061            }
1062            nl("}")
1063        }
1064    }
1065
1066    /**
1067     * When called for a library compilation, we do not generate real implementations
1068     */
1069    public fun writeBaseClass(forLibrary : Boolean) : String =
1070        kcode("package ${layoutBinder.getPackage()};") {
1071            nl("import android.databinding.Bindable;")
1072            nl("import android.databinding.DataBindingUtil;")
1073            nl("import android.databinding.ViewDataBinding;")
1074            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
1075            layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1076                tab("public final ${it.interfaceType} ${it.fieldName};")
1077            }
1078            nl("")
1079            tab("protected ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
1080                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1081                    tab(", ${it.interfaceType} ${it.constructorParamName}")
1082                }
1083            }
1084            tab(") {") {
1085                tab("super(bindingComponent, root_, localFieldCount);")
1086                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1087                    tab("this.${it.fieldName} = ${it.constructorParamName};")
1088                }
1089            }
1090            tab("}")
1091            nl("")
1092            variables.forEach {
1093                if (it.getUserDefinedType() != null) {
1094                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
1095                    tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
1096                }
1097            }
1098            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1099                tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1100            }
1101            tab("}")
1102            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
1103                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1104            }
1105            tab("}")
1106            tab("public static ${baseClassName} bind(android.view.View view) {") {
1107                if (forLibrary) {
1108                    tab("return null;")
1109                } else {
1110                    tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1111                }
1112            }
1113            tab("}")
1114            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1115                if (forLibrary) {
1116                    tab("return null;")
1117                } else {
1118                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
1119                }
1120            }
1121            tab("}")
1122            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1123                if (forLibrary) {
1124                    tab("return null;")
1125                } else {
1126                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false, bindingComponent);")
1127                }
1128            }
1129            tab("}")
1130            tab("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1131                if (forLibrary) {
1132                    tab("return null;")
1133                } else {
1134                    tab("return (${baseClassName})bind(bindingComponent, view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
1135                }
1136            }
1137            tab("}")
1138            nl("}")
1139        }.generate()
1140}
1141