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