LayoutBinderWriter.kt revision 0b6d118e6eeb3bc100fc6a6e66016ab812cb2783
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.expr.ResourceExpr
25import android.databinding.tool.ext.androidId
26import android.databinding.tool.ext.joinToCamelCaseAsVar
27import android.databinding.tool.ext.lazyProp
28import android.databinding.tool.ext.versionedLazy
29import android.databinding.tool.ext.br
30import android.databinding.tool.processing.ErrorMessages
31import android.databinding.tool.reflection.ModelAnalyzer
32import android.databinding.tool.util.L
33import java.util.ArrayList
34import java.util.Arrays
35import java.util.BitSet
36import java.util.HashMap
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.localName = 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 lazyProp { 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.getConstructorParamName(base : String) : String = ext.getUniqueName(base, Scope.CONSTRUCTOR_PARAM, false)
85
86fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
87
88val Expr.needsLocalField by lazyProp { expr : Expr ->
89    expr.canBeEvaluatedToAVariable() && !(expr.isVariable() && !expr.isUsed) && (expr.isDynamic || expr is ResourceExpr)
90}
91
92
93// not necessarily unique. Uniqueness is solved per scope
94val BindingTarget.readableName by lazyProp { target: BindingTarget ->
95    if (target.id == null) {
96        "boundView" + indexFromTag(target.tag)
97    } else {
98        target.id.androidId().stripNonJava()
99    }
100}
101
102fun BindingTarget.superConversion(variable : String) : String {
103    if (resolvedType != null && resolvedType.extendsViewStub()) {
104        return "new android.databinding.ViewStubProxy((android.view.ViewStub) $variable)"
105    } else {
106        return "($interfaceClass) $variable"
107    }
108}
109
110val BindingTarget.fieldName : String by lazyProp { target : BindingTarget ->
111    val name : String
112    val isPublic : kotlin.Boolean
113    if (target.id == null) {
114        name = "m${target.readableName}"
115        isPublic = false
116    } else {
117        name = target.readableName
118        isPublic = true
119    }
120    target.model.getUniqueFieldName(name, isPublic)
121}
122
123val BindingTarget.androidId by lazyProp { target : BindingTarget ->
124    if (target.id.startsWith("@android:id/")) {
125        "android.R.id.${target.id.androidId()}"
126    } else {
127        "R.id.${target.id.androidId()}"
128    }
129}
130
131val BindingTarget.interfaceClass by lazyProp { target : BindingTarget ->
132    if (target.resolvedType != null && target.resolvedType.extendsViewStub()) {
133        "android.databinding.ViewStubProxy"
134    } else {
135        target.interfaceType
136    }
137}
138
139val BindingTarget.constructorParamName by lazyProp { target : BindingTarget ->
140    target.model.getConstructorParamName(target.readableName)
141}
142
143// not necessarily unique. Uniqueness is decided per scope
144val Expr.readableName by lazyProp { expr : Expr ->
145    val stripped = "${expr.uniqueKey.stripNonJava()}"
146    L.d("readableUniqueName for [%s] %s is %s", System.identityHashCode(expr), expr.uniqueKey, stripped)
147    stripped
148}
149
150val Expr.fieldName by lazyProp { expr : Expr ->
151    expr.model.getUniqueFieldName("m${expr.readableName.capitalize()}", false)
152}
153
154val Expr.listenerClassName by lazyProp { expr : Expr ->
155    expr.model.getUniqueFieldName("${expr.resolvedType.simpleName}Impl", false)
156}
157
158val Expr.oldValueName by lazyProp { expr : Expr ->
159    expr.model.getUniqueFieldName("mOld${expr.readableName.capitalize()}", false)
160}
161
162val Expr.executePendingLocalName by lazyProp { expr : Expr ->
163    if(expr.needsLocalField) "${expr.model.ext.getUniqueName(expr.readableName, Scope.EXECUTE_PENDING_METHOD, false)}"
164    else expr.toCode().generate()
165}
166
167val Expr.setterName by lazyProp { expr : Expr ->
168    expr.model.getUniqueMethodName("set${expr.readableName.capitalize()}", true)
169}
170
171val Expr.onChangeName by lazyProp { expr : Expr ->
172    expr.model.getUniqueMethodName("onChange${expr.readableName.capitalize()}", false)
173}
174
175val Expr.getterName by lazyProp { expr : Expr ->
176    expr.model.getUniqueMethodName("get${expr.readableName.capitalize()}", true)
177}
178
179fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic
180
181val Expr.dirtyFlagSet by lazyProp { expr : Expr ->
182    FlagSet(expr.invalidFlags, expr.model.flagBucketCount)
183}
184
185val Expr.invalidateFlagSet by lazyProp { expr : Expr ->
186    FlagSet(expr.id)
187}
188
189val Expr.shouldReadFlagSet by versionedLazy { expr : Expr ->
190    FlagSet(expr.shouldReadFlags, expr.model.flagBucketCount)
191}
192
193val Expr.shouldReadWithConditionalsFlagSet by versionedLazy { expr : Expr ->
194    FlagSet(expr.shouldReadFlagsWithConditionals, expr.model.flagBucketCount)
195}
196
197val Expr.conditionalFlags by lazyProp { expr : Expr ->
198    arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
199            FlagSet(expr.getRequirementFlagIndex(true)))
200}
201
202val LayoutBinder.requiredComponent by lazyProp { layoutBinder: LayoutBinder ->
203    val required = layoutBinder.
204            bindingTargets.
205            flatMap { it.bindings }.
206            firstOrNull { it.bindingAdapterInstanceClass != null }
207    required?.bindingAdapterInstanceClass
208}
209
210fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
211
212fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
213    buckets.withIndex().forEach {
214        if (it.value != 0L) {
215            cb(getWordSuffix(it.index), buckets[it.index])
216        }
217    }
218}
219
220fun getWordSuffix(wordIndex : Int) : String {
221    return if(wordIndex == 0) "" else "_$wordIndex"
222}
223
224fun FlagSet.localValue(bucketIndex : Int) =
225        if (localName == null) binaryCode(bucketIndex)
226        else "$localName${getWordSuffix(bucketIndex)}"
227
228fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
229
230
231fun longToBinary(l : Long) = "0x${java.lang.Long.toHexString(l)}L"
232
233fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
234    val min = Math.min(buckets.size, other.buckets.size)
235    val result = arrayListOf<T>()
236    for (i in 0..(min - 1)) {
237        // if these two can match by any chance, call the callback
238        if (intersect(other, i)) {
239            result.add(cb(getWordSuffix(i), i))
240        }
241    }
242    return result
243}
244
245fun indexFromTag(tag : String) : kotlin.Int {
246    val startIndex : kotlin.Int
247    if (tag.startsWith("binding_")) {
248        startIndex = "binding_".length;
249    } else {
250        startIndex = tag.lastIndexOf('_') + 1
251    }
252    return Integer.parseInt(tag.substring(startIndex))
253}
254
255class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
256    val model = layoutBinder.model
257    val indices = HashMap<BindingTarget, kotlin.Int>()
258    val mDirtyFlags by lazy {
259        val fs = FlagSet(BitSet(), model.flagBucketCount);
260        Arrays.fill(fs.buckets, -1)
261        fs.isDynamic = true
262        model.localizeFlag(fs, "mDirtyFlags")
263        fs
264    }
265
266    val className = layoutBinder.implementationName
267
268    val baseClassName = "${layoutBinder.className}"
269
270    val includedBinders by lazy {
271        layoutBinder.bindingTargets.filter { it.isBinder }
272    }
273
274    val variables by lazy {
275        model.exprMap.values.filterIsInstance(IdentifierExpr::class.java).filter { it.isVariable() }
276    }
277
278    val usedVariables by 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.`package`};") {
286            nl("import ${layoutBinder.modulePackage}.R;")
287            nl("import ${layoutBinder.modulePackage}.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(declareSetVariable())
305                tab(variableSettersAndGetters())
306                tab(onFieldChange())
307
308                tab(executePendingBindings())
309
310                tab(declareListenerImpls())
311                tab(declareDirtyFlags())
312                if (!layoutBinder.hasVariations()) {
313                    tab(declareFactories())
314                }
315            }
316            nl("}")
317            tab(flagMapping())
318            tab("//end")
319        }.generate()
320    }
321    fun calculateIndices() : Unit {
322        val taggedViews = layoutBinder.bindingTargets.filter{
323            it.isUsed && it.tag != null && !it.isBinder
324        }
325        taggedViews.forEach {
326            indices.put(it, indexFromTag(it.tag))
327        }
328        val indexStart = maxIndex() + 1
329        layoutBinder.bindingTargets.filter{
330            it.isUsed && !taggedViews.contains(it)
331        }.withIndex().forEach {
332            indices.put(it.value, it.index + indexStart)
333        }
334    }
335    fun declareIncludeViews() = kcode("") {
336        nl("private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;")
337        nl("private static final android.util.SparseIntArray sViewsWithIds;")
338        nl("static {") {
339            val hasBinders = layoutBinder.bindingTargets.firstOrNull{ it.isUsed && it.isBinder } != null
340            if (!hasBinders) {
341                tab("sIncludes = null;")
342            } else {
343                val numBindings = layoutBinder.bindingTargets.filter{ it.isUsed }.count()
344                tab("sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts($numBindings);")
345                val includeMap = HashMap<BindingTarget, ArrayList<BindingTarget>>()
346                layoutBinder.bindingTargets.filter{ it.isUsed && it.isBinder }.forEach {
347                    val includeTag = it.tag;
348                    val parent = layoutBinder.bindingTargets.firstOrNull {
349                        it.isUsed && !it.isBinder && includeTag.equals(it.tag)
350                    } ?: throw IllegalStateException("Could not find parent of include file")
351                    var list = includeMap[parent]
352                    if (list == null) {
353                        list = ArrayList<BindingTarget>()
354                        includeMap.put(parent, list)
355                    }
356                    list.add(it)
357                }
358
359                includeMap.keys.forEach {
360                    val index = indices[it]
361                    tab("sIncludes.setIncludes($index, ") {
362                        tab ("new String[] {${
363                        includeMap[it]!!.map {
364                            "\"${it.includedLayout}\""
365                        }.joinToString(", ")
366                        }},")
367                        tab("new int[] {${
368                        includeMap[it]!!.map {
369                            "${indices[it]}"
370                        }.joinToString(", ")
371                        }},")
372                        tab("new int[] {${
373                        includeMap[it]!!.map {
374                            "R.layout.${it.includedLayout}"
375                        }.joinToString(", ")
376                        }});")
377                    }
378                }
379            }
380            val viewsWithIds = layoutBinder.bindingTargets.filter {
381                it.isUsed && !it.isBinder && (!it.supportsTag() || (it.id != null && it.tag == null))
382            }
383            if (viewsWithIds.isEmpty()) {
384                tab("sViewsWithIds = null;")
385            } else {
386                tab("sViewsWithIds = new android.util.SparseIntArray();")
387                viewsWithIds.forEach {
388                    tab("sViewsWithIds.put(${it.androidId}, ${indices[it]});")
389                }
390            }
391        }
392        nl("}")
393    }
394
395    fun maxIndex() : kotlin.Int {
396        val maxIndex = indices.values.max()
397        if (maxIndex == null) {
398            return -1
399        } else {
400            return maxIndex
401        }
402    }
403
404    fun declareConstructor(minSdk : kotlin.Int) = kcode("") {
405        val bindingCount = maxIndex() + 1
406        val parameterType : String
407        val superParam : String
408        if (layoutBinder.isMerge) {
409            parameterType = "View[]"
410            superParam = "root[0]"
411        } else {
412            parameterType = "View"
413            superParam = "root"
414        }
415        val rootTagsSupported = minSdk >= 14
416        if (layoutBinder.hasVariations()) {
417            nl("")
418            nl("public $className(android.databinding.DataBindingComponent bindingComponent, $parameterType root) {") {
419                tab("this(bindingComponent, $superParam, mapBindings(bindingComponent, root, $bindingCount, sIncludes, sViewsWithIds));")
420            }
421            nl("}")
422            nl("private $className(android.databinding.DataBindingComponent bindingComponent, $parameterType root, Object[] bindings) {") {
423                tab("super(bindingComponent, $superParam, ${model.observables.size}") {
424                    layoutBinder.sortedTargets.filter { it.id != null }.forEach {
425                        tab(", ${fieldConversion(it)}")
426                    }
427                    tab(");")
428                }
429            }
430        } else {
431            nl("public $baseClassName(android.databinding.DataBindingComponent bindingComponent, $parameterType root) {") {
432                tab("super(bindingComponent, $superParam, ${model.observables.size});")
433                tab("final Object[] bindings = mapBindings(bindingComponent, root, $bindingCount, sIncludes, sViewsWithIds);")
434            }
435        }
436        if (layoutBinder.requiredComponent != null) {
437            tab("ensureBindingComponentIsNotNull(${layoutBinder.requiredComponent}.class);")
438        }
439        val taggedViews = layoutBinder.sortedTargets.filter{it.isUsed }
440        taggedViews.forEach {
441            if (!layoutBinder.hasVariations() || it.id == null) {
442                tab("this.${it.fieldName} = ${fieldConversion(it)};")
443            }
444            if (!it.isBinder) {
445                if (it.resolvedType != null && it.resolvedType.extendsViewStub()) {
446                    tab("this.${it.fieldName}.setContainingBinding(this);")
447                }
448                if (it.supportsTag() && it.tag != null &&
449                        (rootTagsSupported || it.tag.startsWith("binding_"))) {
450                    val originalTag = it.originalTag;
451                    var tagValue = "null"
452                    if (originalTag != null && !originalTag.startsWith("@{")) {
453                        tagValue = "\"$originalTag\""
454                        if (originalTag.startsWith("@")) {
455                            var packageName = layoutBinder.modulePackage
456                            if (originalTag.startsWith("@android:")) {
457                                packageName = "android"
458                            }
459                            val slashIndex = originalTag.indexOf('/')
460                            val resourceId = originalTag.substring(slashIndex + 1)
461                            tagValue = "root.getResources().getString($packageName.R.string.$resourceId)"
462                        }
463                    }
464                    tab("this.${it.fieldName}.setTag($tagValue);")
465                } else if (it.tag != null && !it.tag.startsWith("binding_") &&
466                    it.originalTag != null) {
467                    L.e(ErrorMessages.ROOT_TAG_NOT_SUPPORTED, it.originalTag)
468                }
469            }
470        }
471        tab("setRootTag(root);")
472        tab("invalidateAll();");
473        nl("}")
474    }
475
476    fun fieldConversion(target : BindingTarget) : String {
477        if (!target.isUsed) {
478            return "null"
479        } else {
480            val index = indices[target] ?: throw IllegalStateException("Unknown binding target")
481            val variableName = "bindings[$index]"
482            return target.superConversion(variableName)
483        }
484    }
485
486    fun declareInvalidateAll() = kcode("") {
487        nl("@Override")
488        nl("public void invalidateAll() {") {
489            val fs = FlagSet(layoutBinder.model.invalidateAnyBitSet,
490                    layoutBinder.model.flagBucketCount);
491            tab("synchronized(this) {") {
492                for (i in (0..(mDirtyFlags.buckets.size - 1))) {
493                    tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
494                }
495            } tab("}")
496            includedBinders.filter{it.isUsed }.forEach { binder ->
497                tab("${binder.fieldName}.invalidateAll();")
498            }
499            tab("requestRebind();");
500        }
501        nl("}")
502    }
503
504    fun declareHasPendingBindings()  = kcode("") {
505        nl("@Override")
506        nl("public boolean hasPendingBindings() {") {
507            if (mDirtyFlags.buckets.size > 0) {
508                tab("synchronized(this) {") {
509                    val flagCheck = 0.rangeTo(mDirtyFlags.buckets.size - 1).map {
510                            "${mDirtyFlags.localValue(it)} != 0"
511                    }.joinToString(" || ")
512                    tab("if ($flagCheck) {") {
513                        tab("return true;")
514                    }
515                    tab("}")
516                }
517                tab("}")
518            }
519            includedBinders.filter{it.isUsed }.forEach { binder ->
520                tab("if (${binder.fieldName}.hasPendingBindings()) {") {
521                    tab("return true;")
522                }
523                tab("}")
524            }
525            tab("return false;")
526        }
527        nl("}")
528    }
529
530    fun declareSetVariable() = kcode("") {
531        nl("public boolean setVariable(int variableId, Object variable) {") {
532            tab("switch(variableId) {") {
533                usedVariables.forEach {
534                    tab ("case ${it.name.br()} :") {
535                        tab("${it.setterName}((${it.resolvedType.toJavaCode()}) variable);")
536                        tab("return true;")
537                    }
538                }
539                val declaredOnly = variables.filter { !it.isUsed && it.isDeclared };
540                declaredOnly.forEachIndexed { i, identifierExpr ->
541                    tab ("case ${identifierExpr.name.br()} :") {
542                        if (i == declaredOnly.size - 1) {
543                            tab("return true;")
544                        }
545                    }
546                }
547            }
548            tab("}")
549            tab("return false;")
550        }
551        nl("}")
552    }
553
554    fun variableSettersAndGetters() = kcode("") {
555        variables.filterNot{it.isUsed }.forEach {
556            nl("public void ${it.setterName}(${it.resolvedType.toJavaCode()} ${it.readableName}) {") {
557                tab("// not used, ignore")
558            }
559            nl("}")
560            nl("")
561            nl("public ${it.resolvedType.toJavaCode()} ${it.getterName}() {") {
562                tab("return ${it.defaultValue};")
563            }
564            nl("}")
565        }
566        usedVariables.forEach {
567            if (it.userDefinedType != null) {
568                nl("public void ${it.setterName}(${it.resolvedType.toJavaCode()} ${it.readableName}) {") {
569                    if (it.isObservable) {
570                        tab("updateRegistration(${it.id}, ${it.readableName});");
571                    }
572                    tab("this.${it.fieldName} = ${it.readableName};")
573                    // set dirty flags!
574                    val flagSet = it.invalidateFlagSet
575                    tab("synchronized(this) {") {
576                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
577                            tab("${mDirtyFlags.localName}$suffix |= ${flagSet.localValue(index)};")
578                        }
579                    } tab ("}")
580                    tab("super.requestRebind();")
581                }
582                nl("}")
583                nl("")
584                nl("public ${it.resolvedType.toJavaCode()} ${it.getterName}() {") {
585                    tab("return ${it.fieldName};")
586                }
587                nl("}")
588            }
589        }
590    }
591
592    fun onFieldChange() = kcode("") {
593        nl("@Override")
594        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
595            tab("switch (localFieldId) {") {
596                model.observables.forEach {
597                    tab("case ${it.id} :") {
598                        tab("return ${it.onChangeName}((${it.resolvedType.toJavaCode()}) object, fieldId);")
599                    }
600                }
601            }
602            tab("}")
603            tab("return false;")
604        }
605        nl("}")
606        nl("")
607
608        model.observables.forEach {
609            nl("private boolean ${it.onChangeName}(${it.resolvedType.toJavaCode()} ${it.readableName}, int fieldId) {") {
610                tab("switch (fieldId) {", {
611                    val accessedFields: List<FieldAccessExpr> = it.parents.filterIsInstance(FieldAccessExpr::class.java)
612                    accessedFields.filter { it.hasBindableAnnotations() }
613                            .groupBy { it.brName }
614                            .forEach {
615                                // If two expressions look different but resolve to the same method,
616                                // we are not yet able to merge them. This is why we merge their
617                                // flags below.
618                                tab("case ${it.key}:") {
619                                    tab("synchronized(this) {") {
620                                        val flagSet = it.value.foldRight(FlagSet()) { l, r -> l.invalidateFlagSet.or(r) }
621
622                                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
623                                            tab("${mDirtyFlags.localValue(index)} |= ${flagSet.localValue(index)};")
624                                        }
625                                    } tab("}")
626                                    tab("return true;")
627                                }
628
629                            }
630                    tab("case ${"".br()}:") {
631                        val flagSet = it.invalidateFlagSet
632                        tab("synchronized(this) {") {
633                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
634                                tab("${mDirtyFlags.localName}$suffix |= ${flagSet.localValue(index)};")
635                            }
636                        } tab("}")
637                        tab("return true;")
638                    }
639
640                })
641                tab("}")
642                tab("return false;")
643            }
644            nl("}")
645            nl("")
646        }
647    }
648
649    fun declareViews() = kcode("// views") {
650        val oneLayout = !layoutBinder.hasVariations();
651        layoutBinder.sortedTargets.filter {it.isUsed && (oneLayout || it.id == null)}.forEach {
652            val access : String
653            if (oneLayout && it.id != null) {
654                access = "public"
655            } else {
656                access = "private"
657            }
658            nl("$access final ${it.interfaceClass} ${it.fieldName};")
659        }
660    }
661
662    fun declareVariables() = kcode("// variables") {
663        usedVariables.forEach {
664            nl("private ${it.resolvedType.toJavaCode()} ${it.fieldName};")
665        }
666    }
667
668    fun declareBoundValues() = kcode("// values") {
669        layoutBinder.sortedTargets.filter { it.isUsed }
670                .flatMap { it.bindings }
671                .filter { it.requiresOldValue() }
672                .flatMap{ it.componentExpressions.toArrayList() }
673                .groupBy { it }
674                .forEach {
675                    val expr = it.key
676                    nl("private ${expr.resolvedType.toJavaCode()} ${expr.oldValueName};")
677                }
678    }
679
680    fun declareListeners() = kcode("// listeners") {
681        model.exprMap.values.filter {
682            it is ListenerExpr
683        }.groupBy { it }.forEach {
684            val expr = it.key as ListenerExpr
685            nl("private ${expr.listenerClassName} ${expr.fieldName};")
686        }
687    }
688
689    fun declareDirtyFlags() = kcode("// dirty flag") {
690        model.ext.localizedFlags.forEach { flag ->
691            flag.notEmpty { suffix, value ->
692                nl("private")
693                app(" ", if(flag.isDynamic) null else "static final");
694                app(" ", " ${flag.type} ${flag.localName}$suffix = ${longToBinary(value)};")
695            }
696        }
697    }
698
699    fun flagMapping() = kcode("/* flag mapping") {
700        if (model.flagMapping != null) {
701            val mapping = model.flagMapping
702            for (i in mapping.indices) {
703                tab("flag $i: ${mapping[i]}")
704            }
705        }
706        nl("flag mapping end*/")
707    }
708
709    fun executePendingBindings() = kcode("") {
710        nl("@Override")
711        nl("protected void executeBindings() {") {
712            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
713            tmpDirtyFlags.localName = "dirtyFlags";
714            for (i in (0..mDirtyFlags.buckets.size - 1)) {
715                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
716            }
717            tab("synchronized(this) {") {
718                for (i in (0..mDirtyFlags.buckets.size - 1)) {
719                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
720                    tab("${mDirtyFlags.localValue(i)} = 0;")
721                }
722            } tab("}")
723            model.pendingExpressions.filter { it.needsLocalField }.forEach {
724                tab("${it.resolvedType.toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.defaultValue};")
725            }
726            L.d("writing executePendingBindings for %s", className)
727            do {
728                val batch = ExprModel.filterShouldRead(model.pendingExpressions).toArrayList()
729                val justRead = arrayListOf<Expr>()
730                L.d("batch: %s", batch)
731                while (!batch.none()) {
732                    val readNow = batch.filter { it.shouldReadNow(justRead) }
733                    if (readNow.isEmpty()) {
734                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
735                    }
736                    L.d("new read now. batch size: %d, readNow size: %d", batch.size, readNow.size)
737                    nl(readWithDependants(readNow, justRead, batch, tmpDirtyFlags))
738                    batch.removeAll(justRead)
739                }
740                tab("// batch finished")
741            } while (model.markBitsRead())
742            // verify everything is read.
743            val batch = ExprModel.filterShouldRead(model.pendingExpressions).toArrayList()
744            if (batch.isNotEmpty()) {
745                L.e("could not generate code for %s. This might be caused by circular dependencies."
746                        + "Please report on b.android.com. %d %s %s", layoutBinder.layoutname,
747                        batch.size, batch[0], batch[0].toCode().generate())
748            }
749            //
750            layoutBinder.sortedTargets.filter { it.isUsed }
751                    .flatMap { it.bindings }
752                    .groupBy {
753                        "${tmpDirtyFlags.mapOr(it.expr.dirtyFlagSet) { suffix, index ->
754                            "(${tmpDirtyFlags.localValue(index)} & ${it.expr.dirtyFlagSet.localValue(index)}) != 0"
755                        }.joinToString(" || ") }"
756                    }.forEach {
757                tab("if (${it.key}) {") {
758                    it.value.groupBy { Math.max(1, it.minApi) }.forEach {
759                        val setterValues = kcode("") {
760                            it.value.forEach { binding ->
761                                val fieldName: String
762                                if (binding.target.viewClass.
763                                        equals(binding.target.interfaceType)) {
764                                    fieldName = "this.${binding.target.fieldName}"
765                                } else {
766                                    fieldName = "((${binding.target.viewClass}) this.${binding.target.fieldName})"
767                                }
768                                tab(binding.toJavaCode(fieldName, "this.mBindingComponent")).app(";")
769                            }
770                        }
771                        tab("// api target ${it.key}")
772                        if (it.key > 1) {
773                            tab("if(getBuildSdkInt() >= ${it.key}) {") {
774                                app("", setterValues)
775                            }
776                            tab("}")
777                        } else {
778                            app("", setterValues)
779                        }
780                    }
781                }
782                tab("}")
783            }
784
785
786            layoutBinder.sortedTargets.filter { it.isUsed }
787                    .flatMap { it.bindings }
788                    .filter { it.requiresOldValue() }
789                    .groupBy {"${tmpDirtyFlags.mapOr(it.expr.dirtyFlagSet) { suffix, index ->
790                        "(${tmpDirtyFlags.localValue(index)} & ${it.expr.dirtyFlagSet.localValue(index)}) != 0"
791                    }.joinToString(" || ")
792                    }"}.forEach {
793                tab("if (${it.key}) {") {
794                    it.value.groupBy { it.expr }.map { it.value.first() }.forEach {
795                        it.componentExpressions.forEach { expr ->
796                            tab("this.${expr.oldValueName} = ${expr.toCode().generate()};")
797                        }
798                    }
799                }
800                tab("}")
801            }
802            includedBinders.filter{it.isUsed }.forEach { binder ->
803                tab("${binder.fieldName}.executePendingBindings();")
804            }
805            layoutBinder.sortedTargets.filter{
806                it.isUsed && it.resolvedType != null && it.resolvedType.extendsViewStub()
807            }.forEach {
808                tab("if (${it.fieldName}.getBinding() != null) {") {
809                    tab("${it.fieldName}.getBinding().executePendingBindings();")
810                }
811                tab("}")
812            }
813        }
814        nl("}")
815    }
816
817    fun readWithDependants(expressionList: List<Expr>, justRead: MutableList<Expr>,
818            batch: MutableList<Expr>, tmpDirtyFlags: FlagSet,
819            inheritedFlags: FlagSet? = null) : KCode = kcode("") {
820        expressionList.groupBy { it.shouldReadFlagSet }.forEach {
821            val flagSet = it.key
822            val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
823            val expressions = it.value
824            val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
825                "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
826            }.joinToString(" || ")
827            })"
828            val readCode = kcode("") {
829                val dependants = ArrayList<Expr>()
830                expressions.groupBy { condition(it) }.forEach {
831                    val condition = it.key
832                    val assignedValues = it.value.filter { it.needsLocalField && !it.isVariable() }
833                    if (!assignedValues.isEmpty()) {
834                        val assignment = kcode("") {
835                            assignedValues.forEach { expr: Expr ->
836                                tab("// read ${expr.uniqueKey}")
837                                tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";")
838                            }
839                        }
840                        if (condition != null) {
841                            tab("if ($condition) {") {
842                                app("", assignment)
843                            }
844                            tab ("}")
845                        } else {
846                            app("", assignment)
847                        }
848                        it.value.filter { it.isObservable }.forEach { expr: Expr ->
849                            tab("updateRegistration(${expr.id}, ${expr.executePendingLocalName});")
850                        }
851                    }
852
853                    it.value.forEach { expr: Expr ->
854                        justRead.add(expr)
855                        L.d("%s / readWithDependants %s", className, expr.uniqueKey);
856                        L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
857
858                        // if I am the condition for an expression, set its flag
859                        expr.dependants.filter {
860                            !it.isConditional && it.dependant is TernaryExpr &&
861                                    (it.dependant as TernaryExpr).pred == expr
862                        }.map { it.dependant }.groupBy {
863                            // group by when those ternaries will be evaluated (e.g. don't set conditional flags for no reason)
864                            val ternaryBitSet = it.shouldReadFlagsWithConditionals
865                            val isBehindTernary = ternaryBitSet.nextSetBit(model.invalidateAnyFlagIndex) == -1
866                            if (!isBehindTernary) {
867                                val ternaryFlags = it.shouldReadWithConditionalsFlagSet
868                                "if(${tmpDirtyFlags.mapOr(ternaryFlags){ suffix, index ->
869                                    "(${tmpDirtyFlags.localValue(index)} & ${ternaryFlags.localValue(index)}) != 0"
870                                }.joinToString(" || ")}) {"
871                            } else {
872                                // TODO if it is behind a ternary, we should set it when its predicate is elevated
873                                // Normally, this would mean that there is another code path to re-read our current expression.
874                                // Unfortunately, this may not be true due to the coverage detection in `expr#markAsReadIfDone`, this may never happen.
875                                // for v1.0, we'll go with always setting it and suffering an unnecessary calculation for this edge case.
876                                // we can solve this by listening to elevation events from the model.
877                                ""
878                            }
879                        }.forEach {
880                            val hasAnotherIf = it.key != ""
881                            if (hasAnotherIf) {
882                                tab(it.key) {
883                                    tab("if (${expr.executePendingLocalName}) {") {
884                                        it.value.forEach {
885                                            val set = it.getRequirementFlagSet(true)
886                                            mDirtyFlags.mapOr(set) { suffix, index ->
887                                                tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
888                                            }
889                                        }
890                                    }
891                                    tab("} else {") {
892                                        it.value.forEach {
893                                            val set = it.getRequirementFlagSet(false)
894                                            mDirtyFlags.mapOr(set) { suffix, index ->
895                                                tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
896                                            }
897                                        }
898                                    }.tab("}")
899                                }.app("}")
900                            } else {
901                                tab("if (${expr.executePendingLocalName}) {") {
902                                    it.value.forEach {
903                                        val set = it.getRequirementFlagSet(true)
904                                        mDirtyFlags.mapOr(set) { suffix, index ->
905                                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
906                                        }
907                                    }
908                                }
909                                tab("} else {") {
910                                    it.value.forEach {
911                                        val set = it.getRequirementFlagSet(false)
912                                        mDirtyFlags.mapOr(set) { suffix, index ->
913                                            tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
914                                        }
915                                    }
916                                } app("}")
917                            }
918                        }
919                        val chosen = expr.dependants.filter {
920                            val dependant = it.dependant
921                            batch.contains(dependant) &&
922                                    dependant.shouldReadFlagSet.andNot(flagSet).isEmpty &&
923                                    dependant.shouldReadNow(justRead)
924                        }
925                        if (chosen.isNotEmpty()) {
926                            dependants.addAll(chosen.map { it.dependant })
927                        }
928                    }
929                }
930                if (dependants.isNotEmpty()) {
931                    val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
932                    nl(readWithDependants(dependants, justRead, batch, tmpDirtyFlags, nextInheritedFlags))
933                }
934            }
935
936            if (needsIfWrapper) {
937                tab(ifClause) {
938                    app(" {")
939                    app("", readCode)
940                }
941                tab("}")
942            } else {
943                app("", readCode)
944            }
945        }
946    }
947
948    fun condition(expr : Expr) : String? {
949        if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
950            // create an if case for all dependencies that might be null
951            val nullables = expr.dependencies.filter {
952                it.isMandatory && it.other.resolvedType.isNullable
953            }.map { it.other }
954            if (!expr.isEqualityCheck && nullables.isNotEmpty()) {
955                return "${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}"
956            } else {
957                return null
958            }
959        } else {
960            return null
961        }
962    }
963
964    fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
965        model.exprMap.values.filter {
966            it.isUsed && it is ListenerExpr
967        }.groupBy { it }.forEach {
968            val expr = it.key as ListenerExpr
969            val listenerType = expr.resolvedType;
970            val extendsImplements : String
971            if (listenerType.isInterface) {
972                extendsImplements = "implements"
973            } else {
974                extendsImplements = "extends"
975            }
976            nl("public static class ${expr.listenerClassName} $extendsImplements ${listenerType.canonicalName}{") {
977                if (expr.child.isDynamic) {
978                    tab("private ${expr.child.resolvedType.toJavaCode()} value;")
979                    tab("public ${expr.listenerClassName} setValue(${expr.child.resolvedType.toJavaCode()} value) {") {
980                        tab("this.value = value;")
981                        tab("return value == null ? null : this;")
982                    }
983                    tab("}")
984                }
985                val listenerMethod = expr.method
986                val parameterTypes = listenerMethod.parameterTypes
987                val returnType = listenerMethod.getReturnType(parameterTypes.toArrayList())
988                tab("@Override")
989                tab("public $returnType ${listenerMethod.name}(${
990                    parameterTypes.withIndex().map {
991                        "${it.value.toJavaCode()} arg${it.index}"
992                    }.joinToString(", ")
993                }) {") {
994                    val obj : String
995                    if (expr.child.isDynamic) {
996                        obj = "this.value"
997                    } else {
998                        obj = expr.child.toCode().generate();
999                    }
1000                    val returnStr : String
1001                    if (!returnType.isVoid) {
1002                        returnStr = "return "
1003                    } else {
1004                        returnStr = ""
1005                    }
1006                    val args = parameterTypes.withIndex().map {
1007                        "arg${it.index}"
1008                    }.joinToString(", ")
1009                    tab("$returnStr$obj.${expr.name}($args);")
1010                }
1011                tab("}")
1012            }
1013            nl("}")
1014        }
1015    }
1016
1017    fun declareFactories() = kcode("") {
1018        nl("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1019            tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1020        }
1021        nl("}")
1022        nl("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1023            tab("return android.databinding.DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, root, attachToRoot, bindingComponent);")
1024        }
1025        nl("}")
1026        if (!layoutBinder.isMerge) {
1027            nl("public static $baseClassName inflate(android.view.LayoutInflater inflater) {") {
1028                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1029            }
1030            nl("}")
1031            nl("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1032                tab("return bind(inflater.inflate(${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, null, false), bindingComponent);")
1033            }
1034            nl("}")
1035            nl("public static $baseClassName bind(android.view.View view) {") {
1036                tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1037            }
1038            nl("}")
1039            nl("public static $baseClassName bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1040                tab("if (!\"${layoutBinder.tag}_0\".equals(view.getTag())) {") {
1041                    tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
1042                }
1043                tab("}")
1044                tab("return new $baseClassName(bindingComponent, view);")
1045            }
1046            nl("}")
1047        }
1048    }
1049
1050    /**
1051     * When called for a library compilation, we do not generate real implementations
1052     */
1053    public fun writeBaseClass(forLibrary : Boolean) : String =
1054        kcode("package ${layoutBinder.`package`};") {
1055            nl("import android.databinding.Bindable;")
1056            nl("import android.databinding.DataBindingUtil;")
1057            nl("import android.databinding.ViewDataBinding;")
1058            nl("public abstract class $baseClassName extends ViewDataBinding {")
1059            layoutBinder.sortedTargets.filter{it.id != null}.forEach {
1060                tab("public final ${it.interfaceClass} ${it.fieldName};")
1061            }
1062            nl("")
1063            tab("protected $baseClassName(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
1064                layoutBinder.sortedTargets.filter{it.id != null}.forEach {
1065                    tab(", ${it.interfaceClass} ${it.constructorParamName}")
1066                }
1067            }
1068            tab(") {") {
1069                tab("super(bindingComponent, root_, localFieldCount);")
1070                layoutBinder.sortedTargets.filter{it.id != null}.forEach {
1071                    tab("this.${it.fieldName} = ${it.constructorParamName};")
1072                }
1073            }
1074            tab("}")
1075            nl("")
1076            variables.forEach {
1077                if (it.userDefinedType != null) {
1078                    val type = ModelAnalyzer.getInstance().applyImports(it.userDefinedType, model.imports)
1079                    tab("public abstract void ${it.setterName}($type ${it.readableName});")
1080                }
1081            }
1082            tab("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1083                tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1084            }
1085            tab("}")
1086            tab("public static $baseClassName inflate(android.view.LayoutInflater inflater) {") {
1087                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1088            }
1089            tab("}")
1090            tab("public static $baseClassName bind(android.view.View view) {") {
1091                if (forLibrary) {
1092                    tab("return null;")
1093                } else {
1094                    tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1095                }
1096            }
1097            tab("}")
1098            tab("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1099                if (forLibrary) {
1100                    tab("return null;")
1101                } else {
1102                    tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, root, attachToRoot, bindingComponent);")
1103                }
1104            }
1105            tab("}")
1106            tab("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1107                if (forLibrary) {
1108                    tab("return null;")
1109                } else {
1110                    tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, null, false, bindingComponent);")
1111                }
1112            }
1113            tab("}")
1114            tab("public static $baseClassName bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1115                if (forLibrary) {
1116                    tab("return null;")
1117                } else {
1118                    tab("return ($baseClassName)bind(bindingComponent, view, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname});")
1119                }
1120            }
1121            tab("}")
1122            nl("}")
1123        }.generate()
1124}
1125