LayoutBinderWriter.kt revision 793e979f25e190162eacf46d6a4efc3efc1d2f91
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.reflection.ModelAnalyzer
30import android.databinding.tool.util.L
31import java.util.ArrayList
32import java.util.Arrays
33import java.util.BitSet
34import java.util.HashMap
35import java.util.HashSet
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) {
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                }
470            }
471        }
472        tab("setRootTag(root);")
473        tab("invalidateAll();");
474        nl("}")
475    }
476
477    fun fieldConversion(target : BindingTarget) : String {
478        if (!target.isUsed()) {
479            return "null"
480        } else {
481            val index = indices.get(target)
482            if (index == null) {
483                throw IllegalStateException("Unknown binding target")
484            }
485            val variableName = "bindings[${index}]"
486            return target.superConversion(variableName)
487        }
488    }
489
490    fun declareInvalidateAll() = kcode("") {
491        nl("@Override")
492        nl("public void invalidateAll() {") {
493            val fs = FlagSet(layoutBinder.getModel().getInvalidateAnyBitSet(),
494                    layoutBinder.getModel().getFlagBucketCount());
495            tab("synchronized(this) {") {
496                for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
497                    tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
498                }
499            } tab("}")
500            includedBinders.filter{it.isUsed()}.forEach { binder ->
501                tab("${binder.fieldName}.invalidateAll();")
502            }
503            tab("requestRebind();");
504        }
505        nl("}")
506    }
507
508    fun declareHasPendingBindings()  = kcode("") {
509        nl("@Override")
510        nl("public boolean hasPendingBindings() {") {
511            if (mDirtyFlags.buckets.size() > 0) {
512                tab("synchronized(this) {") {
513                    val flagCheck = 0.rangeTo(mDirtyFlags.buckets.size() - 1).map {
514                            "${mDirtyFlags.localValue(it)} != 0"
515                    }.joinToString(" || ")
516                    tab("if (${flagCheck}) {") {
517                        tab("return true;")
518                    }
519                    tab("}")
520                }
521                tab("}")
522            }
523            includedBinders.filter{it.isUsed()}.forEach { binder ->
524                tab("if (${binder.fieldName}.hasPendingBindings()) {") {
525                    tab("return true;")
526                }
527                tab("}")
528            }
529            tab("return false;")
530        }
531        nl("}")
532    }
533
534    fun declareSetVariable() = kcode("") {
535        nl("public boolean setVariable(int variableId, Object variable) {") {
536            tab("switch(variableId) {") {
537                usedVariables.forEach {
538                    tab ("case ${it.getName().br()} :") {
539                        tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
540                        tab("return true;")
541                    }
542                }
543            }
544            tab("}")
545            tab("return false;")
546        }
547        nl("}")
548    }
549
550    fun declareLog() = kcode("") {
551        nl("private void log(String msg, long i) {") {
552            tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
553        }
554        nl("}")
555    }
556
557    fun variableSettersAndGetters() = kcode("") {
558        variables.filterNot{it.isUsed()}.forEach {
559            nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
560                tab("// not used, ignore")
561            }
562            nl("}")
563            nl("")
564            nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
565                tab("return ${it.getDefaultValue()};")
566            }
567            nl("}")
568        }
569        usedVariables.forEach {
570            if (it.getUserDefinedType() != null) {
571                nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableName}) {") {
572                    if (it.isObservable()) {
573                        tab("updateRegistration(${it.getId()}, ${it.readableName});");
574                    }
575                    tab("this.${it.fieldName} = ${it.readableName};")
576                    // set dirty flags!
577                    val flagSet = it.invalidateFlagSet
578                    tab("synchronized(this) {") {
579                        mDirtyFlags.mapOr(flagSet) { suffix, index ->
580                            tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
581                        }
582                    } tab ("}")
583                    tab("super.requestRebind();")
584                }
585                nl("}")
586                nl("")
587                nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
588                    tab("return ${it.fieldName};")
589                }
590                nl("}")
591            }
592        }
593    }
594
595    fun onFieldChange() = kcode("") {
596        nl("@Override")
597        nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
598            tab("switch (localFieldId) {") {
599                model.getObservables().forEach {
600                    tab("case ${it.getId()} :") {
601                        tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
602                    }
603                }
604            }
605            tab("}")
606            tab("return false;")
607        }
608        nl("}")
609        nl("")
610
611        model.getObservables().forEach {
612            nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableName}, int fieldId) {") {
613                tab("switch (fieldId) {", {
614                    val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
615                    accessedFields.filter { it.hasBindableAnnotations() }
616                            .groupBy { it.getName() }
617                            .forEach {
618                                tab("case ${it.key.br()}:") {
619                                    val field = it.value.first()
620                                    tab("synchronized(this) {") {
621                                        mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
622                                            tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
623                                        }
624                                    } tab("}")
625                                    tab("return true;")
626                                }
627
628                            }
629                    tab("case ${"".br()}:") {
630                        val flagSet = it.invalidateFlagSet
631                        tab("synchronized(this) {") {
632                            mDirtyFlags.mapOr(flagSet) { suffix, index ->
633                                tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
634                            }
635                        } tab("}")
636                        tab("return true;")
637                    }
638
639                })
640                tab("}")
641                tab("return false;")
642            }
643            nl("}")
644            nl("")
645        }
646    }
647
648    fun declareViews() = kcode("// views") {
649        val oneLayout = !layoutBinder.hasVariations();
650        layoutBinder.getSortedTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
651            val access : String
652            if (oneLayout && it.getId() != null) {
653                access = "public"
654            } else {
655                access = "private"
656            }
657            nl("${access} final ${it.interfaceType} ${it.fieldName};")
658        }
659    }
660
661    fun declareVariables() = kcode("// variables") {
662        usedVariables.forEach {
663            nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
664        }
665    }
666
667    fun declareBoundValues() = kcode("// values") {
668        layoutBinder.getSortedTargets().filter { it.isUsed() }
669                .flatMap { it.getBindings() }
670                .filter { it.requiresOldValue() }
671                .flatMap{ it.getComponentExpressions().toArrayList() }
672                .groupBy { it }
673                .forEach {
674                    val expr = it.getKey()
675                    nl("private ${expr.getResolvedType().toJavaCode()} ${expr.oldValueName};")
676                }
677    }
678
679    fun declareListeners() = kcode("// listeners") {
680        model.getExprMap().values().filter {
681            it is ListenerExpr
682        }.groupBy { it }.forEach {
683            val expr = it.key as ListenerExpr
684            nl("private ${expr.listenerClassName} ${expr.fieldName};")
685        }
686    }
687
688    fun declareDirtyFlags() = kcode("// dirty flag") {
689        model.ext.localizedFlags.forEach { flag ->
690            flag.notEmpty { suffix, value ->
691                nl("private")
692                app(" ", if(flag.isDynamic()) null else "static final");
693                app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
694            }
695        }
696    }
697
698    fun flagMapping() = kcode("/* flag mapping") {
699        if (model.getFlagMapping() != null) {
700            val mapping = model.getFlagMapping()
701            for (i in mapping.indices) {
702                tab("flag $i: ${mapping[i]}")
703            }
704        }
705        nl("flag mapping end*/")
706    }
707
708    fun executePendingBindings() = kcode("") {
709        nl("@Override")
710        nl("protected void executeBindings() {") {
711            val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
712            tmpDirtyFlags.setLocalName("dirtyFlags");
713            for (i in (0..mDirtyFlags.buckets.size() - 1)) {
714                tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;")
715            }
716            tab("synchronized(this) {") {
717                for (i in (0..mDirtyFlags.buckets.size() - 1)) {
718                    tab("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
719                    tab("${mDirtyFlags.localValue(i)} = 0;")
720                }
721            } tab("}")
722            model.getPendingExpressions().filterNot { !it.canBeEvaluatedToAVariable() || (it.isVariable() && !it.isUsed()) }.forEach {
723                tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.getDefaultValue()};")
724            }
725            L.d("writing executePendingBindings for %s", className)
726            do {
727                val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
728                val justRead = arrayListOf<Expr>()
729                L.d("batch: %s", batch)
730                while (!batch.none()) {
731                    val readNow = batch.filter { it.shouldReadNow(justRead) }
732                    if (readNow.isEmpty()) {
733                        throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
734                    }
735                    L.d("new read now. batch size: %d, readNow size: %d", batch.size(), readNow.size())
736                    nl(readWithDependants(readNow, justRead, batch, tmpDirtyFlags))
737                    batch.removeAll(justRead)
738                }
739                tab("// batch finished")
740            } while (model.markBitsRead())
741            // verify everything is read.
742            val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
743            if (batch.isNotEmpty()) {
744                L.e("could not generate code for %s. This might be caused by circular dependencies."
745                        + "Please report on b.android.com. %d %s %s", layoutBinder.getLayoutname(),
746                        batch.size(), batch.get(0), batch.get(0).toCode().generate())
747            }
748            //
749            layoutBinder.getSortedTargets().filter { it.isUsed() }
750                    .flatMap { it.getBindings() }
751                    .groupBy {
752                        "${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
753                            "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
754                        }.joinToString(" || ") }"
755                    }.forEach {
756                tab("if (${it.key}) {") {
757                    it.value.groupBy { Math.max(1, it.getMinApi()) }.forEach {
758                        val setterValues = kcode("") {
759                            it.value.forEach { binding ->
760                                val fieldName: String
761                                if (binding.getTarget().getViewClass().
762                                        equals(binding.getTarget().getInterfaceType())) {
763                                    fieldName = "this.${binding.getTarget().fieldName}"
764                                } else {
765                                    fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
766                                }
767                                tab(binding.toJavaCode(fieldName, "this.mBindingComponent")).app(";")
768                            }
769                        }
770                        tab("// api target ${it.key}")
771                        if (it.key > 1) {
772                            tab("if(getBuildSdkInt() >= ${it.key}) {") {
773                                app("", setterValues)
774                            }
775                            tab("}")
776                        } else {
777                            app("", setterValues)
778                        }
779                    }
780                }
781                tab("}")
782            }
783
784
785            layoutBinder.getSortedTargets().filter { it.isUsed() }
786                    .flatMap { it.getBindings() }
787                    .filter { it.requiresOldValue() }
788                    .groupBy {"${tmpDirtyFlags.mapOr(it.getExpr().dirtyFlagSet) { suffix, index ->
789                        "(${tmpDirtyFlags.localValue(index)} & ${it.getExpr().dirtyFlagSet.localValue(index)}) != 0"
790                    }.joinToString(" || ")
791                    }"}.forEach {
792                tab("if (${it.key}) {") {
793                    it.value.groupBy { it.getExpr() }.map { it.value.first() }.forEach {
794                        it.getComponentExpressions().forEach { expr ->
795                            tab("this.${expr.oldValueName} = ${expr.toCode().generate()};")
796                        }
797                    }
798                }
799                tab("}")
800            }
801            includedBinders.filter{it.isUsed()}.forEach { binder ->
802                tab("${binder.fieldName}.executePendingBindings();")
803            }
804            layoutBinder.getSortedTargets().filter{
805                it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
806            }.forEach {
807                tab("if (${it.fieldName}.getBinding() != null) {") {
808                    tab("${it.fieldName}.getBinding().executePendingBindings();")
809                }
810                tab("}")
811            }
812        }
813        nl("}")
814    }
815
816    fun readWithDependants(expressionList: List<Expr>, justRead: MutableList<Expr>,
817            batch: MutableList<Expr>, tmpDirtyFlags: FlagSet,
818            inheritedFlags: FlagSet? = null) : KCode = kcode("") {
819        expressionList.groupBy { it.shouldReadFlagSet }.forEach {
820            val flagSet = it.key
821            val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
822            val expressions = it.value
823            val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
824                "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
825            }.joinToString(" || ")
826            })"
827            val readCode = kcode("") {
828                val dependants = ArrayList<Expr>()
829                expressions.groupBy { condition(it) }.forEach {
830                    val condition = it.key
831                    val assignedValues = it.value.filter {
832                        it.canBeEvaluatedToAVariable() && !it.isVariable()
833                    }
834                    if (!assignedValues.isEmpty()) {
835                        val assignment = kcode("") {
836                            assignedValues.forEach { expr: Expr ->
837                                tab("// read ${expr.getUniqueKey()}")
838                                tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";")
839                            }
840                        }
841                        if (condition != null) {
842                            tab("if (${condition}) {") {
843                                app("", assignment)
844                            }
845                            tab ("}")
846                        } else {
847                            app("", assignment)
848                        }
849                    }
850                    it.value.filter { it.isObservable() }.forEach { expr: Expr ->
851                        tab("updateRegistration(${expr.getId()}, ${expr.executePendingLocalName});")
852                    }
853
854                    it.value.forEach { expr: Expr ->
855                        justRead.add(expr)
856                        L.d("%s / readWithDependants %s", className, expr.getUniqueKey());
857                        L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper);
858
859                        // if I am the condition for an expression, set its flag
860                        val conditionals = expr.getDependants().filter {
861                            !it.isConditional() && it.getDependant() is TernaryExpr &&
862                                    (it.getDependant() as TernaryExpr).getPred() == expr
863                        }.map { it.getDependant() }
864                        if (conditionals.isNotEmpty()) {
865                            tab("// setting conditional flags")
866                            tab("if (${expr.executePendingLocalName}) {") {
867                                conditionals.forEach {
868                                    val set = it.getRequirementFlagSet(true)
869                                    mDirtyFlags.mapOr(set) { suffix, index ->
870                                        tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
871                                    }
872                                }
873                            }
874                            tab("} else {") {
875                                conditionals.forEach {
876                                    val set = it.getRequirementFlagSet(false)
877                                    mDirtyFlags.mapOr(set) { suffix, index ->
878                                        tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
879                                    }
880                                }
881                            }
882                            tab("}")
883                        }
884
885                        val chosen = expr.getDependants().filter {
886                            val dependant = it.getDependant()
887                            batch.contains(dependant) &&
888                                    dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
889                                    dependant.shouldReadNow(justRead)
890                        }
891                        if (chosen.isNotEmpty()) {
892                            dependants.addAll(chosen.map { it.getDependant() })
893                        }
894                    }
895                }
896                if (dependants.isNotEmpty()) {
897                    val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
898                    nl(readWithDependants(dependants, justRead, batch, tmpDirtyFlags, nextInheritedFlags))
899                }
900            }
901
902            if (needsIfWrapper) {
903                tab(ifClause) {
904                    app(" {")
905                    app("", readCode)
906                }
907                tab("}")
908            } else {
909                app("", readCode)
910            }
911        }
912    }
913
914    fun condition(expr : Expr) : String? {
915        if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
916            // create an if case for all dependencies that might be null
917            val nullables = expr.getDependencies().filter {
918                it.isMandatory() && it.getOther().getResolvedType().isNullable()
919            }.map { it.getOther() }
920            if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
921                return "${nullables.map { "${it.executePendingLocalName} != null" }.joinToString(" && ")}"
922            } else {
923                return null
924            }
925        } else {
926            return null
927        }
928    }
929
930    fun declareListenerImpls() = kcode("// Listener Stub Implementations") {
931        model.getExprMap().values().filter {
932            it.isUsed() && it is ListenerExpr
933        }.groupBy { it }.forEach {
934            val expr = it.key as ListenerExpr
935            val listenerType = expr.getResolvedType();
936            val extendsImplements : String
937            if (listenerType.isInterface()) {
938                extendsImplements = "implements"
939            } else {
940                extendsImplements = "extends"
941            }
942            nl("public static class ${expr.listenerClassName} ${extendsImplements} ${listenerType.getCanonicalName()}{") {
943                if (expr.getChild().isDynamic()) {
944                    tab("private ${expr.getChild().getResolvedType().toJavaCode()} value;")
945                    tab("public ${expr.listenerClassName} setValue(${expr.getChild().getResolvedType().toJavaCode()} value) {") {
946                        tab("this.value = value;")
947                        tab("return value == null ? null : this;")
948                    }
949                    tab("}")
950                }
951                val listenerMethod = expr.getMethod()
952                val parameterTypes = listenerMethod.getParameterTypes()
953                val returnType = listenerMethod.getReturnType(parameterTypes.toArrayList())
954                tab("@Override")
955                tab("public ${returnType} ${listenerMethod.getName()}(${
956                    parameterTypes.withIndex().map {
957                        "${it.value.toJavaCode()} arg${it.index}"
958                    }.joinToString(", ")
959                }) {") {
960                    val obj : String
961                    if (expr.getChild().isDynamic()) {
962                        obj = "this.value"
963                    } else {
964                        obj = expr.getChild().toCode().generate();
965                    }
966                    val returnStr : String
967                    if (!returnType.isVoid()) {
968                        returnStr = "return "
969                    } else {
970                        returnStr = ""
971                    }
972                    val args = parameterTypes.withIndex().map {
973                        "arg${it.index}"
974                    }.joinToString(", ")
975                    tab("${returnStr}${obj}.${expr.getName()}(${args});")
976                }
977                tab("}")
978            }
979            nl("}")
980        }
981    }
982
983    fun declareFactories() = kcode("") {
984        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
985            tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
986        }
987        nl("}")
988        nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
989            tab("return android.databinding.DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
990        }
991        nl("}")
992        if (!layoutBinder.isMerge()) {
993            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
994                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
995            }
996            nl("}")
997            nl("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
998                tab("return bind(inflater.inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false), bindingComponent);")
999            }
1000            nl("}")
1001            nl("public static ${baseClassName} bind(android.view.View view) {") {
1002                tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1003            }
1004            nl("}")
1005            nl("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1006                tab("if (!\"${layoutBinder.getTag()}_0\".equals(view.getTag())) {") {
1007                    tab("throw new RuntimeException(\"view tag isn't correct on view:\" + view.getTag());")
1008                }
1009                tab("}")
1010                tab("return new ${baseClassName}(bindingComponent, view);")
1011            }
1012            nl("}")
1013        }
1014    }
1015
1016    /**
1017     * When called for a library compilation, we do not generate real implementations
1018     */
1019    public fun writeBaseClass(forLibrary : Boolean) : String =
1020        kcode("package ${layoutBinder.getPackage()};") {
1021            nl("import android.databinding.Bindable;")
1022            nl("import android.databinding.DataBindingUtil;")
1023            nl("import android.databinding.ViewDataBinding;")
1024            nl("public abstract class ${baseClassName} extends ViewDataBinding {")
1025            layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1026                tab("public final ${it.interfaceType} ${it.fieldName};")
1027            }
1028            nl("")
1029            tab("protected ${baseClassName}(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
1030                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1031                    tab(", ${it.interfaceType} ${it.constructorParamName}")
1032                }
1033            }
1034            tab(") {") {
1035                tab("super(bindingComponent, root_, localFieldCount);")
1036                layoutBinder.getSortedTargets().filter{it.getId() != null}.forEach {
1037                    tab("this.${it.fieldName} = ${it.constructorParamName};")
1038                }
1039            }
1040            tab("}")
1041            nl("")
1042            variables.forEach {
1043                if (it.getUserDefinedType() != null) {
1044                    val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
1045                    tab("public abstract void ${it.setterName}(${type} ${it.readableName});")
1046                }
1047            }
1048            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
1049                tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
1050            }
1051            tab("}")
1052            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater) {") {
1053                tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
1054            }
1055            tab("}")
1056            tab("public static ${baseClassName} bind(android.view.View view) {") {
1057                if (forLibrary) {
1058                    tab("return null;")
1059                } else {
1060                    tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
1061                }
1062            }
1063            tab("}")
1064            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
1065                if (forLibrary) {
1066                    tab("return null;")
1067                } else {
1068                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, attachToRoot, bindingComponent);")
1069                }
1070            }
1071            tab("}")
1072            tab("public static ${baseClassName} inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
1073                if (forLibrary) {
1074                    tab("return null;")
1075                } else {
1076                    tab("return DataBindingUtil.<${baseClassName}>inflate(inflater, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false, bindingComponent);")
1077                }
1078            }
1079            tab("}")
1080            tab("public static ${baseClassName} bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
1081                if (forLibrary) {
1082                    tab("return null;")
1083                } else {
1084                    tab("return (${baseClassName})bind(bindingComponent, view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
1085                }
1086            }
1087            tab("}")
1088            nl("}")
1089        }.generate()
1090}
1091