1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.databinding.tool.expr; 18 19 import android.databinding.tool.BindingTarget; 20 import android.databinding.tool.CallbackWrapper; 21 import android.databinding.tool.InverseBinding; 22 import android.databinding.tool.reflection.ModelAnalyzer; 23 import android.databinding.tool.reflection.ModelClass; 24 import android.databinding.tool.reflection.ModelMethod; 25 import android.databinding.tool.store.Location; 26 import android.databinding.tool.util.L; 27 import android.databinding.tool.util.Preconditions; 28 import android.databinding.tool.writer.ExprModelExt; 29 import android.databinding.tool.writer.FlagSet; 30 31 import org.antlr.v4.runtime.ParserRuleContext; 32 import org.jetbrains.annotations.Nullable; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.BitSet; 37 import java.util.HashMap; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.concurrent.atomic.AtomicInteger; 41 42 public class ExprModel { 43 44 Map<String, Expr> mExprMap = new HashMap<String, Expr>(); 45 46 List<Expr> mBindingExpressions = new ArrayList<Expr>(); 47 48 private int mInvalidateableFieldLimit = 0; 49 50 private int mRequirementIdCount = 0; 51 52 // each arg list receives a unique id even if it is the same arguments and method. 53 private int mArgListIdCounter = 0; 54 55 private static final String TRUE_KEY_SUFFIX = "== true"; 56 private static final String FALSE_KEY_SUFFIX = "== false"; 57 58 /** 59 * Any expression can be invalidated by invalidating this flag. 60 */ 61 private BitSet mInvalidateAnyFlags; 62 private int mInvalidateAnyFlagIndex; 63 64 /** 65 * Used by code generation. Keeps the list of expressions that are waiting to be evaluated. 66 */ 67 private List<Expr> mPendingExpressions; 68 69 /** 70 * Used for converting flags into identifiers while debugging. 71 */ 72 private String[] mFlagMapping; 73 74 private int mFlagBucketCount;// how many buckets we use to identify flags 75 76 private List<Expr> mObservables; 77 78 private boolean mSealed = false; 79 80 private Map<String, String> mImports = new HashMap<String, String>(); 81 82 private ParserRuleContext mCurrentParserContext; 83 private Location mCurrentLocationInFile; 84 85 private Map<String, CallbackWrapper> mCallbackWrappers = new HashMap<String, CallbackWrapper>(); 86 87 private AtomicInteger mCallbackIdCounter = new AtomicInteger(); 88 89 private ExprModelExt mExt = new ExprModelExt(); 90 91 /** 92 * Adds the expression to the list of expressions and returns it. 93 * If it already exists, returns existing one. 94 * 95 * @param expr The new parsed expression 96 * @return The expression itself or another one if the same thing was parsed before 97 */ register(T expr)98 public <T extends Expr> T register(T expr) { 99 Preconditions.check(!mSealed, "Cannot add expressions to a model after it is sealed"); 100 Location location = null; 101 if (mCurrentParserContext != null) { 102 location = new Location(mCurrentParserContext); 103 location.setParentLocation(mCurrentLocationInFile); 104 } 105 //noinspection unchecked 106 T existing = (T) mExprMap.get(expr.getUniqueKey()); 107 if (existing != null) { 108 Preconditions.check(expr.getParents().isEmpty(), 109 "If an expression already exists, it should've never been added to a parent," 110 + "if thats the case, somewhere we are creating an expression w/o" 111 + "calling expression model"); 112 // tell the expr that it is being swapped so that if it was added to some other expr 113 // as a parent, those can swap their references 114 expr.onSwappedWith(existing); 115 if (location != null) { 116 existing.addLocation(location); 117 } 118 return existing; 119 } 120 mExprMap.put(expr.getUniqueKey(), expr); 121 expr.setModel(this); 122 if (location != null) { 123 expr.addLocation(location); 124 } 125 return expr; 126 } 127 markSealed()128 protected void markSealed() { 129 mSealed = true; 130 } 131 getExt()132 public ExprModelExt getExt() { 133 return mExt; 134 } 135 obtainCallbackId()136 public int obtainCallbackId() { 137 return mCallbackIdCounter.incrementAndGet(); 138 } 139 setCurrentParserContext(ParserRuleContext currentParserContext)140 public void setCurrentParserContext(ParserRuleContext currentParserContext) { 141 mCurrentParserContext = currentParserContext; 142 } 143 getCurrentParserContext()144 public ParserRuleContext getCurrentParserContext() { 145 return mCurrentParserContext; 146 } 147 getCurrentLocationInFile()148 public Location getCurrentLocationInFile() { 149 return mCurrentLocationInFile; 150 } 151 getExprMap()152 public Map<String, Expr> getExprMap() { 153 return mExprMap; 154 } 155 size()156 public int size() { 157 return mExprMap.size(); 158 } 159 comparison(String op, Expr left, Expr right)160 public ComparisonExpr comparison(String op, Expr left, Expr right) { 161 return register(new ComparisonExpr(op, left, right)); 162 } 163 instanceOfOp(Expr expr, String type)164 public InstanceOfExpr instanceOfOp(Expr expr, String type) { 165 return register(new InstanceOfExpr(expr, type)); 166 } 167 field(Expr parent, String name)168 public FieldAccessExpr field(Expr parent, String name) { 169 return register(new FieldAccessExpr(parent, name)); 170 } 171 observableField(Expr parent, String name)172 public FieldAccessExpr observableField(Expr parent, String name) { 173 return register(new ObservableFieldExpr(parent, name)); 174 } 175 methodReference(Expr parent, String name)176 public MethodReferenceExpr methodReference(Expr parent, String name) { 177 return register(new MethodReferenceExpr(parent, name)); 178 } 179 symbol(String text, Class type)180 public SymbolExpr symbol(String text, Class type) { 181 return register(new SymbolExpr(text, type)); 182 } 183 ternary(Expr pred, Expr ifTrue, Expr ifFalse)184 public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) { 185 return register(new TernaryExpr(pred, ifTrue, ifFalse)); 186 } 187 identifier(String name)188 public IdentifierExpr identifier(String name) { 189 return register(new IdentifierExpr(name)); 190 } 191 staticIdentifier(String name)192 public StaticIdentifierExpr staticIdentifier(String name) { 193 return register(new StaticIdentifierExpr(name)); 194 } 195 builtInVariable(String name, String type, String accessCode)196 public BuiltInVariableExpr builtInVariable(String name, String type, String accessCode) { 197 return register(new BuiltInVariableExpr(name, type, accessCode)); 198 } 199 viewFieldExpr(BindingTarget bindingTarget)200 public ViewFieldExpr viewFieldExpr(BindingTarget bindingTarget) { 201 return register(new ViewFieldExpr(bindingTarget)); 202 } 203 204 /** 205 * Creates a static identifier for the given class or returns the existing one. 206 */ staticIdentifierFor(final ModelClass modelClass)207 public StaticIdentifierExpr staticIdentifierFor(final ModelClass modelClass) { 208 final String type = modelClass.getCanonicalName(); 209 // check for existing 210 StaticIdentifierExpr id = findStaticIdentifierExpr(type); 211 if (id != null) { 212 return id; 213 } 214 215 // does not exist. Find a name for it. 216 int cnt = 0; 217 int dotIndex = type.lastIndexOf("."); 218 String baseName; 219 Preconditions.check(dotIndex < type.length() - 1, "Invalid type %s", type); 220 if (dotIndex == -1) { 221 baseName = type; 222 } else { 223 baseName = type.substring(dotIndex + 1); 224 } 225 while (true) { 226 String candidate = cnt == 0 ? baseName : baseName + cnt; 227 if (!mImports.containsKey(candidate)) { 228 return addImport(candidate, type, null); 229 } 230 cnt ++; 231 Preconditions.check(cnt < 100, "Failed to create an import for " + type); 232 } 233 } 234 235 @Nullable 236 private StaticIdentifierExpr findStaticIdentifierExpr(String type) { 237 for (Expr expr : mExprMap.values()) { 238 if (expr instanceof StaticIdentifierExpr) { 239 StaticIdentifierExpr id = (StaticIdentifierExpr) expr; 240 if (id.getUserDefinedType().equals(type)) { 241 return id; 242 } 243 } 244 } 245 return null; 246 } 247 248 public MethodCallExpr methodCall(Expr target, String name, List<Expr> args) { 249 return register(new MethodCallExpr(target, name, args)); 250 } 251 252 public MathExpr math(Expr left, String op, Expr right) { 253 return register(new MathExpr(left, op, right)); 254 } 255 256 public TernaryExpr logical(Expr left, String op, Expr right) { 257 if ("&&".equals(op)) { 258 // left && right 259 // left ? right : false 260 return register(new TernaryExpr(left, right, symbol("false", boolean.class))); 261 } else { 262 // left || right 263 // left ? true : right 264 return register(new TernaryExpr(left, symbol("true", boolean.class), right)); 265 } 266 } 267 268 public BitShiftExpr bitshift(Expr left, String op, Expr right) { 269 return register(new BitShiftExpr(left, op, right)); 270 } 271 272 public UnaryExpr unary(String op, Expr expr) { 273 return register(new UnaryExpr(op, expr)); 274 } 275 276 public Expr resourceExpr(BindingTarget target, String packageName, String resourceType, 277 String resourceName, List<Expr> args) { 278 return register(new ResourceExpr(target, packageName, resourceType, resourceName, args)); 279 } 280 281 public Expr bracketExpr(Expr variableExpr, Expr argExpr) { 282 return register(new BracketExpr(variableExpr, argExpr)); 283 } 284 285 public Expr castExpr(String type, Expr expr) { 286 return register(new CastExpr(type, expr)); 287 } 288 289 public TwoWayListenerExpr twoWayListenerExpr(InverseBinding inverseBinding) { 290 return register(new TwoWayListenerExpr(inverseBinding)); 291 } 292 public List<Expr> getBindingExpressions() { 293 return mBindingExpressions; 294 } 295 296 public StaticIdentifierExpr addImport(String alias, String type, Location location) { 297 String existing = mImports.get(alias); 298 if (existing != null) { 299 if (existing.equals(type)) { 300 final StaticIdentifierExpr id = findStaticIdentifierExpr(type); 301 Preconditions.checkNotNull(id, "Missing import expression although it is" 302 + " registered"); 303 return id; 304 } else { 305 L.e("%s has already been defined as %s but trying to re-define as %s", alias, 306 existing, type); 307 } 308 } 309 310 final StaticIdentifierExpr id = staticIdentifier(alias); 311 L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName()); 312 id.setUserDefinedType(type); 313 if (location != null) { 314 id.addLocation(location); 315 } 316 mImports.put(alias, type); 317 return id; 318 } 319 320 public Map<String, String> getImports() { 321 return mImports; 322 } 323 324 /** 325 * The actual thingy that is set on the binding target. 326 * 327 * Input must be already registered 328 */ 329 public Expr bindingExpr(Expr bindingExpr) { 330 Preconditions.check(mExprMap.containsKey(bindingExpr.getUniqueKey()), 331 "Main expression should already be registered"); 332 if (!mBindingExpressions.contains(bindingExpr)) { 333 mBindingExpressions.add(bindingExpr); 334 } 335 return bindingExpr; 336 } 337 338 public void removeExpr(Expr expr) { 339 Preconditions.check(!mSealed, "Can't modify the expression list after sealing the model."); 340 mBindingExpressions.remove(expr); 341 mExprMap.remove(expr.getUniqueKey()); 342 } 343 344 public List<Expr> getObservables() { 345 return mObservables; 346 } 347 348 /** 349 * Give id to each expression. Will be useful if we serialize. 350 */ 351 public void seal() { 352 L.d("sealing model"); 353 List<Expr> notifiableExpressions = new ArrayList<Expr>(); 354 //ensure class analyzer. We need to know observables at this point 355 final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); 356 updateExpressions(modelAnalyzer); 357 358 int counter = 0; 359 final Iterable<Expr> observables = filterObservables(modelAnalyzer); 360 List<String> flagMapping = new ArrayList<String>(); 361 mObservables = new ArrayList<Expr>(); 362 for (Expr expr : observables) { 363 // observables gets initial ids 364 flagMapping.add(expr.getUniqueKey()); 365 expr.setId(counter++); 366 mObservables.add(expr); 367 notifiableExpressions.add(expr); 368 L.d("observable %s", expr.getUniqueKey()); 369 } 370 371 // non-observable identifiers gets next ids 372 final Iterable<Expr> nonObservableIds = filterNonObservableIds(modelAnalyzer); 373 for (Expr expr : nonObservableIds) { 374 flagMapping.add(expr.getUniqueKey()); 375 expr.setId(counter++); 376 notifiableExpressions.add(expr); 377 L.d("non-observable %s", expr.getUniqueKey()); 378 } 379 380 // descendants of observables gets following ids 381 for (Expr expr : observables) { 382 for (Expr parent : expr.getParents()) { 383 if (parent.hasId()) { 384 continue;// already has some id, means observable 385 } 386 // only fields earn an id 387 if (parent instanceof FieldAccessExpr) { 388 FieldAccessExpr fae = (FieldAccessExpr) parent; 389 L.d("checking field access expr %s. getter: %s", fae,fae.getGetter()); 390 // FAE#getter might be null if it is used only in a callback. 391 if (fae.getGetter() != null && fae.isDynamic() 392 && fae.getGetter().canBeInvalidated()) { 393 flagMapping.add(parent.getUniqueKey()); 394 parent.setId(counter++); 395 notifiableExpressions.add(parent); 396 L.d("notifiable field %s : %s for %s : %s", parent.getUniqueKey(), 397 Integer.toHexString(System.identityHashCode(parent)), 398 expr.getUniqueKey(), 399 Integer.toHexString(System.identityHashCode(expr))); 400 } 401 } 402 } 403 } 404 405 // now all 2-way bound view fields 406 for (Expr expr : mExprMap.values()) { 407 if (expr instanceof FieldAccessExpr) { 408 FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) expr; 409 if (fieldAccessExpr.getTarget() instanceof ViewFieldExpr) { 410 flagMapping.add(fieldAccessExpr.getUniqueKey()); 411 fieldAccessExpr.setId(counter++); 412 } 413 } 414 } 415 416 // non-dynamic binding expressions receive some ids so that they can be invalidated 417 L.d("list of binding expressions"); 418 for (int i = 0; i < mBindingExpressions.size(); i++) { 419 L.d("[%d] %s", i, mBindingExpressions.get(i)); 420 } 421 // we don't assign ids to constant binding expressions because now invalidateAll has its own 422 // flag. 423 424 for (Expr expr : notifiableExpressions) { 425 expr.enableDirectInvalidation(); 426 } 427 428 // make sure all dependencies are resolved to avoid future race conditions 429 for (Expr expr : mExprMap.values()) { 430 expr.getDependencies(); 431 } 432 mInvalidateAnyFlagIndex = counter ++; 433 flagMapping.add("INVALIDATE ANY"); 434 mInvalidateableFieldLimit = counter; 435 BitSet invalidateableFlags = new BitSet(); 436 for (int i = 0; i < mInvalidateableFieldLimit; i++) { 437 invalidateableFlags.set(i, true); 438 } 439 440 // make sure all dependencies are resolved to avoid future race conditions 441 for (Expr expr : mExprMap.values()) { 442 if (expr.isConditional()) { 443 L.d("requirement id for %s is %d", expr, counter); 444 expr.setRequirementId(counter); 445 flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX); 446 flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX); 447 counter += 2; 448 } 449 } 450 BitSet conditionalFlags = new BitSet(); 451 for (int i = mInvalidateableFieldLimit; i < counter; i++) { 452 conditionalFlags.set(i, true); 453 } 454 mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2; 455 456 // everybody gets an id 457 for (Map.Entry<String, Expr> entry : mExprMap.entrySet()) { 458 final Expr value = entry.getValue(); 459 if (!value.hasId()) { 460 value.setId(counter++); 461 } 462 } 463 464 mFlagMapping = new String[flagMapping.size()]; 465 flagMapping.toArray(mFlagMapping); 466 467 mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize); 468 mInvalidateAnyFlags = new BitSet(); 469 mInvalidateAnyFlags.set(mInvalidateAnyFlagIndex, true); 470 471 for (Expr expr : mExprMap.values()) { 472 expr.getShouldReadFlagsWithConditionals(); 473 } 474 475 for (Expr expr : mExprMap.values()) { 476 // ensure all types are calculated 477 expr.getResolvedType(); 478 } 479 480 mSealed = true; 481 } 482 483 /** 484 * Run updateExpr on each binding expression until no new expressions are added. 485 * <p> 486 * Some expressions (e.g. field access) may replace themselves and add/remove new dependencies 487 * so we need to make sure each expression's update is called at least once. 488 */ 489 private void updateExpressions(ModelAnalyzer modelAnalyzer) { 490 int startSize = -1; 491 while (startSize != mExprMap.size()) { 492 startSize = mExprMap.size(); 493 ArrayList<Expr> exprs = new ArrayList<Expr>(mBindingExpressions); 494 for (Expr expr : exprs) { 495 expr.updateExpr(modelAnalyzer); 496 } 497 } 498 } 499 500 public int getFlagBucketCount() { 501 return mFlagBucketCount; 502 } 503 504 public int getTotalFlagCount() { 505 return mRequirementIdCount * 2 + mInvalidateableFieldLimit; 506 } 507 508 public int getInvalidateableFieldLimit() { 509 return mInvalidateableFieldLimit; 510 } 511 512 public String[] getFlagMapping() { 513 return mFlagMapping; 514 } 515 516 public String getFlag(int id) { 517 return mFlagMapping[id]; 518 } 519 520 private List<Expr> filterNonObservableIds(final ModelAnalyzer modelAnalyzer) { 521 List<Expr> result = new ArrayList<Expr>(); 522 for (Expr input : mExprMap.values()) { 523 if (input instanceof IdentifierExpr 524 && !input.hasId() 525 && !input.isObservable() 526 && input.isDynamic()) { 527 result.add(input); 528 } 529 } 530 return result; 531 } 532 533 private Iterable<Expr> filterObservables(final ModelAnalyzer modelAnalyzer) { 534 List<Expr> result = new ArrayList<Expr>(); 535 for (Expr input : mExprMap.values()) { 536 if (input.isObservable()) { 537 result.add(input); 538 } 539 } 540 return result; 541 } 542 543 public List<Expr> getPendingExpressions() { 544 if (mPendingExpressions == null) { 545 mPendingExpressions = new ArrayList<Expr>(); 546 for (Expr expr : mExprMap.values()) { 547 // if an expression is NOT dynanic but has conditional dependants, still return it 548 // so that conditional flags can be set 549 if (!expr.isRead() && (expr.isDynamic() || expr.hasConditionalDependant())) { 550 mPendingExpressions.add(expr); 551 } 552 } 553 } 554 return mPendingExpressions; 555 } 556 557 public boolean markBitsRead() { 558 // each has should read flags, we set them back on them 559 List<Expr> markedSomeFlagsRead = new ArrayList<Expr>(); 560 for (Expr expr : filterShouldRead(getPendingExpressions())) { 561 expr.markFlagsAsRead(expr.getShouldReadFlags()); 562 markedSomeFlagsRead.add(expr); 563 } 564 return pruneDone(markedSomeFlagsRead); 565 } 566 567 private boolean pruneDone(List<Expr> markedSomeFlagsAsRead) { 568 boolean marked = true; 569 List<Expr> markedAsReadList = new ArrayList<Expr>(); 570 while (marked) { 571 marked = false; 572 for (Expr expr : mExprMap.values()) { 573 if (expr.isRead()) { 574 continue; 575 } 576 if (expr.markAsReadIfDone()) { 577 L.d("marked %s as read ", expr.getUniqueKey()); 578 marked = true; 579 markedAsReadList.add(expr); 580 markedSomeFlagsAsRead.remove(expr); 581 } 582 } 583 } 584 boolean elevated = false; 585 for (Expr markedAsRead : markedAsReadList) { 586 for (Dependency dependency : markedAsRead.getDependants()) { 587 if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) { 588 elevated = true; 589 } 590 } 591 } 592 if (!elevated) { 593 for (Expr partialRead : markedSomeFlagsAsRead) { 594 // even if all paths are not satisfied, we can elevate certain conditional 595 // dependencies if all of their paths are satisfied. 596 for (Dependency dependency : partialRead.getDependants()) { 597 Expr dependant = dependency.getDependant(); 598 if (dependant.isConditional() && dependant.getAllCalculationPaths() 599 .areAllPathsSatisfied(partialRead.mReadSoFar)) { 600 if (dependant.considerElevatingConditionals(partialRead)) { 601 elevated = true; 602 } 603 } 604 } 605 } 606 } 607 if (elevated) { 608 // some conditionals are elevated. We should re-calculate flags 609 for (Expr expr : getPendingExpressions()) { 610 if (!expr.isRead()) { 611 expr.invalidateReadFlags(); 612 } 613 } 614 mPendingExpressions = null; 615 } 616 return elevated; 617 } 618 619 private static boolean hasConditionalOrNestedCannotReadDependency(Expr expr) { 620 for (Dependency dependency : expr.getDependencies()) { 621 if (dependency.isConditional() || dependency.getOther().hasNestedCannotRead()) { 622 return true; 623 } 624 } 625 return false; 626 } 627 628 public static ArrayList<Expr> filterShouldRead(Iterable<Expr> exprs) { 629 ArrayList<Expr> result = new ArrayList<Expr>(); 630 for (Expr expr : exprs) { 631 if (!expr.getShouldReadFlags().isEmpty() && 632 !hasConditionalOrNestedCannotReadDependency(expr)) { 633 result.add(expr); 634 } 635 } 636 return result; 637 } 638 639 /** 640 * May return null if flag is equal to invalidate any flag. 641 */ 642 public Expr findFlagExpression(int flag) { 643 if (mInvalidateAnyFlags.get(flag)) { 644 return null; 645 } 646 final String key = mFlagMapping[flag]; 647 if (mExprMap.containsKey(key)) { 648 return mExprMap.get(key); 649 } 650 int falseIndex = key.indexOf(FALSE_KEY_SUFFIX); 651 if (falseIndex > -1) { 652 final String trimmed = key.substring(0, falseIndex); 653 return mExprMap.get(trimmed); 654 } 655 int trueIndex = key.indexOf(TRUE_KEY_SUFFIX); 656 if (trueIndex > -1) { 657 final String trimmed = key.substring(0, trueIndex); 658 return mExprMap.get(trimmed); 659 } 660 // log everything we call 661 StringBuilder error = new StringBuilder(); 662 error.append("cannot find flag:").append(flag).append("\n"); 663 error.append("invalidate any flag:").append(mInvalidateAnyFlags).append("\n"); 664 error.append("key:").append(key).append("\n"); 665 error.append("flag mapping:").append(Arrays.toString(mFlagMapping)); 666 L.e(error.toString()); 667 return null; 668 } 669 670 public BitSet getInvalidateAnyBitSet() { 671 return mInvalidateAnyFlags; 672 } 673 674 public int getInvalidateAnyFlagIndex() { 675 return mInvalidateAnyFlagIndex; 676 } 677 678 public Expr argListExpr(Iterable<Expr> expressions) { 679 return register(new ArgListExpr(mArgListIdCounter ++, expressions)); 680 } 681 682 public void setCurrentLocationInFile(Location location) { 683 mCurrentLocationInFile = location; 684 } 685 686 public Expr listenerExpr(Expr expression, String name, ModelClass listenerType, 687 ModelMethod listenerMethod) { 688 return register(new ListenerExpr(expression, name, listenerType, listenerMethod)); 689 } 690 691 public FieldAssignmentExpr assignment(Expr target, String name, Expr value) { 692 return register(new FieldAssignmentExpr(target, name, value)); 693 } 694 695 public Map<String, CallbackWrapper> getCallbackWrappers() { 696 return mCallbackWrappers; 697 } 698 699 public CallbackWrapper callbackWrapper(ModelClass klass, ModelMethod method) { 700 final String key = CallbackWrapper.uniqueKey(klass, method); 701 CallbackWrapper wrapper = mCallbackWrappers.get(key); 702 if (wrapper == null) { 703 wrapper = new CallbackWrapper(klass, method); 704 mCallbackWrappers.put(key, wrapper); 705 } 706 return wrapper; 707 } 708 709 public LambdaExpr lambdaExpr(Expr expr, CallbackExprModel callbackExprModel) { 710 return register(new LambdaExpr(expr, callbackExprModel)); 711 } 712 713 public IdentifierExpr findIdentifier(String name) { 714 for (Expr expr : mExprMap.values()) { 715 if (expr instanceof IdentifierExpr && name.equals(((IdentifierExpr) expr).getName())) { 716 return (IdentifierExpr) expr; 717 } 718 } 719 return null; 720 } 721 } 722