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