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