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