ExprModelTest.java revision 9784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3
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 org.apache.commons.lang3.ArrayUtils; 20import org.junit.Before; 21import org.junit.Rule; 22import org.junit.Test; 23import org.junit.rules.TestWatcher; 24import org.junit.runner.Description; 25 26import android.databinding.BaseObservable; 27import android.databinding.Bindable; 28import android.databinding.tool.LayoutBinder; 29import android.databinding.tool.MockLayoutBinder; 30import android.databinding.tool.reflection.ModelAnalyzer; 31import android.databinding.tool.reflection.ModelClass; 32import android.databinding.tool.reflection.java.JavaAnalyzer; 33import android.databinding.tool.store.Location; 34import android.databinding.tool.util.L; 35import android.databinding.tool.writer.KCode; 36 37import java.util.ArrayList; 38import java.util.Arrays; 39import java.util.BitSet; 40import java.util.Collections; 41import java.util.List; 42 43import static org.junit.Assert.assertEquals; 44import static org.junit.Assert.assertFalse; 45import static org.junit.Assert.assertNotNull; 46import static org.junit.Assert.assertNull; 47import static org.junit.Assert.assertSame; 48import static org.junit.Assert.assertTrue; 49 50public class ExprModelTest { 51 52 private static class DummyExpr extends Expr { 53 54 String mKey; 55 56 public DummyExpr(String key, DummyExpr... children) { 57 super(children); 58 mKey = key; 59 } 60 61 @Override 62 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 63 return modelAnalyzer.findClass(Integer.class); 64 } 65 66 @Override 67 protected List<Dependency> constructDependencies() { 68 return constructDynamicChildrenDependencies(); 69 } 70 71 @Override 72 protected String computeUniqueKey() { 73 return mKey + super.computeUniqueKey(); 74 } 75 76 @Override 77 protected KCode generateCode() { 78 return new KCode(); 79 } 80 } 81 82 ExprModel mExprModel; 83 84 @Rule 85 public TestWatcher mTestWatcher = new TestWatcher() { 86 @Override 87 protected void failed(Throwable e, Description description) { 88 if (mExprModel != null && mExprModel.getFlagMapping() != null) { 89 final String[] mapping = mExprModel.getFlagMapping(); 90 for (int i = 0; i < mapping.length; i++) { 91 L.d("flag %d: %s", i, mapping[i]); 92 } 93 } 94 } 95 }; 96 97 @Before 98 public void setUp() throws Exception { 99 JavaAnalyzer.initForTests(); 100 mExprModel = new ExprModel(); 101 } 102 103 @Test 104 public void testAddNormal() { 105 final DummyExpr d = new DummyExpr("a"); 106 assertSame(d, mExprModel.register(d)); 107 assertSame(d, mExprModel.register(d)); 108 assertEquals(1, mExprModel.mExprMap.size()); 109 } 110 111 @Test 112 public void testAddDupe1() { 113 final DummyExpr d = new DummyExpr("a"); 114 assertSame(d, mExprModel.register(d)); 115 assertSame(d, mExprModel.register(new DummyExpr("a"))); 116 assertEquals(1, mExprModel.mExprMap.size()); 117 } 118 119 @Test 120 public void testAddMultiple() { 121 mExprModel.register(new DummyExpr("a")); 122 mExprModel.register(new DummyExpr("b")); 123 assertEquals(2, mExprModel.mExprMap.size()); 124 } 125 126 127 @Test 128 public void testAddWithChildren() { 129 DummyExpr a = new DummyExpr("a"); 130 DummyExpr b = new DummyExpr("b"); 131 DummyExpr c = new DummyExpr("c", a, b); 132 mExprModel.register(c); 133 DummyExpr a2 = new DummyExpr("a"); 134 DummyExpr b2 = new DummyExpr("b"); 135 DummyExpr c2 = new DummyExpr("c", a, b); 136 assertEquals(c, mExprModel.register(c2)); 137 } 138 139 @Test 140 public void testShouldRead() { 141 MockLayoutBinder lb = new MockLayoutBinder(); 142 mExprModel = lb.getModel(); 143 IdentifierExpr a = lb.addVariable("a", "java.lang.String", null); 144 IdentifierExpr b = lb.addVariable("b", "java.lang.String", null); 145 IdentifierExpr c = lb.addVariable("c", "java.lang.String", null); 146 lb.parse("a == null ? b : c", null); 147 mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); 148 lb.getModel().seal(); 149 List<Expr> shouldRead = getShouldRead(); 150 // a and a == null 151 assertEquals(2, shouldRead.size()); 152 final List<Expr> readFirst = getReadFirst(shouldRead, null); 153 assertEquals(1, readFirst.size()); 154 final Expr first = readFirst.get(0); 155 assertSame(a, first); 156 // now , assume we've read this 157 final BitSet shouldReadFlags = first.getShouldReadFlags(); 158 assertNotNull(shouldReadFlags); 159 } 160 161 @Test 162 public void testReadConstantTernary() { 163 MockLayoutBinder lb = new MockLayoutBinder(); 164 mExprModel = lb.getModel(); 165 IdentifierExpr a = lb.addVariable("a", "java.lang.String", null); 166 IdentifierExpr b = lb.addVariable("b", "java.lang.String", null); 167 TernaryExpr ternaryExpr = parse(lb, "true ? a : b", TernaryExpr.class); 168 mExprModel.seal(); 169 List<Expr> shouldRead = getShouldRead(); 170 assertExactMatch(shouldRead, ternaryExpr.getPred()); 171 List<Expr> first = getReadFirst(shouldRead); 172 assertExactMatch(first, ternaryExpr.getPred()); 173 mExprModel.markBitsRead(); 174 shouldRead = getShouldRead(); 175 assertExactMatch(shouldRead, a, b, ternaryExpr); 176 first = getReadFirst(shouldRead); 177 assertExactMatch(first, a, b); 178 List<Expr> justRead = new ArrayList<Expr>(); 179 justRead.add(a); 180 justRead.add(b); 181 first = filterOut(getReadFirst(shouldRead, justRead), justRead); 182 assertExactMatch(first, ternaryExpr); 183 assertFalse(mExprModel.markBitsRead()); 184 } 185 186 @Test 187 public void testTernaryWithPlus() { 188 MockLayoutBinder lb = new MockLayoutBinder(); 189 mExprModel = lb.getModel(); 190 IdentifierExpr user = lb 191 .addVariable("user", "android.databinding.tool.expr.ExprModelTest.User", 192 null); 193 MathExpr parsed = parse(lb, "user.name + \" \" + (user.lastName ?? \"\")", MathExpr.class); 194 mExprModel.seal(); 195 List<Expr> toRead = getShouldRead(); 196 List<Expr> readNow = getReadFirst(toRead); 197 assertEquals(1, readNow.size()); 198 assertSame(user, readNow.get(0)); 199 List<Expr> justRead = new ArrayList<Expr>(); 200 justRead.add(user); 201 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 202 assertEquals(2, readNow.size()); //user.name && user.lastName 203 justRead.addAll(readNow); 204 // user.lastname (T, F), user.name + " " 205 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 206 assertEquals(2, readNow.size()); //user.name && user.lastName 207 justRead.addAll(readNow); 208 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 209 assertEquals(0, readNow.size()); 210 mExprModel.markBitsRead(); 211 212 toRead = getShouldRead(); 213 assertEquals(2, toRead.size()); 214 justRead.clear(); 215 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 216 assertEquals(1, readNow.size()); 217 assertSame(parsed.getRight(), readNow.get(0)); 218 justRead.addAll(readNow); 219 220 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 221 assertEquals(1, readNow.size()); 222 assertSame(parsed, readNow.get(0)); 223 justRead.addAll(readNow); 224 225 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 226 assertEquals(0, readNow.size()); 227 mExprModel.markBitsRead(); 228 assertEquals(0, getShouldRead().size()); 229 } 230 231 private List<Expr> filterOut(List<Expr> itr, final List<Expr> exclude) { 232 List<Expr> result = new ArrayList<Expr>(); 233 for (Expr expr : itr) { 234 if (!exclude.contains(expr)) { 235 result.add(expr); 236 } 237 } 238 return result; 239 } 240 241 @Test 242 public void testTernaryInsideTernary() { 243 MockLayoutBinder lb = new MockLayoutBinder(); 244 mExprModel = lb.getModel(); 245 IdentifierExpr cond1 = lb.addVariable("cond1", "boolean", null); 246 IdentifierExpr cond2 = lb.addVariable("cond2", "boolean", null); 247 248 IdentifierExpr a = lb.addVariable("a", "boolean", null); 249 IdentifierExpr b = lb.addVariable("b", "boolean", null); 250 IdentifierExpr c = lb.addVariable("c", "boolean", null); 251 252 final TernaryExpr ternaryExpr = parse(lb, "cond1 ? cond2 ? a : b : c", TernaryExpr.class); 253 final TernaryExpr innerTernary = (TernaryExpr) ternaryExpr.getIfTrue(); 254 mExprModel.seal(); 255 256 List<Expr> toRead = getShouldRead(); 257 assertEquals(1, toRead.size()); 258 assertEquals(ternaryExpr.getPred(), toRead.get(0)); 259 260 List<Expr> readNow = getReadFirst(toRead); 261 assertEquals(1, readNow.size()); 262 assertEquals(ternaryExpr.getPred(), readNow.get(0)); 263 int cond1True = ternaryExpr.getRequirementFlagIndex(true); 264 int cond1False = ternaryExpr.getRequirementFlagIndex(false); 265 // ok, it is read now. 266 mExprModel.markBitsRead(); 267 268 // now it should read cond2 or c, depending on the flag from first 269 toRead = getShouldRead(); 270 assertEquals(2, toRead.size()); 271 assertExactMatch(toRead, ternaryExpr.getIfFalse(), innerTernary.getPred()); 272 assertFlags(ternaryExpr.getIfFalse(), cond1False); 273 assertFlags(ternaryExpr.getIfTrue(), cond1True); 274 275 mExprModel.markBitsRead(); 276 277 // now it should read a or b, innerTernary, outerTernary 278 toRead = getShouldRead(); 279 assertExactMatch(toRead, innerTernary.getIfTrue(), innerTernary.getIfFalse(), ternaryExpr, 280 innerTernary); 281 assertFlags(innerTernary.getIfTrue(), innerTernary.getRequirementFlagIndex(true)); 282 assertFlags(innerTernary.getIfFalse(), innerTernary.getRequirementFlagIndex(false)); 283 assertFalse(mExprModel.markBitsRead()); 284 } 285 286 @Test 287 public void testRequirementFlags() { 288 MockLayoutBinder lb = new MockLayoutBinder(); 289 mExprModel = lb.getModel(); 290 IdentifierExpr a = lb.addVariable("a", "java.lang.String", null); 291 IdentifierExpr b = lb.addVariable("b", "java.lang.String", null); 292 IdentifierExpr c = lb.addVariable("c", "java.lang.String", null); 293 IdentifierExpr d = lb.addVariable("d", "java.lang.String", null); 294 IdentifierExpr e = lb.addVariable("e", "java.lang.String", null); 295 final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e", null); 296 assertTrue(aTernary instanceof TernaryExpr); 297 final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue(); 298 assertTrue(bTernary instanceof TernaryExpr); 299 final Expr aIsNull = mExprModel 300 .comparison("==", a, mExprModel.symbol("null", Object.class)); 301 final Expr bIsNull = mExprModel 302 .comparison("==", b, mExprModel.symbol("null", Object.class)); 303 lb.getModel().seal(); 304 List<Expr> shouldRead = getShouldRead(); 305 // a and a == null 306 assertEquals(2, shouldRead.size()); 307 assertFalse(a.getShouldReadFlags().isEmpty()); 308 assertTrue(a.getShouldReadFlags().get(a.getId())); 309 assertTrue(b.getShouldReadFlags().isEmpty()); 310 assertTrue(c.getShouldReadFlags().isEmpty()); 311 assertTrue(d.getShouldReadFlags().isEmpty()); 312 assertTrue(e.getShouldReadFlags().isEmpty()); 313 314 List<Expr> readFirst = getReadFirst(shouldRead, null); 315 assertEquals(1, readFirst.size()); 316 final Expr first = readFirst.get(0); 317 assertSame(a, first); 318 assertTrue(mExprModel.markBitsRead()); 319 for (Expr expr : mExprModel.getPendingExpressions()) { 320 assertNull(expr.mShouldReadFlags); 321 } 322 shouldRead = getShouldRead(); 323 assertExactMatch(shouldRead, e, b, bIsNull); 324 325 assertFlags(e, aTernary.getRequirementFlagIndex(false)); 326 327 assertFlags(b, aTernary.getRequirementFlagIndex(true)); 328 assertFlags(bIsNull, aTernary.getRequirementFlagIndex(true)); 329 assertTrue(mExprModel.markBitsRead()); 330 shouldRead = getShouldRead(); 331 assertEquals(4, shouldRead.size()); 332 assertTrue(shouldRead.contains(c)); 333 assertTrue(shouldRead.contains(d)); 334 assertTrue(shouldRead.contains(aTernary)); 335 assertTrue(shouldRead.contains(bTernary)); 336 337 assertTrue(c.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(true))); 338 assertEquals(1, c.getShouldReadFlags().cardinality()); 339 340 assertTrue(d.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(false))); 341 assertEquals(1, d.getShouldReadFlags().cardinality()); 342 343 assertTrue(bTernary.getShouldReadFlags().get(aTernary.getRequirementFlagIndex(true))); 344 assertEquals(1, bTernary.getShouldReadFlags().cardinality()); 345 // +1 for invalidate all flag 346 assertEquals(6, aTernary.getShouldReadFlags().cardinality()); 347 for (Expr expr : new Expr[]{a, b, c, d, e}) { 348 assertTrue(aTernary.getShouldReadFlags().get(expr.getId())); 349 } 350 351 readFirst = getReadFirst(shouldRead); 352 assertEquals(2, readFirst.size()); 353 assertTrue(readFirst.contains(c)); 354 assertTrue(readFirst.contains(d)); 355 assertFalse(mExprModel.markBitsRead()); 356 } 357 358 @Test 359 public void testPostConditionalDependencies() { 360 MockLayoutBinder lb = new MockLayoutBinder(); 361 mExprModel = lb.getModel(); 362 363 IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName(), null); 364 IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName(), null); 365 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(), null); 366 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(), null); 367 IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName(), null); 368 IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName(), null); 369 IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName(), null); 370 TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class); 371 TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx`" 372 + " + u2.getCond(e) ", TernaryExpr.class); 373 Expr abCmp = abTernary.getPred(); 374 Expr bcCmp = bcTernary.getPred(); 375 Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred(); 376 final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse(); 377 Expr u2GetCondE = xxPlusU2getCondE.getRight(); 378 Expr u1Name = abTernary.getIfTrue(); 379 Expr u2Name = abTernary.getIfFalse(); 380 Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue(); 381 Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse(); 382 383 mExprModel.seal(); 384 List<Expr> shouldRead = getShouldRead(); 385 386 assertExactMatch(shouldRead, a, b, c, abCmp, bcCmp); 387 388 List<Expr> firstRead = getReadFirst(shouldRead); 389 390 assertExactMatch(firstRead, a, b, c); 391 392 assertFlags(a, a, b, u1, u2, u1Name, u2Name); 393 assertFlags(b, a, b, u1, u2, u1Name, u2Name, c, d, u1LastName, u2LastName, e); 394 assertFlags(c, b, c, u1, d, u1LastName, u2LastName, e); 395 assertFlags(abCmp, a, b, u1, u2, u1Name, u2Name); 396 assertFlags(bcCmp, b, c, u1, d, u1LastName, u2LastName, e); 397 398 assertTrue(mExprModel.markBitsRead()); 399 400 shouldRead = getShouldRead(); 401 Expr[] batch = {d, e, u1, u2, u1GetCondD, u2GetCondE, xxPlusU2getCondE, abTernary, 402 abTernary.getIfTrue(), abTernary.getIfFalse()}; 403 assertExactMatch(shouldRead, batch); 404 firstRead = getReadFirst(shouldRead); 405 assertExactMatch(firstRead, d, e, u1, u2); 406 407 assertFlags(d, bcTernary.getRequirementFlagIndex(true)); 408 assertFlags(e, bcTernary.getRequirementFlagIndex(false)); 409 assertFlags(u1, bcTernary.getRequirementFlagIndex(true), 410 abTernary.getRequirementFlagIndex(true)); 411 assertFlags(u2, bcTernary.getRequirementFlagIndex(false), 412 abTernary.getRequirementFlagIndex(false)); 413 414 assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true)); 415 assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false)); 416 assertFlags(xxPlusU2getCondE, bcTernary.getRequirementFlagIndex(false)); 417 assertFlags(abTernary, a, b, u1, u2, u1Name, u2Name); 418 assertFlags(abTernary.getIfTrue(), abTernary.getRequirementFlagIndex(true)); 419 assertFlags(abTernary.getIfFalse(), abTernary.getRequirementFlagIndex(false)); 420 421 assertTrue(mExprModel.markBitsRead()); 422 423 shouldRead = getShouldRead(); 424 // FIXME: there is no real case to read u1 anymore because if b>c was not true, 425 // u1.getCond(d) will never be set. Right now, we don't have mechanism to figure this out 426 // and also it does not affect correctness (just an unnecessary if stmt) 427 assertExactMatch(shouldRead, u1, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary); 428 firstRead = getReadFirst(shouldRead); 429 assertExactMatch(firstRead, u1, u2); 430 assertFlags(u1, bcTernary.getIfTrue().getRequirementFlagIndex(true)); 431 assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false)); 432 assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true)); 433 assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false)); 434 435 assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true)); 436 assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e); 437 438 assertFalse(mExprModel.markBitsRead()); 439 } 440 441 @Test 442 public void testCircularDependency() { 443 MockLayoutBinder lb = new MockLayoutBinder(); 444 mExprModel = lb.getModel(); 445 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(), 446 null); 447 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(), 448 null); 449 final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class); 450 mExprModel.seal(); 451 List<Expr> shouldRead = getShouldRead(); 452 assertExactMatch(shouldRead, a, abTernary.getPred()); 453 assertTrue(mExprModel.markBitsRead()); 454 shouldRead = getShouldRead(); 455 assertExactMatch(shouldRead, b, abTernary); 456 assertFalse(mExprModel.markBitsRead()); 457 } 458 459 @Test 460 public void testNestedCircularDependency() { 461 MockLayoutBinder lb = new MockLayoutBinder(); 462 mExprModel = lb.getModel(); 463 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(), 464 null); 465 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(), 466 null); 467 IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName(), 468 null); 469 final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class); 470 final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue(); 471 mExprModel.seal(); 472 List<Expr> shouldRead = getShouldRead(); 473 assertExactMatch(shouldRead, a, a3Ternary.getPred()); 474 assertTrue(mExprModel.markBitsRead()); 475 shouldRead = getShouldRead(); 476 assertExactMatch(shouldRead, c, c4Ternary.getPred()); 477 assertFlags(c, a3Ternary.getRequirementFlagIndex(true), 478 a3Ternary.getRequirementFlagIndex(false)); 479 assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true)); 480 } 481 482 @Test 483 public void testInterExprDependency() { 484 MockLayoutBinder lb = new MockLayoutBinder(); 485 mExprModel = lb.getModel(); 486 IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName(), 487 null); 488 final Expr uComment = parse(lb, "u.comment", FieldAccessExpr.class); 489 final TernaryExpr uTernary = parse(lb, "u.getUseComment ? u.comment : `xx`", TernaryExpr.class); 490 mExprModel.seal(); 491 assertTrue(uTernary.getPred().canBeInvalidated()); 492 List<Expr> shouldRead = getShouldRead(); 493 assertExactMatch(shouldRead, u, uComment, uTernary.getPred()); 494 assertTrue(mExprModel.markBitsRead()); 495 shouldRead = getShouldRead(); 496 assertExactMatch(shouldRead, uComment, uTernary); 497 } 498 499 @Test 500 public void testInterExprCircularDependency() { 501 MockLayoutBinder lb = new MockLayoutBinder(); 502 mExprModel = lb.getModel(); 503 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(), 504 null); 505 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(), 506 null); 507 final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class); 508 final TernaryExpr abTernary2 = parse(lb, "b > 3 ? b : a", TernaryExpr.class); 509 mExprModel.seal(); 510 List<Expr> shouldRead = getShouldRead(); 511 assertExactMatch(shouldRead, a, b, abTernary.getPred(), abTernary2.getPred()); 512 assertTrue(mExprModel.markBitsRead()); 513 shouldRead = getShouldRead(); 514 assertExactMatch(shouldRead, abTernary, abTernary2); 515 } 516 517 @Test 518 public void testInterExprCircularDependency2() { 519 MockLayoutBinder lb = new MockLayoutBinder(); 520 mExprModel = lb.getModel(); 521 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), 522 null); 523 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), 524 null); 525 final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class); 526 final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class); 527 mExprModel.seal(); 528 List<Expr> shouldRead = getShouldRead(); 529 assertExactMatch(shouldRead, a, b); 530 assertFlags(a, a, b); 531 assertFlags(b, a, b); 532 List<Expr> readFirst = getReadFirst(shouldRead); 533 assertExactMatch(readFirst, a, b); 534 assertTrue(mExprModel.markBitsRead()); 535 shouldRead = getShouldRead(); 536 assertExactMatch(shouldRead, abTernary, baTernary); 537 readFirst = getReadFirst(shouldRead); 538 assertExactMatch(readFirst, abTernary, baTernary); 539 assertFalse(mExprModel.markBitsRead()); 540 shouldRead = getShouldRead(); 541 assertEquals(0, shouldRead.size()); 542 } 543 544 @Test 545 public void testInterExprCircularDependency3() { 546 MockLayoutBinder lb = new MockLayoutBinder(); 547 mExprModel = lb.getModel(); 548 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), 549 null); 550 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), 551 null); 552 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(), 553 null); 554 final TernaryExpr abTernary = parse(lb, "a ? b : c", TernaryExpr.class); 555 final TernaryExpr abTernary2 = parse(lb, "b ? a : c", TernaryExpr.class); 556 mExprModel.seal(); 557 List<Expr> shouldRead = getShouldRead(); 558 assertExactMatch(shouldRead, a, b); 559 assertTrue(mExprModel.markBitsRead()); 560 shouldRead = getShouldRead(); 561 // read a and b again, this time for their dependencies and also the rest since everything 562 // is ready to be read 563 assertExactMatch(shouldRead, c, abTernary, abTernary2); 564 mExprModel.markBitsRead(); 565 shouldRead = getShouldRead(); 566 assertEquals(0, shouldRead.size()); 567 } 568 569 @Test 570 public void testInterExprCircularDependency4() { 571 MockLayoutBinder lb = new MockLayoutBinder(); 572 mExprModel = lb.getModel(); 573 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), 574 null); 575 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), 576 null); 577 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(), 578 null); 579 IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName(), 580 null); 581 final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class); 582 final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class); 583 final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class); 584 mExprModel.seal(); 585 List<Expr> shouldRead = getShouldRead(); 586 // check if a,b or c should be read. these are easily calculated from binding expressions' 587 // invalidation 588 assertExactMatch(shouldRead, c, a, b); 589 590 List<Expr> justRead = new ArrayList<Expr>(); 591 List<Expr> readFirst = getReadFirst(shouldRead); 592 assertExactMatch(readFirst, c, a, b); 593 Collections.addAll(justRead, a, b, c); 594 assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size()); 595 assertTrue(mExprModel.markBitsRead()); 596 shouldRead = getShouldRead(); 597 // if a and b are not invalid, a won't be read in the first step. But if c's expression 598 // is invalid and c == true, a must be read. Depending on a, d might be read as well. 599 // don't need to read b anymore because `a ? b : true` and `b ? a : false` has the same 600 // invalidation flags. 601 assertExactMatch(shouldRead, a, abTernary, baTernary); 602 justRead.clear(); 603 604 readFirst = getReadFirst(shouldRead); 605 // first must read `a`. 606 assertExactMatch(readFirst, a); 607 Collections.addAll(justRead, a); 608 609 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 610 assertExactMatch(readFirst, abTernary, baTernary); 611 Collections.addAll(justRead, abTernary, baTernary); 612 613 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 614 assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size()); 615 assertTrue(mExprModel.markBitsRead()); 616 617 shouldRead = getShouldRead(); 618 // now we can read adf ternary and c ternary 619 justRead.clear(); 620 assertExactMatch(shouldRead, d, cTernary.getIfTrue(), cTernary); 621 readFirst = getReadFirst(shouldRead); 622 assertExactMatch(readFirst, d); 623 Collections.addAll(justRead, d); 624 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 625 assertExactMatch(readFirst, cTernary.getIfTrue()); 626 Collections.addAll(justRead, cTernary.getIfTrue()); 627 628 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 629 assertExactMatch(readFirst, cTernary); 630 Collections.addAll(justRead, cTernary); 631 632 assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size()); 633 634 assertFalse(mExprModel.markBitsRead()); 635 } 636 637 @Test 638 public void testInterExprDeepDependency() { 639 MockLayoutBinder lb = new MockLayoutBinder(); 640 mExprModel = lb.getModel(); 641 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), null); 642 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), null); 643 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(), null); 644 final TernaryExpr t1 = parse(lb, "c ? (a ? b : true) : false", TernaryExpr.class); 645 final TernaryExpr t2 = parse(lb, "c ? (b ? a : false) : true", TernaryExpr.class); 646 final TernaryExpr abTernary = (TernaryExpr) t1.getIfTrue(); 647 final TernaryExpr baTernary = (TernaryExpr) t2.getIfTrue(); 648 mExprModel.seal(); 649 List<Expr> shouldRead = getShouldRead(); 650 assertExactMatch(shouldRead, c); 651 assertTrue(mExprModel.markBitsRead()); 652 shouldRead = getShouldRead(); 653 assertExactMatch(shouldRead, a, b); 654 assertTrue(mExprModel.markBitsRead()); 655 shouldRead = getShouldRead(); 656 assertExactMatch(shouldRead, a, b, t1.getIfTrue(), t2.getIfTrue(), t1, t2); 657 assertFlags(b, abTernary.getRequirementFlagIndex(true)); 658 assertFlags(a, baTernary.getRequirementFlagIndex(true)); 659 assertFalse(mExprModel.markBitsRead()); 660 } 661 662 @Test 663 public void testInterExprDependencyNotReadyYet() { 664 MockLayoutBinder lb = new MockLayoutBinder(); 665 mExprModel = lb.getModel(); 666 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), null); 667 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), null); 668 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(), null); 669 IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName(), null); 670 IdentifierExpr e = lb.addVariable("e", boolean.class.getCanonicalName(), null); 671 final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class); 672 final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class); 673 final TernaryExpr eaTernary = parse(lb, "e ? a : false", TernaryExpr.class); 674 mExprModel.seal(); 675 List<Expr> shouldRead = getShouldRead(); 676 assertExactMatch(shouldRead, b, c, e); 677 assertTrue(mExprModel.markBitsRead()); 678 shouldRead = getShouldRead(); 679 assertExactMatch(shouldRead, a, baTernary, eaTernary); 680 assertTrue(mExprModel.markBitsRead()); 681 shouldRead = getShouldRead(); 682 assertExactMatch(shouldRead, d, cTernary.getIfTrue(), cTernary); 683 assertFalse(mExprModel.markBitsRead()); 684 } 685 686 @Test 687 public void testNoFlagsForNonBindingStatic() { 688 MockLayoutBinder lb = new MockLayoutBinder(); 689 mExprModel = lb.getModel(); 690 lb.addVariable("a", int.class.getCanonicalName(), null); 691 final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class); 692 mExprModel.seal(); 693 // +1 for invalidate all flag 694 assertEquals(1, parsed.getRight().getInvalidFlags().cardinality()); 695 // +1 for invalidate all flag 696 assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality()); 697 // +1 for invalidate all flag 698 assertEquals(2, mExprModel.getInvalidateableFieldLimit()); 699 } 700 701 @Test 702 public void testFlagsForBindingStatic() { 703 MockLayoutBinder lb = new MockLayoutBinder(); 704 mExprModel = lb.getModel(); 705 lb.addVariable("a", int.class.getCanonicalName(), null); 706 final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class); 707 final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class); 708 mExprModel.seal(); 709 assertTrue(staticParsed.isBindingExpression()); 710 // +1 for invalidate all flag 711 assertEquals(1, staticParsed.getInvalidFlags().cardinality()); 712 assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags()); 713 // +1 for invalidate all flag 714 assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality()); 715 // +1 for invalidate all flag 716 assertEquals(2, mExprModel.getInvalidateableFieldLimit()); 717 } 718 719 @Test 720 public void testFinalFieldOfAVariable() { 721 MockLayoutBinder lb = new MockLayoutBinder(); 722 mExprModel = lb.getModel(); 723 IdentifierExpr user = lb.addVariable("user", User.class.getCanonicalName(), 724 null); 725 Expr fieldGet = parse(lb, "user.finalField", FieldAccessExpr.class); 726 mExprModel.seal(); 727 assertTrue(fieldGet.isDynamic()); 728 // read user 729 assertExactMatch(getShouldRead(), user, fieldGet); 730 mExprModel.markBitsRead(); 731 // no need to read user.finalField 732 assertEquals(0, getShouldRead().size()); 733 } 734 735 @Test 736 public void testFinalFieldOfAField() { 737 MockLayoutBinder lb = new MockLayoutBinder(); 738 mExprModel = lb.getModel(); 739 lb.addVariable("user", User.class.getCanonicalName(), null); 740 Expr finalFieldGet = parse(lb, "user.subObj.finalField", FieldAccessExpr.class); 741 mExprModel.seal(); 742 assertTrue(finalFieldGet.isDynamic()); 743 Expr userSubObjGet = finalFieldGet.getChildren().get(0); 744 // read user 745 List<Expr> shouldRead = getShouldRead(); 746 assertEquals(3, shouldRead.size()); 747 assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet, 748 finalFieldGet); 749 mExprModel.markBitsRead(); 750 // no need to read user.subObj.finalField because it is final 751 assertEquals(0, getShouldRead().size()); 752 } 753 754 @Test 755 public void testFinalFieldOfAMethod() { 756 MockLayoutBinder lb = new MockLayoutBinder(); 757 mExprModel = lb.getModel(); 758 lb.addVariable("user", User.class.getCanonicalName(), null); 759 Expr finalFieldGet = parse(lb, "user.anotherSubObj.finalField", FieldAccessExpr.class); 760 mExprModel.seal(); 761 assertTrue(finalFieldGet.isDynamic()); 762 Expr userSubObjGet = finalFieldGet.getChildren().get(0); 763 // read user 764 List<Expr> shouldRead = getShouldRead(); 765 assertEquals(3, shouldRead.size()); 766 assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet, 767 finalFieldGet); 768 mExprModel.markBitsRead(); 769 // no need to read user.subObj.finalField because it is final 770 assertEquals(0, getShouldRead().size()); 771 } 772 773 @Test 774 public void testFinalOfAClass() { 775 MockLayoutBinder lb = new MockLayoutBinder(); 776 mExprModel = lb.getModel(); 777 mExprModel.addImport("View", "android.view.View", null); 778 FieldAccessExpr fieldAccess = parse(lb, "View.VISIBLE", FieldAccessExpr.class); 779 assertFalse(fieldAccess.isDynamic()); 780 mExprModel.seal(); 781 assertEquals(0, getShouldRead().size()); 782 } 783 784 @Test 785 public void testStaticFieldOfInstance() { 786 MockLayoutBinder lb = new MockLayoutBinder(); 787 mExprModel = lb.getModel(); 788 lb.addVariable("myView", "android.view.View", null); 789 FieldAccessExpr fieldAccess = parse(lb, "myView.VISIBLE", FieldAccessExpr.class); 790 assertFalse(fieldAccess.isDynamic()); 791 mExprModel.seal(); 792 assertEquals(0, getShouldRead().size()); 793 final Expr child = fieldAccess.getChild(); 794 assertTrue(child instanceof StaticIdentifierExpr); 795 StaticIdentifierExpr id = (StaticIdentifierExpr) child; 796 assertEquals(id.getResolvedType().getCanonicalName(), "android.view.View"); 797 // on demand import 798 assertEquals("android.view.View", mExprModel.getImports().get("View")); 799 } 800 801 @Test 802 public void testOnDemandImportConflict() { 803 MockLayoutBinder lb = new MockLayoutBinder(); 804 mExprModel = lb.getModel(); 805 final IdentifierExpr myView = lb.addVariable("u", "android.view.View", 806 null); 807 mExprModel.addImport("View", User.class.getCanonicalName(), null); 808 final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(myView.getResolvedType()); 809 mExprModel.seal(); 810 // on demand import with conflict 811 assertEquals("android.view.View", mExprModel.getImports().get("View1")); 812 assertEquals("View1", id.getName()); 813 assertEquals("android.view.View", id.getUserDefinedType()); 814 } 815 816 @Test 817 public void testOnDemandImportAlreadyImported() { 818 MockLayoutBinder lb = new MockLayoutBinder(); 819 mExprModel = lb.getModel(); 820 final StaticIdentifierExpr ux = mExprModel.addImport("UX", User.class.getCanonicalName(), 821 null); 822 final IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName(), 823 null); 824 final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(u.getResolvedType()); 825 mExprModel.seal(); 826 // on demand import with conflict 827 assertSame(ux, id); 828 } 829 830 @Test 831 public void testStaticMethodOfInstance() { 832 MockLayoutBinder lb = new MockLayoutBinder(); 833 mExprModel = lb.getModel(); 834 lb.addVariable("user", User.class.getCanonicalName(), null); 835 MethodCallExpr methodCall = parse(lb, "user.ourStaticMethod()", MethodCallExpr.class); 836 assertTrue(methodCall.isDynamic()); 837 mExprModel.seal(); 838 final Expr child = methodCall.getTarget(); 839 assertTrue(child instanceof StaticIdentifierExpr); 840 StaticIdentifierExpr id = (StaticIdentifierExpr) child; 841 assertEquals(id.getResolvedType().getCanonicalName(), User.class.getCanonicalName()); 842 } 843 844 @Test 845 public void testFinalOfStaticField() { 846 MockLayoutBinder lb = new MockLayoutBinder(); 847 mExprModel = lb.getModel(); 848 mExprModel.addImport("UX", User.class.getCanonicalName(), null); 849 FieldAccessExpr fieldAccess = parse(lb, "UX.innerStaticInstance.finalStaticField", 850 FieldAccessExpr.class); 851 assertFalse(fieldAccess.isDynamic()); 852 mExprModel.seal(); 853 // nothing to read since it is all final and static 854 assertEquals(0, getShouldRead().size()); 855 } 856 857 @Test 858 public void testFinalOfFinalStaticField() { 859 MockLayoutBinder lb = new MockLayoutBinder(); 860 mExprModel = lb.getModel(); 861 mExprModel.addImport("User", User.class.getCanonicalName(), null); 862 FieldAccessExpr fieldAccess = parse(lb, "User.innerFinalStaticInstance.finalStaticField", 863 FieldAccessExpr.class); 864 assertFalse(fieldAccess.isDynamic()); 865 mExprModel.seal(); 866 assertEquals(0, getShouldRead().size()); 867 } 868 869 @Test 870 public void testLocationTracking() { 871 MockLayoutBinder lb = new MockLayoutBinder(); 872 mExprModel = lb.getModel(); 873 final String input = "a > 3 ? b : c"; 874 TernaryExpr ternaryExpr = parse(lb, input, TernaryExpr.class); 875 final Location location = ternaryExpr.getLocations().get(0); 876 assertNotNull(location); 877 assertEquals(0, location.startLine); 878 assertEquals(0, location.startOffset); 879 assertEquals(0, location.endLine); 880 assertEquals(input.length() - 1, location.endOffset); 881 882 final ComparisonExpr comparison = (ComparisonExpr) ternaryExpr.getPred(); 883 final Location predLoc = comparison.getLocations().get(0); 884 assertNotNull(predLoc); 885 assertEquals(0, predLoc.startLine); 886 assertEquals(0, predLoc.startOffset); 887 assertEquals(0, predLoc.endLine); 888 assertEquals(4, predLoc.endOffset); 889 890 final Location aLoc = comparison.getLeft().getLocations().get(0); 891 assertNotNull(aLoc); 892 assertEquals(0, aLoc.startLine); 893 assertEquals(0, aLoc.startOffset); 894 assertEquals(0, aLoc.endLine); 895 assertEquals(0, aLoc.endOffset); 896 897 final Location tLoc = comparison.getRight().getLocations().get(0); 898 assertNotNull(tLoc); 899 assertEquals(0, tLoc.startLine); 900 assertEquals(4, tLoc.startOffset); 901 assertEquals(0, tLoc.endLine); 902 assertEquals(4, tLoc.endOffset); 903 904 final Location bLoc = ternaryExpr.getIfTrue().getLocations().get(0); 905 assertNotNull(bLoc); 906 assertEquals(0, bLoc.startLine); 907 assertEquals(8, bLoc.startOffset); 908 assertEquals(0, bLoc.endLine); 909 assertEquals(8, bLoc.endOffset); 910 911 final Location cLoc = ternaryExpr.getIfFalse().getLocations().get(0); 912 assertNotNull(cLoc); 913 assertEquals(0, cLoc.startLine); 914 assertEquals(12, cLoc.startOffset); 915 assertEquals(0, cLoc.endLine); 916 assertEquals(12, cLoc.endOffset); 917 } 918 919// TODO uncomment when we have inner static access 920// @Test 921// public void testFinalOfInnerStaticClass() { 922// MockLayoutBinder lb = new MockLayoutBinder(); 923// mExprModel = lb.getModel(); 924// mExprModel.addImport("User", User.class.getCanonicalName()); 925// FieldAccessExpr fieldAccess = parse(lb, "User.InnerStaticClass.finalStaticField", FieldAccessExpr.class); 926// assertFalse(fieldAccess.isDynamic()); 927// mExprModel.seal(); 928// assertEquals(0, getShouldRead().size()); 929// } 930 931 private void assertFlags(Expr a, int... flags) { 932 BitSet bitset = new BitSet(); 933 for (int flag : flags) { 934 bitset.set(flag); 935 } 936 assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags()); 937 } 938 939 private void assertFlags(Expr a, Expr... exprs) { 940 BitSet bitSet = a.getShouldReadFlags(); 941 for (Expr expr : exprs) { 942 BitSet clone = (BitSet) bitSet.clone(); 943 clone.and(expr.getInvalidFlags()); 944 assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr 945 .getUniqueKey(), expr.getInvalidFlags(), clone); 946 } 947 948 BitSet composite = new BitSet(); 949 for (Expr expr : exprs) { 950 composite.or(expr.getInvalidFlags()); 951 } 952 assertEquals("composite flags should match", composite, bitSet); 953 } 954 955 private void assertExactMatch(List<Expr> iterable, Expr... exprs) { 956 int i = 0; 957 String listLog = Arrays.toString(iterable.toArray()); 958 String itemsLog = Arrays.toString(exprs); 959 String log = "list: " + listLog + "\nitems: " + itemsLog; 960 log("list", iterable); 961 for (Expr expr : exprs) { 962 assertTrue((i++) + ":must contain " + expr.getUniqueKey() + "\n" + log, 963 iterable.contains(expr)); 964 } 965 i = 0; 966 for (Expr expr : iterable) { 967 assertTrue((i++) + ":must be expected " + expr.getUniqueKey() + "\n" + log, 968 ArrayUtils.contains(exprs, expr)); 969 } 970 } 971 972 private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) { 973 final Expr parsed = binder.parse(input, null); 974 assertTrue(klass.isAssignableFrom(parsed.getClass())); 975 return (T) parsed; 976 } 977 978 private void log(String s, List<Expr> iterable) { 979 L.d(s); 980 for (Expr e : iterable) { 981 L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(), 982 e.getShouldReadFlagsWithConditionals(), e.getReadSoFar()); 983 } 984 L.d("end of %s", s); 985 } 986 987 private List<Expr> getReadFirst(List<Expr> shouldRead) { 988 return getReadFirst(shouldRead, null); 989 } 990 991 private List<Expr> getReadFirst(List<Expr> shouldRead, final List<Expr> justRead) { 992 List<Expr> result = new ArrayList<Expr>(); 993 for (Expr expr : shouldRead) { 994 if (expr.shouldReadNow(justRead)) { 995 result.add(expr); 996 } 997 } 998 return result; 999 } 1000 1001 private List<Expr> getShouldRead() { 1002 return mExprModel.filterShouldRead(mExprModel.getPendingExpressions()); 1003 } 1004 1005 public static class User extends BaseObservable { 1006 1007 String name; 1008 1009 String lastName; 1010 1011 public final int finalField = 5; 1012 1013 public static InnerStaticClass innerStaticInstance = new InnerStaticClass(); 1014 1015 public static final InnerStaticClass innerFinalStaticInstance = new InnerStaticClass(); 1016 1017 public SubObj subObj = new SubObj(); 1018 1019 public String getName() { 1020 return name; 1021 } 1022 1023 public String getLastName() { 1024 return lastName; 1025 } 1026 1027 public boolean getCond(int i) { 1028 return true; 1029 } 1030 1031 public SubObj getAnotherSubObj() { 1032 return new SubObj(); 1033 } 1034 1035 public static boolean ourStaticMethod() { 1036 return true; 1037 } 1038 1039 public String comment; 1040 1041 @Bindable 1042 public boolean getUseComment() { 1043 return true; 1044 } 1045 1046 public static class InnerStaticClass { 1047 1048 public static final int finalField = 3; 1049 1050 public static final int finalStaticField = 3; 1051 } 1052 } 1053 1054 public static class SubObj { 1055 1056 public final int finalField = 5; 1057 } 1058 1059} 1060