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