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