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