ExprModel.java revision 0c2ed0cbaee2f206e926bfc780b05e9f1e52b551
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 17package android.databinding.tool.expr; 18 19import com.google.common.base.Optional; 20import com.google.common.base.Preconditions; 21import com.google.common.base.Predicate; 22import com.google.common.collect.Iterables; 23import com.google.common.collect.Lists; 24 25import android.databinding.tool.reflection.ModelAnalyzer; 26import android.databinding.tool.reflection.ModelClass; 27import android.databinding.tool.util.L; 28import android.databinding.tool.writer.FlagSet; 29 30import java.util.ArrayList; 31import java.util.Arrays; 32import java.util.BitSet; 33import java.util.HashMap; 34import java.util.List; 35import java.util.Map; 36 37public class ExprModel { 38 39 Map<String, Expr> mExprMap = new HashMap<String, Expr>(); 40 41 List<Expr> mBindingExpressions = new ArrayList<Expr>(); 42 43 private int mInvalidateableFieldLimit = 0; 44 45 private int mRequirementIdCount = 0; 46 47 // each arg list receives a unique id even if it is the same arguments and method. 48 private int mArgListIdCounter = 0; 49 50 private static final String TRUE_KEY_SUFFIX = "== true"; 51 private static final String FALSE_KEY_SUFFIX = "== false"; 52 53 /** 54 * Any expression can be invalidated by invalidating this flag. 55 */ 56 private BitSet mInvalidateAnyFlags; 57 58 /** 59 * Used by code generation. Keeps the list of expressions that are waiting to be evaluated. 60 */ 61 private List<Expr> mPendingExpressions; 62 63 /** 64 * Used for converting flags into identifiers while debugging. 65 */ 66 private String[] mFlagMapping; 67 68 private BitSet mInvalidateableFlags; 69 private BitSet mConditionalFlags; 70 71 private int mFlagBucketCount;// how many buckets we use to identify flags 72 73 private List<Expr> mObservables; 74 75 private boolean mSealed = false; 76 77 private Map<String, String> mImports = new HashMap<String, String>(); 78 79 /** 80 * Adds the expression to the list of expressions and returns it. 81 * If it already exists, returns existing one. 82 * 83 * @param expr The new parsed expression 84 * @return The expression itself or another one if the same thing was parsed before 85 */ 86 public <T extends Expr> T register(T expr) { 87 Preconditions.checkState(!mSealed, "Cannot add expressions to a model after it is sealed"); 88 T existing = (T) mExprMap.get(expr.getUniqueKey()); 89 if (existing != null) { 90 Preconditions.checkState(expr.getParents().isEmpty(), 91 "If an expression already exists, it should've never been added to a parent," 92 + "if thats the case, somewhere we are creating an expression w/o" 93 + "calling expression model"); 94 // tell the expr that it is being swapped so that if it was added to some other expr 95 // as a parent, those can swap their references 96 expr.onSwappedWith(existing); 97 return existing; 98 } 99 mExprMap.put(expr.getUniqueKey(), expr); 100 expr.setModel(this); 101 return expr; 102 } 103 104 public void unregister(Expr expr) { 105 mExprMap.remove(expr.getUniqueKey()); 106 } 107 108 public Map<String, Expr> getExprMap() { 109 return mExprMap; 110 } 111 112 public int size() { 113 return mExprMap.size(); 114 } 115 116 public ComparisonExpr comparison(String op, Expr left, Expr right) { 117 return register(new ComparisonExpr(op, left, right)); 118 } 119 120 public InstanceOfExpr instanceOfOp(Expr expr, String type) { 121 return register(new InstanceOfExpr(expr, type)); 122 } 123 124 public FieldAccessExpr field(Expr parent, String name) { 125 return register(new FieldAccessExpr(parent, name)); 126 } 127 128 public FieldAccessExpr observableField(Expr parent, String name) { 129 return register(new FieldAccessExpr(parent, name, true)); 130 } 131 132 public SymbolExpr symbol(String text, Class type) { 133 return register(new SymbolExpr(text, type)); 134 } 135 136 public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) { 137 return register(new TernaryExpr(pred, ifTrue, ifFalse)); 138 } 139 140 public IdentifierExpr identifier(String name) { 141 return register(new IdentifierExpr(name)); 142 } 143 144 public StaticIdentifierExpr staticIdentifier(String name) { 145 return register(new StaticIdentifierExpr(name)); 146 } 147 148 /** 149 * Creates a static identifier for the given class or returns the existing one. 150 */ 151 public StaticIdentifierExpr staticIdentifierFor(final ModelClass modelClass) { 152 final String type = modelClass.getCanonicalName(); 153 Optional<Expr> existing = Iterables.tryFind(mExprMap.values(), new Predicate<Expr>() { 154 @Override 155 public boolean apply(Expr input) { 156 if (!(input instanceof StaticIdentifierExpr)) { 157 return false; 158 } 159 StaticIdentifierExpr id = (StaticIdentifierExpr) input; 160 return id.getUserDefinedType().equals(type); 161 } 162 }); 163 if (existing.isPresent()) { 164 return (StaticIdentifierExpr) existing.get(); 165 } 166 167 // does not exist. Find a name for it. 168 int cnt = 0; 169 int dotIndex = type.lastIndexOf("."); 170 String baseName; 171 Preconditions.checkArgument(dotIndex < type.length() - 1, "Invalid type %s", type); 172 if (dotIndex == -1) { 173 baseName = type; 174 } else { 175 baseName = type.substring(dotIndex + 1); 176 } 177 while (true) { 178 String candidate = cnt == 0 ? baseName : baseName + cnt; 179 if (!mImports.containsKey(candidate)) { 180 return addImport(candidate, type); 181 } 182 cnt ++; 183 Preconditions.checkState(cnt < 100, "Failed to create an import for " + type); 184 } 185 } 186 187 public MethodCallExpr methodCall(Expr target, String name, List<Expr> args) { 188 return register(new MethodCallExpr(target, name, args)); 189 } 190 191 public MathExpr math(Expr left, String op, Expr right) { 192 return register(new MathExpr(left, op, right)); 193 } 194 195 public TernaryExpr logical(Expr left, String op, Expr right) { 196 if ("&&".equals(op)) { 197 // left && right 198 // left ? right : false 199 return register(new TernaryExpr(left, right, symbol("false", boolean.class))); 200 } else { 201 // left || right 202 // left ? true : right 203 return register(new TernaryExpr(left, symbol("true", boolean.class), right)); 204 } 205 } 206 207 public BitShiftExpr bitshift(Expr left, String op, Expr right) { 208 return register(new BitShiftExpr(left, op, right)); 209 } 210 211 public UnaryExpr unary(String op, Expr expr) { 212 return register(new UnaryExpr(op, expr)); 213 } 214 215 public Expr group(Expr grouped) { 216 return register(new GroupExpr(grouped)); 217 } 218 219 public Expr resourceExpr(String packageName, String resourceType, String resourceName, 220 List<Expr> args) { 221 return register(new ResourceExpr(packageName, resourceType, resourceName, args)); 222 } 223 224 public Expr bracketExpr(Expr variableExpr, Expr argExpr) { 225 return register(new BracketExpr(variableExpr, argExpr)); 226 } 227 228 public Expr castExpr(String type, Expr expr) { 229 return register(new CastExpr(type, expr)); 230 } 231 232 public List<Expr> getBindingExpressions() { 233 return mBindingExpressions; 234 } 235 236 public StaticIdentifierExpr addImport(String alias, String type) { 237 Preconditions.checkState(!mImports.containsKey(alias), 238 "%s has already been defined as %s", alias, type); 239 final StaticIdentifierExpr id = staticIdentifier(alias); 240 L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName()); 241 id.setUserDefinedType(type); 242 mImports.put(alias, type); 243 return id; 244 } 245 246 public Map<String, String> getImports() { 247 return mImports; 248 } 249 250 /** 251 * The actual thingy that is set on the binding target. 252 * 253 * Input must be already registered 254 */ 255 public Expr bindingExpr(Expr bindingExpr) { 256 Preconditions.checkArgument(mExprMap.containsKey(bindingExpr.getUniqueKey()), 257 "Main expression should already be registered"); 258 if (!mBindingExpressions.contains(bindingExpr)) { 259 mBindingExpressions.add(bindingExpr); 260 } 261 return bindingExpr; 262 } 263 264 public List<Expr> getObservables() { 265 return mObservables; 266 } 267 268 public void seal() { 269 seal(null); 270 } 271 /** 272 * Give id to each expression. Will be useful if we serialize. 273 */ 274 public void seal(ResolveListenersCallback resolveListeners) { 275 L.d("sealing model"); 276 List<Expr> notifiableExpressions = new ArrayList<Expr>(); 277 //ensure class analyzer. We need to know observables at this point 278 final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); 279 updateExpressions(modelAnalyzer); 280 281 if (resolveListeners != null) { 282 resolveListeners.resolveListeners(); 283 } 284 285 int counter = 0; 286 final Iterable<Expr> observables = filterObservables(modelAnalyzer); 287 List<String> flagMapping = Lists.newArrayList(); 288 mObservables = Lists.newArrayList(); 289 for (Expr expr : observables) { 290 // observables gets initial ids 291 flagMapping.add(expr.getUniqueKey()); 292 expr.setId(counter++); 293 mObservables.add(expr); 294 notifiableExpressions.add(expr); 295 L.d("observable %s", expr.getUniqueKey()); 296 } 297 298 // non-observable identifiers gets next ids 299 final Iterable<Expr> nonObservableIds = filterNonObservableIds(modelAnalyzer); 300 for (Expr expr : nonObservableIds) { 301 flagMapping.add(expr.getUniqueKey()); 302 expr.setId(counter++); 303 notifiableExpressions.add(expr); 304 L.d("non-observable %s", expr.getUniqueKey()); 305 } 306 307 // descendants of observables gets following ids 308 for (Expr expr : observables) { 309 for (Expr parent : expr.getParents()) { 310 if (parent.hasId()) { 311 continue;// already has some id, means observable 312 } 313 // only fields earn an id 314 if (parent instanceof FieldAccessExpr) { 315 FieldAccessExpr fae = (FieldAccessExpr) parent; 316 L.d("checking field access expr %s. getter: %s", fae,fae.getGetter()); 317 if (fae.isDynamic() && fae.getGetter().canBeInvalidated()) { 318 flagMapping.add(parent.getUniqueKey()); 319 parent.setId(counter++); 320 notifiableExpressions.add(parent); 321 L.d("notifiable field %s : %s for %s : %s", parent.getUniqueKey(), 322 Integer.toHexString(System.identityHashCode(parent)), 323 expr.getUniqueKey(), 324 Integer.toHexString(System.identityHashCode(expr))); 325 } 326 } 327 } 328 } 329 330 // non-dynamic binding expressions receive some ids so that they can be invalidated 331 L.d("list of binding expressions"); 332 for (int i = 0; i < mBindingExpressions.size(); i++) { 333 L.d("[%d] %s", i, mBindingExpressions.get(i)); 334 } 335 // we don't assign ids to constant binding expressions because now invalidateAll has its own 336 // flag. 337 338 for (Expr expr : notifiableExpressions) { 339 expr.enableDirectInvalidation(); 340 } 341 342 // make sure all dependencies are resolved to avoid future race conditions 343 for (Expr expr : mExprMap.values()) { 344 expr.getDependencies(); 345 } 346 final int invalidateAnyFlagIndex = counter ++; 347 flagMapping.add("INVALIDATE ANY"); 348 mInvalidateableFieldLimit = counter; 349 mInvalidateableFlags = new BitSet(); 350 for (int i = 0; i < mInvalidateableFieldLimit; i++) { 351 mInvalidateableFlags.set(i, true); 352 } 353 354 // make sure all dependencies are resolved to avoid future race conditions 355 for (Expr expr : mExprMap.values()) { 356 if (expr.isConditional()) { 357 L.d("requirement id for %s is %d", expr, counter); 358 expr.setRequirementId(counter); 359 flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX); 360 flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX); 361 counter += 2; 362 } 363 } 364 mConditionalFlags = new BitSet(); 365 for (int i = mInvalidateableFieldLimit; i < counter; i++) { 366 mConditionalFlags.set(i, true); 367 } 368 mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2; 369 370 // everybody gets an id 371 for (Map.Entry<String, Expr> entry : mExprMap.entrySet()) { 372 final Expr value = entry.getValue(); 373 if (!value.hasId()) { 374 value.setId(counter++); 375 } 376 } 377 378 mFlagMapping = new String[flagMapping.size()]; 379 flagMapping.toArray(mFlagMapping); 380 381 mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize); 382 mInvalidateAnyFlags = new BitSet(); 383 mInvalidateAnyFlags.set(invalidateAnyFlagIndex, true); 384 385 for (Expr expr : mExprMap.values()) { 386 expr.getShouldReadFlagsWithConditionals(); 387 } 388 389 for (Expr expr : mExprMap.values()) { 390 // ensure all types are calculated 391 expr.getResolvedType(); 392 } 393 394 mSealed = true; 395 } 396 397 /** 398 * Run updateExpr on each binding expression until no new expressions are added. 399 * <p> 400 * Some expressions (e.g. field access) may replace themselves and add/remove new dependencies 401 * so we need to make sure each expression's update is called at least once. 402 */ 403 private void updateExpressions(ModelAnalyzer modelAnalyzer) { 404 int startSize = -1; 405 while (startSize != mExprMap.size()) { 406 startSize = mExprMap.size(); 407 ArrayList<Expr> exprs = new ArrayList<Expr>(mBindingExpressions); 408 for (Expr expr : exprs) { 409 expr.updateExpr(modelAnalyzer); 410 } 411 } 412 } 413 414 public int getFlagBucketCount() { 415 return mFlagBucketCount; 416 } 417 418 public int getTotalFlagCount() { 419 return mRequirementIdCount * 2 + mInvalidateableFieldLimit; 420 } 421 422 public int getInvalidateableFieldLimit() { 423 return mInvalidateableFieldLimit; 424 } 425 426 public String[] getFlagMapping() { 427 return mFlagMapping; 428 } 429 430 public String getFlag(int id) { 431 return mFlagMapping[id]; 432 } 433 434 private Iterable<Expr> filterNonObservableIds(final ModelAnalyzer modelAnalyzer) { 435 return Iterables.filter(mExprMap.values(), new Predicate<Expr>() { 436 @Override 437 public boolean apply(Expr input) { 438 return input instanceof IdentifierExpr 439 && !input.hasId() 440 && !input.isObservable() 441 && input.isDynamic(); 442 } 443 }); 444 } 445 446 private Iterable<Expr> filterObservables(final ModelAnalyzer modelAnalyzer) { 447 return Iterables.filter(mExprMap.values(), new Predicate<Expr>() { 448 @Override 449 public boolean apply(Expr input) { 450 return input.isObservable(); 451 } 452 }); 453 } 454 455 public List<Expr> getPendingExpressions() { 456 if (mPendingExpressions == null) { 457 mPendingExpressions = Lists.newArrayList(); 458 for (Expr expr : mExprMap.values()) { 459 if (!expr.isRead() && expr.isDynamic()) { 460 mPendingExpressions.add(expr); 461 } 462 } 463 } 464 return mPendingExpressions; 465 } 466 467 public boolean markBitsRead() { 468 // each has should read flags, we set them back on them 469 List<Expr> markedSomeFlagsRead = Lists.newArrayList(); 470 for (Expr expr : filterShouldRead(getPendingExpressions())) { 471 expr.markFlagsAsRead(expr.getShouldReadFlags()); 472 markedSomeFlagsRead.add(expr); 473 } 474 return pruneDone(markedSomeFlagsRead); 475 } 476 477 private boolean pruneDone(List<Expr> markedSomeFlagsAsRead) { 478 boolean marked = true; 479 List<Expr> markedAsReadList = Lists.newArrayList(); 480 while (marked) { 481 marked = false; 482 for (Expr expr : mExprMap.values()) { 483 if (expr.isRead()) { 484 continue; 485 } 486 if (expr.markAsReadIfDone()) { 487 L.d("marked %s as read ", expr.getUniqueKey()); 488 marked = true; 489 markedAsReadList.add(expr); 490 markedSomeFlagsAsRead.remove(expr); 491 } 492 } 493 } 494 boolean elevated = false; 495 for (Expr markedAsRead : markedAsReadList) { 496 for (Dependency dependency : markedAsRead.getDependants()) { 497 if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) { 498 elevated = true; 499 } 500 } 501 } 502 for (Expr partialRead : markedSomeFlagsAsRead) { 503 boolean allPathsAreSatisfied = partialRead.getAllCalculationPaths() 504 .areAllPathsSatisfied(partialRead.mReadSoFar); 505 if (!allPathsAreSatisfied) { 506 continue; 507 } 508 for (Dependency dependency : partialRead.getDependants()) { 509 if (dependency.getDependant().considerElevatingConditionals(partialRead)) { 510 elevated = true; 511 } 512 } 513 } 514 if (elevated) { 515 // some conditionals are elevated. We should re-calculate flags 516 for (Expr expr : getPendingExpressions()) { 517 if (!expr.isRead()) { 518 expr.invalidateReadFlags(); 519 } 520 } 521 mPendingExpressions = null; 522 } 523 return elevated; 524 } 525 526 public static Iterable<Expr> filterShouldRead(Iterable<Expr> exprs) { 527 return toCollection(Iterables.filter(exprs, sShouldReadPred)); 528 } 529 530 public static List<Expr> toCollection(Iterable<Expr> iterable) { 531 return Arrays.asList(Iterables.toArray(iterable, Expr.class)); 532 } 533 534 private static final Predicate<Expr> sShouldReadPred = new Predicate<Expr>() { 535 @Override 536 public boolean apply(final Expr expr) { 537 return !expr.getShouldReadFlags().isEmpty() && !Iterables.any( 538 expr.getDependencies(), new Predicate<Dependency>() { 539 @Override 540 public boolean apply(Dependency dependency) { 541 final boolean result = dependency.isConditional() || 542 dependency.getOther().hasNestedCannotRead(); 543 return result; 544 } 545 }); 546 } 547 }; 548 549 /** 550 * May return null if flag is equal to invalidate any flag. 551 */ 552 public Expr findFlagExpression(int flag) { 553 if (mInvalidateAnyFlags.get(flag)) { 554 return null; 555 } 556 final String key = mFlagMapping[flag]; 557 if (mExprMap.containsKey(key)) { 558 return mExprMap.get(key); 559 } 560 int falseIndex = key.indexOf(FALSE_KEY_SUFFIX); 561 if (falseIndex > -1) { 562 final String trimmed = key.substring(0, falseIndex); 563 return mExprMap.get(trimmed); 564 } 565 int trueIndex = key.indexOf(TRUE_KEY_SUFFIX); 566 if (trueIndex > -1) { 567 final String trimmed = key.substring(0, trueIndex); 568 return mExprMap.get(trimmed); 569 } 570 // log everything we call 571 StringBuilder error = new StringBuilder(); 572 error.append("cannot find flag:").append(flag).append("\n"); 573 error.append("invalidate any flag:").append(mInvalidateAnyFlags).append("\n"); 574 error.append("key:").append(key).append("\n"); 575 error.append("flag mapping:").append(Arrays.toString(mFlagMapping)); 576 Preconditions.checkArgument(false, error.toString()); 577 return null; 578 } 579 580 public BitSet getInvalidateAnyBitSet() { 581 return mInvalidateAnyFlags; 582 } 583 584 public Expr argListExpr(Iterable<Expr> expressions) { 585 return register(new ArgListExpr(mArgListIdCounter ++, expressions)); 586 } 587 588 public interface ResolveListenersCallback { 589 void resolveListeners(); 590 } 591} 592