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