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