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