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