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