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