ExprModelTest.java revision 2611838bffef5a009ca71e3e9e59a93f29b098ed
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.tool.LayoutBinder; 28import android.databinding.tool.MockLayoutBinder; 29import android.databinding.tool.reflection.ModelAnalyzer; 30import android.databinding.tool.reflection.ModelClass; 31import android.databinding.tool.reflection.java.JavaAnalyzer; 32import android.databinding.tool.util.L; 33 34import java.lang.reflect.Field; 35import java.util.ArrayList; 36import java.util.Arrays; 37import java.util.BitSet; 38import java.util.Collections; 39import java.util.List; 40 41import static org.junit.Assert.assertEquals; 42import static org.junit.Assert.assertFalse; 43import static org.junit.Assert.assertNotNull; 44import static org.junit.Assert.assertNull; 45import static org.junit.Assert.assertSame; 46import static org.junit.Assert.assertTrue; 47 48public 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"); 137 IdentifierExpr b = lb.addVariable("b", "java.lang.String"); 138 IdentifierExpr c = lb.addVariable("c", "java.lang.String"); 139 lb.parse("a == null ? b : c"); 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 MathExpr parsed = parse(lb, "user.name + \" \" + (user.lastName ?? \"\")", MathExpr.class); 161 mExprModel.seal(); 162 List<Expr> toRead = getShouldRead(); 163 List<Expr> readNow = getReadFirst(toRead); 164 assertEquals(1, readNow.size()); 165 assertSame(user, readNow.get(0)); 166 List<Expr> justRead = new ArrayList<Expr>(); 167 justRead.add(user); 168 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 169 assertEquals(2, readNow.size()); //user.name && user.lastName 170 justRead.addAll(readNow); 171 // user.lastname (T, F), user.name + " " 172 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 173 assertEquals(2, readNow.size()); //user.name && user.lastName 174 justRead.addAll(readNow); 175 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 176 assertEquals(0, readNow.size()); 177 mExprModel.markBitsRead(); 178 179 toRead = getShouldRead(); 180 assertEquals(2, toRead.size()); 181 justRead.clear(); 182 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 183 assertEquals(1, readNow.size()); 184 assertSame(parsed.getRight(), readNow.get(0)); 185 justRead.addAll(readNow); 186 187 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 188 assertEquals(1, readNow.size()); 189 assertSame(parsed, readNow.get(0)); 190 justRead.addAll(readNow); 191 192 readNow = filterOut(getReadFirst(toRead, justRead), justRead); 193 assertEquals(0, readNow.size()); 194 mExprModel.markBitsRead(); 195 assertEquals(0, getShouldRead().size()); 196 } 197 198 private List<Expr> filterOut(List<Expr> itr, final List<Expr> exclude) { 199 List<Expr> result = new ArrayList<Expr>(); 200 for (Expr expr : itr) { 201 if (!exclude.contains(expr)) { 202 result.add(expr); 203 } 204 } 205 return result; 206 } 207 208 @Test 209 public void testTernaryInsideTernary() { 210 LayoutBinder lb = new MockLayoutBinder(); 211 mExprModel = lb.getModel(); 212 IdentifierExpr cond1 = lb.addVariable("cond1", "boolean"); 213 IdentifierExpr cond2 = lb.addVariable("cond2", "boolean"); 214 215 IdentifierExpr a = lb.addVariable("a", "boolean"); 216 IdentifierExpr b = lb.addVariable("b", "boolean"); 217 IdentifierExpr c = lb.addVariable("c", "boolean"); 218 219 final TernaryExpr ternaryExpr = parse(lb, "cond1 ? cond2 ? a : b : c", TernaryExpr.class); 220 final TernaryExpr innerTernary = (TernaryExpr) ternaryExpr.getIfTrue(); 221 mExprModel.seal(); 222 223 List<Expr> toRead = getShouldRead(); 224 assertEquals(1, toRead.size()); 225 assertEquals(ternaryExpr.getPred(), toRead.get(0)); 226 227 List<Expr> readNow = getReadFirst(toRead); 228 assertEquals(1, readNow.size()); 229 assertEquals(ternaryExpr.getPred(), readNow.get(0)); 230 int cond1True = ternaryExpr.getRequirementFlagIndex(true); 231 int cond1False = ternaryExpr.getRequirementFlagIndex(false); 232 // ok, it is read now. 233 mExprModel.markBitsRead(); 234 235 // now it should read cond2 or c, depending on the flag from first 236 toRead = getShouldRead(); 237 assertEquals(2, toRead.size()); 238 assertExactMatch(toRead, ternaryExpr.getIfFalse(), innerTernary.getPred()); 239 assertFlags(ternaryExpr.getIfFalse(), cond1False); 240 assertFlags(ternaryExpr.getIfTrue(), cond1True); 241 242 mExprModel.markBitsRead(); 243 244 // now it should read a or b, innerTernary, outerTernary 245 toRead = getShouldRead(); 246 assertExactMatch(toRead, innerTernary.getIfTrue(), innerTernary.getIfFalse(), ternaryExpr, 247 innerTernary); 248 assertFlags(innerTernary.getIfTrue(), innerTernary.getRequirementFlagIndex(true)); 249 assertFlags(innerTernary.getIfFalse(), innerTernary.getRequirementFlagIndex(false)); 250 assertFalse(mExprModel.markBitsRead()); 251 } 252 253 @Test 254 public void testRequirementFlags() { 255 LayoutBinder lb = new MockLayoutBinder(); 256 mExprModel = lb.getModel(); 257 IdentifierExpr a = lb.addVariable("a", "java.lang.String"); 258 IdentifierExpr b = lb.addVariable("b", "java.lang.String"); 259 IdentifierExpr c = lb.addVariable("c", "java.lang.String"); 260 IdentifierExpr d = lb.addVariable("d", "java.lang.String"); 261 IdentifierExpr e = lb.addVariable("e", "java.lang.String"); 262 final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e"); 263 assertTrue(aTernary instanceof TernaryExpr); 264 final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue(); 265 assertTrue(bTernary instanceof TernaryExpr); 266 final Expr aIsNull = mExprModel 267 .comparison("==", a, mExprModel.symbol("null", Object.class)); 268 final Expr bIsNull = mExprModel 269 .comparison("==", b, mExprModel.symbol("null", Object.class)); 270 lb.getModel().seal(); 271 List<Expr> shouldRead = getShouldRead(); 272 // a and a == null 273 assertEquals(2, shouldRead.size()); 274 assertFalse(a.getShouldReadFlags().isEmpty()); 275 assertTrue(a.getShouldReadFlags().get(a.getId())); 276 assertTrue(b.getShouldReadFlags().isEmpty()); 277 assertTrue(c.getShouldReadFlags().isEmpty()); 278 assertTrue(d.getShouldReadFlags().isEmpty()); 279 assertTrue(e.getShouldReadFlags().isEmpty()); 280 281 List<Expr> readFirst = getReadFirst(shouldRead, null); 282 assertEquals(1, readFirst.size()); 283 final Expr first = readFirst.get(0); 284 assertSame(a, first); 285 assertTrue(mExprModel.markBitsRead()); 286 for (Expr expr : mExprModel.getPendingExpressions()) { 287 assertNull(expr.mShouldReadFlags); 288 } 289 shouldRead = getShouldRead(); 290 assertExactMatch(shouldRead, e, b, bIsNull); 291 292 assertFlags(e, aTernary.getRequirementFlagIndex(false)); 293 294 assertFlags(b, aTernary.getRequirementFlagIndex(true)); 295 assertFlags(bIsNull, aTernary.getRequirementFlagIndex(true)); 296 assertTrue(mExprModel.markBitsRead()); 297 shouldRead = getShouldRead(); 298 assertEquals(4, shouldRead.size()); 299 assertTrue(shouldRead.contains(c)); 300 assertTrue(shouldRead.contains(d)); 301 assertTrue(shouldRead.contains(aTernary)); 302 assertTrue(shouldRead.contains(bTernary)); 303 304 assertTrue(c.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(true))); 305 assertEquals(1, c.getShouldReadFlags().cardinality()); 306 307 assertTrue(d.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(false))); 308 assertEquals(1, d.getShouldReadFlags().cardinality()); 309 310 assertTrue(bTernary.getShouldReadFlags().get(aTernary.getRequirementFlagIndex(true))); 311 assertEquals(1, bTernary.getShouldReadFlags().cardinality()); 312 // +1 for invalidate all flag 313 assertEquals(6, aTernary.getShouldReadFlags().cardinality()); 314 for (Expr expr : new Expr[]{a, b, c, d, e}) { 315 assertTrue(aTernary.getShouldReadFlags().get(expr.getId())); 316 } 317 318 readFirst = getReadFirst(shouldRead); 319 assertEquals(2, readFirst.size()); 320 assertTrue(readFirst.contains(c)); 321 assertTrue(readFirst.contains(d)); 322 assertFalse(mExprModel.markBitsRead()); 323 } 324 325 @Test 326 public void testPostConditionalDependencies() { 327 LayoutBinder lb = new MockLayoutBinder(); 328 mExprModel = lb.getModel(); 329 330 IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName()); 331 IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName()); 332 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 333 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 334 IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName()); 335 IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName()); 336 IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName()); 337 TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class); 338 TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx`" 339 + " + u2.getCond(e) ", TernaryExpr.class); 340 Expr abCmp = abTernary.getPred(); 341 Expr bcCmp = bcTernary.getPred(); 342 Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred(); 343 final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse(); 344 Expr u2GetCondE = xxPlusU2getCondE.getRight(); 345 Expr u1Name = abTernary.getIfTrue(); 346 Expr u2Name = abTernary.getIfFalse(); 347 Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue(); 348 Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse(); 349 350 mExprModel.seal(); 351 List<Expr> shouldRead = getShouldRead(); 352 353 assertExactMatch(shouldRead, a, b, c, abCmp, bcCmp); 354 355 List<Expr> firstRead = getReadFirst(shouldRead); 356 357 assertExactMatch(firstRead, a, b, c); 358 359 assertFlags(a, a, b, u1, u2, u1Name, u2Name); 360 assertFlags(b, a, b, u1, u2, u1Name, u2Name, c, d, u1LastName, u2LastName, e); 361 assertFlags(c, b, c, u1, d, u1LastName, u2LastName, e); 362 assertFlags(abCmp, a, b, u1, u2, u1Name, u2Name); 363 assertFlags(bcCmp, b, c, u1, d, u1LastName, u2LastName, e); 364 365 assertTrue(mExprModel.markBitsRead()); 366 367 shouldRead = getShouldRead(); 368 Expr[] batch = {d, e, u1, u2, u1GetCondD, u2GetCondE, xxPlusU2getCondE, abTernary, 369 abTernary.getIfTrue(), abTernary.getIfFalse()}; 370 assertExactMatch(shouldRead, batch); 371 firstRead = getReadFirst(shouldRead); 372 assertExactMatch(firstRead, d, e, u1, u2); 373 374 assertFlags(d, bcTernary.getRequirementFlagIndex(true)); 375 assertFlags(e, bcTernary.getRequirementFlagIndex(false)); 376 assertFlags(u1, bcTernary.getRequirementFlagIndex(true), 377 abTernary.getRequirementFlagIndex(true)); 378 assertFlags(u2, bcTernary.getRequirementFlagIndex(false), 379 abTernary.getRequirementFlagIndex(false)); 380 381 assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true)); 382 assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false)); 383 assertFlags(xxPlusU2getCondE, bcTernary.getRequirementFlagIndex(false)); 384 assertFlags(abTernary, a, b, u1, u2, u1Name, u2Name); 385 assertFlags(abTernary.getIfTrue(), abTernary.getRequirementFlagIndex(true)); 386 assertFlags(abTernary.getIfFalse(), abTernary.getRequirementFlagIndex(false)); 387 388 assertTrue(mExprModel.markBitsRead()); 389 390 shouldRead = getShouldRead(); 391 // actually, there is no real case to read u1 anymore because if b>c was not true, 392 // u1.getCond(d) will never be set. Right now, we don't have mechanism to figure this out 393 // and also it does not affect correctness (just an unnecessary if stmt) 394 assertExactMatch(shouldRead, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary); 395 firstRead = getReadFirst(shouldRead); 396 assertExactMatch(firstRead, u1LastName, u2); 397 398 assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true)); 399 assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false)); 400 assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false)); 401 402 assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true)); 403 assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e); 404 } 405 406 @Test 407 public void testCircularDependency() { 408 LayoutBinder lb = new MockLayoutBinder(); 409 mExprModel = lb.getModel(); 410 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 411 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 412 final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class); 413 mExprModel.seal(); 414 List<Expr> shouldRead = getShouldRead(); 415 assertExactMatch(shouldRead, a, abTernary.getPred()); 416 assertTrue(mExprModel.markBitsRead()); 417 shouldRead = getShouldRead(); 418 assertExactMatch(shouldRead, b, abTernary); 419 assertFalse(mExprModel.markBitsRead()); 420 } 421 422 @Test 423 public void testNestedCircularDependency() { 424 LayoutBinder lb = new MockLayoutBinder(); 425 mExprModel = lb.getModel(); 426 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 427 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 428 IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName()); 429 final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class); 430 final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue(); 431 mExprModel.seal(); 432 List<Expr> shouldRead = getShouldRead(); 433 assertExactMatch(shouldRead, a, a3Ternary.getPred()); 434 assertTrue(mExprModel.markBitsRead()); 435 shouldRead = getShouldRead(); 436 assertExactMatch(shouldRead, c, c4Ternary.getPred()); 437 assertFlags(c, a3Ternary.getRequirementFlagIndex(true), 438 a3Ternary.getRequirementFlagIndex(false)); 439 assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true)); 440 } 441 442 @Test 443 public void testInterExprCircularDependency() { 444 LayoutBinder lb = new MockLayoutBinder(); 445 mExprModel = lb.getModel(); 446 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 447 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 448 final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class); 449 final TernaryExpr abTernary2 = parse(lb, "b > 3 ? b : a", TernaryExpr.class); 450 mExprModel.seal(); 451 List<Expr> shouldRead = getShouldRead(); 452 assertExactMatch(shouldRead, a, b, abTernary.getPred(), abTernary2.getPred()); 453 assertTrue(mExprModel.markBitsRead()); 454 shouldRead = getShouldRead(); 455 assertExactMatch(shouldRead, abTernary, abTernary2); 456 } 457 458 @Test 459 public void testInterExprCircularDependency2() { 460 LayoutBinder lb = new MockLayoutBinder(); 461 mExprModel = lb.getModel(); 462 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName()); 463 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName()); 464 final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class); 465 final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class); 466 mExprModel.seal(); 467 List<Expr> shouldRead = getShouldRead(); 468 assertExactMatch(shouldRead, a, b); 469 List<Expr> readFirst = getReadFirst(shouldRead); 470 assertExactMatch(readFirst, a, b); 471 assertTrue(mExprModel.markBitsRead()); 472 shouldRead = getShouldRead(); 473 // read a and b again, this time for their dependencies and also the rest since everything 474 // is ready to be read 475 assertExactMatch(shouldRead, a, b, abTernary, baTernary); 476 List<Expr> justRead = new ArrayList<Expr>(); 477 readFirst = getReadFirst(shouldRead); 478 assertExactMatch(readFirst, a, b); 479 Collections.addAll(justRead, a, b); 480 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 481 assertExactMatch(readFirst, abTernary, baTernary); 482 483 484 assertFalse(mExprModel.markBitsRead()); 485 shouldRead = getShouldRead(); 486 assertEquals(0, shouldRead.size()); 487 } 488 489 @Test 490 public void testInterExprCircularDependency3() { 491 LayoutBinder lb = new MockLayoutBinder(); 492 mExprModel = lb.getModel(); 493 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName()); 494 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName()); 495 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName()); 496 final TernaryExpr abTernary = parse(lb, "a ? b : c", TernaryExpr.class); 497 final TernaryExpr abTernary2 = parse(lb, "b ? a : c", TernaryExpr.class); 498 mExprModel.seal(); 499 List<Expr> shouldRead = getShouldRead(); 500 assertExactMatch(shouldRead, a, b); 501 assertTrue(mExprModel.markBitsRead()); 502 shouldRead = getShouldRead(); 503 // read a and b again, this time for their dependencies and also the rest since everything 504 // is ready to be read 505 assertExactMatch(shouldRead, a, b, c, abTernary, abTernary2); 506 mExprModel.markBitsRead(); 507 shouldRead = getShouldRead(); 508 assertEquals(0, shouldRead.size()); 509 } 510 511 @Test 512 public void testInterExprCircularDependency4() { 513 LayoutBinder lb = new MockLayoutBinder(); 514 mExprModel = lb.getModel(); 515 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName()); 516 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName()); 517 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName()); 518 IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName()); 519 final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class); 520 final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class); 521 final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class); 522 mExprModel.seal(); 523 List<Expr> shouldRead = getShouldRead(); 524 assertExactMatch(shouldRead, c, a, b); 525 526 List<Expr> justRead = new ArrayList<Expr>(); 527 List<Expr> readFirst = getReadFirst(shouldRead); 528 assertExactMatch(readFirst, c, a, b); 529 Collections.addAll(justRead, a, b, c); 530 assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size()); 531 assertTrue(mExprModel.markBitsRead()); 532 shouldRead = getShouldRead(); 533 assertExactMatch(shouldRead, a, b, d, cTernary.getIfTrue(), cTernary, abTernary, baTernary); 534 justRead.clear(); 535 536 readFirst = getReadFirst(shouldRead); 537 assertExactMatch(readFirst, a, b, d); 538 Collections.addAll(justRead, a, b, d); 539 540 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 541 assertExactMatch(readFirst, cTernary.getIfTrue(), abTernary, baTernary); 542 Collections.addAll(justRead, cTernary.getIfTrue(), abTernary, baTernary); 543 544 readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead); 545 assertExactMatch(readFirst, cTernary); 546 Collections.addAll(justRead, cTernary); 547 548 assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size()); 549 550 assertFalse(mExprModel.markBitsRead()); 551 } 552 553 @Test 554 public void testInterExprDependencyNotReadyYet() { 555 LayoutBinder lb = new MockLayoutBinder(); 556 mExprModel = lb.getModel(); 557 IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName()); 558 IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName()); 559 IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName()); 560 IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName()); 561 IdentifierExpr e = lb.addVariable("e", boolean.class.getCanonicalName()); 562 final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class); 563 final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class); 564 final TernaryExpr eaTernary = parse(lb, "e ? a : false", TernaryExpr.class); 565 mExprModel.seal(); 566 List<Expr> shouldRead = getShouldRead(); 567 assertExactMatch(shouldRead, b, c, e); 568 assertTrue(mExprModel.markBitsRead()); 569 shouldRead = getShouldRead(); 570 assertExactMatch(shouldRead, a, baTernary, eaTernary); 571 assertTrue(mExprModel.markBitsRead()); 572 shouldRead = getShouldRead(); 573 assertExactMatch(shouldRead, d, cTernary.getIfTrue(), cTernary); 574 assertFalse(mExprModel.markBitsRead()); 575 } 576 577 @Test 578 public void testNoFlagsForNonBindingStatic() { 579 LayoutBinder lb = new MockLayoutBinder(); 580 mExprModel = lb.getModel(); 581 lb.addVariable("a", int.class.getCanonicalName()); 582 final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class); 583 mExprModel.seal(); 584 // +1 for invalidate all flag 585 assertEquals(1, parsed.getRight().getInvalidFlags().cardinality()); 586 // +1 for invalidate all flag 587 assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality()); 588 // +1 for invalidate all flag 589 assertEquals(2, mExprModel.getInvalidateableFieldLimit()); 590 } 591 592 @Test 593 public void testFlagsForBindingStatic() { 594 LayoutBinder lb = new MockLayoutBinder(); 595 mExprModel = lb.getModel(); 596 lb.addVariable("a", int.class.getCanonicalName()); 597 final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class); 598 final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class); 599 mExprModel.seal(); 600 assertTrue(staticParsed.isBindingExpression()); 601 // +1 for invalidate all flag 602 assertEquals(1, staticParsed.getInvalidFlags().cardinality()); 603 assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags()); 604 // +1 for invalidate all flag 605 assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality()); 606 // +1 for invalidate all flag 607 assertEquals(2, mExprModel.getInvalidateableFieldLimit()); 608 } 609 610 @Test 611 public void testFinalFieldOfAVariable() { 612 LayoutBinder lb = new MockLayoutBinder(); 613 mExprModel = lb.getModel(); 614 IdentifierExpr user = lb.addVariable("user", User.class.getCanonicalName()); 615 Expr fieldGet = parse(lb, "user.finalField", FieldAccessExpr.class); 616 mExprModel.seal(); 617 assertTrue(fieldGet.isDynamic()); 618 // read user 619 assertExactMatch(getShouldRead(), user, fieldGet); 620 mExprModel.markBitsRead(); 621 // no need to read user.finalField 622 assertEquals(0, getShouldRead().size()); 623 } 624 625 @Test 626 public void testFinalFieldOfAField() { 627 LayoutBinder lb = new MockLayoutBinder(); 628 mExprModel = lb.getModel(); 629 lb.addVariable("user", User.class.getCanonicalName()); 630 Expr finalFieldGet = parse(lb, "user.subObj.finalField", FieldAccessExpr.class); 631 mExprModel.seal(); 632 assertTrue(finalFieldGet.isDynamic()); 633 Expr userSubObjGet = finalFieldGet.getChildren().get(0); 634 // read user 635 List<Expr> shouldRead = getShouldRead(); 636 assertEquals(3, shouldRead.size()); 637 assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet, 638 finalFieldGet); 639 mExprModel.markBitsRead(); 640 // no need to read user.subObj.finalField because it is final 641 assertEquals(0, getShouldRead().size()); 642 } 643 644 @Test 645 public void testFinalFieldOfAMethod() { 646 LayoutBinder lb = new MockLayoutBinder(); 647 mExprModel = lb.getModel(); 648 lb.addVariable("user", User.class.getCanonicalName()); 649 Expr finalFieldGet = parse(lb, "user.anotherSubObj.finalField", FieldAccessExpr.class); 650 mExprModel.seal(); 651 assertTrue(finalFieldGet.isDynamic()); 652 Expr userSubObjGet = finalFieldGet.getChildren().get(0); 653 // read user 654 List<Expr> shouldRead = getShouldRead(); 655 assertEquals(3, shouldRead.size()); 656 assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet, 657 finalFieldGet); 658 mExprModel.markBitsRead(); 659 // no need to read user.subObj.finalField because it is final 660 assertEquals(0, getShouldRead().size()); 661 } 662 663 @Test 664 public void testFinalOfAClass() { 665 LayoutBinder lb = new MockLayoutBinder(); 666 mExprModel = lb.getModel(); 667 mExprModel.addImport("View", "android.view.View"); 668 FieldAccessExpr fieldAccess = parse(lb, "View.VISIBLE", FieldAccessExpr.class); 669 assertFalse(fieldAccess.isDynamic()); 670 mExprModel.seal(); 671 assertEquals(0, getShouldRead().size()); 672 } 673 674 @Test 675 public void testStaticFieldOfInstance() { 676 LayoutBinder lb = new MockLayoutBinder(); 677 mExprModel = lb.getModel(); 678 lb.addVariable("myView", "android.view.View"); 679 FieldAccessExpr fieldAccess = parse(lb, "myView.VISIBLE", FieldAccessExpr.class); 680 assertFalse(fieldAccess.isDynamic()); 681 mExprModel.seal(); 682 assertEquals(0, getShouldRead().size()); 683 final Expr child = fieldAccess.getChild(); 684 assertTrue(child instanceof StaticIdentifierExpr); 685 StaticIdentifierExpr id = (StaticIdentifierExpr) child; 686 assertEquals(id.getResolvedType().getCanonicalName(), "android.view.View"); 687 // on demand import 688 assertEquals("android.view.View", mExprModel.getImports().get("View")); 689 } 690 691 @Test 692 public void testOnDemandImportConflict() { 693 LayoutBinder lb = new MockLayoutBinder(); 694 mExprModel = lb.getModel(); 695 final IdentifierExpr myView = lb.addVariable("u", "android.view.View"); 696 mExprModel.addImport("View", User.class.getCanonicalName()); 697 final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(myView.getResolvedType()); 698 mExprModel.seal(); 699 // on demand import with conflict 700 assertEquals("android.view.View", mExprModel.getImports().get("View1")); 701 assertEquals("View1", id.getName()); 702 assertEquals("android.view.View", id.getUserDefinedType()); 703 } 704 705 @Test 706 public void testOnDemandImportAlreadyImported() { 707 LayoutBinder lb = new MockLayoutBinder(); 708 mExprModel = lb.getModel(); 709 final StaticIdentifierExpr ux = mExprModel.addImport("UX", User.class.getCanonicalName()); 710 final IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName()); 711 final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(u.getResolvedType()); 712 mExprModel.seal(); 713 // on demand import with conflict 714 assertSame(ux, id); 715 } 716 717 @Test 718 public void testStaticMethodOfInstance() { 719 LayoutBinder lb = new MockLayoutBinder(); 720 mExprModel = lb.getModel(); 721 lb.addVariable("user", User.class.getCanonicalName()); 722 MethodCallExpr methodCall = parse(lb, "user.ourStaticMethod()", MethodCallExpr.class); 723 assertTrue(methodCall.isDynamic()); 724 mExprModel.seal(); 725 final Expr child = methodCall.getTarget(); 726 assertTrue(child instanceof StaticIdentifierExpr); 727 StaticIdentifierExpr id = (StaticIdentifierExpr) child; 728 assertEquals(id.getResolvedType().getCanonicalName(), User.class.getCanonicalName()); 729 } 730 731 @Test 732 public void testFinalOfStaticField() { 733 LayoutBinder lb = new MockLayoutBinder(); 734 mExprModel = lb.getModel(); 735 mExprModel.addImport("UX", User.class.getCanonicalName()); 736 FieldAccessExpr fieldAccess = parse(lb, "UX.innerStaticInstance.finalStaticField", FieldAccessExpr.class); 737 assertFalse(fieldAccess.isDynamic()); 738 mExprModel.seal(); 739 // nothing to read since it is all final and static 740 assertEquals(0, getShouldRead().size()); 741 } 742 743 @Test 744 public void testFinalOfFinalStaticField() { 745 LayoutBinder lb = new MockLayoutBinder(); 746 mExprModel = lb.getModel(); 747 mExprModel.addImport("User", User.class.getCanonicalName()); 748 FieldAccessExpr fieldAccess = parse(lb, "User.innerFinalStaticInstance.finalStaticField", FieldAccessExpr.class); 749 assertFalse(fieldAccess.isDynamic()); 750 mExprModel.seal(); 751 assertEquals(0, getShouldRead().size()); 752 } 753 754// TODO uncomment when we have inner static access 755// @Test 756// public void testFinalOfInnerStaticClass() { 757// LayoutBinder lb = new MockLayoutBinder(); 758// mExprModel = lb.getModel(); 759// mExprModel.addImport("User", User.class.getCanonicalName()); 760// FieldAccessExpr fieldAccess = parse(lb, "User.InnerStaticClass.finalStaticField", FieldAccessExpr.class); 761// assertFalse(fieldAccess.isDynamic()); 762// mExprModel.seal(); 763// assertEquals(0, getShouldRead().size()); 764// } 765 766 private void assertFlags(Expr a, int... flags) { 767 BitSet bitset = new BitSet(); 768 for (int flag : flags) { 769 bitset.set(flag); 770 } 771 assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags()); 772 } 773 774 private void assertFlags(Expr a, Expr... exprs) { 775 BitSet bitSet = a.getShouldReadFlags(); 776 for (Expr expr : exprs) { 777 BitSet clone = (BitSet) bitSet.clone(); 778 clone.and(expr.getInvalidFlags()); 779 assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr 780 .getUniqueKey(), expr.getInvalidFlags(), clone); 781 } 782 783 BitSet composite = new BitSet(); 784 for (Expr expr : exprs) { 785 composite.or(expr.getInvalidFlags()); 786 } 787 assertEquals("composite flags should match", composite, bitSet); 788 } 789 790 private void assertExactMatch(List<Expr> iterable, Expr... exprs) { 791 int i = 0; 792 String log = Arrays.toString(iterable.toArray()); 793 log("list", iterable); 794 for (Expr expr : exprs) { 795 assertTrue((i++) + ":must contain " + expr.getUniqueKey() + "\n" + log, 796 iterable.contains(expr)); 797 } 798 i = 0; 799 for (Expr expr : iterable) { 800 assertTrue((i++) + ":must be expected " + expr.getUniqueKey(), 801 ArrayUtils.contains(exprs, expr)); 802 } 803 } 804 805 private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) { 806 final Expr parsed = binder.parse(input); 807 assertTrue(klass.isAssignableFrom(parsed.getClass())); 808 return (T) parsed; 809 } 810 811 private void log(String s, List<Expr> iterable) { 812 L.d(s); 813 for (Expr e : iterable) { 814 L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(), 815 e.getShouldReadFlagsWithConditionals(), e.getReadSoFar()); 816 } 817 L.d("end of %s", s); 818 } 819 820 private List<Expr> getReadFirst(List<Expr> shouldRead) { 821 return getReadFirst(shouldRead, null); 822 } 823 824 private List<Expr> getReadFirst(List<Expr> shouldRead, final List<Expr> justRead) { 825 List<Expr> result = new ArrayList<Expr>(); 826 for (Expr expr : shouldRead) { 827 if (expr.shouldReadNow(justRead)) { 828 result.add(expr); 829 } 830 } 831 return result; 832 } 833 834 private List<Expr> getShouldRead() { 835 return mExprModel.filterShouldRead(mExprModel.getPendingExpressions()); 836 } 837 838 public static class User extends BaseObservable { 839 840 String name; 841 842 String lastName; 843 844 public final int finalField = 5; 845 public static InnerStaticClass innerStaticInstance = new InnerStaticClass(); 846 public static final InnerStaticClass innerFinalStaticInstance = new InnerStaticClass(); 847 public SubObj subObj = new SubObj(); 848 849 public String getName() { 850 return name; 851 } 852 853 public String getLastName() { 854 return lastName; 855 } 856 857 public boolean getCond(int i) { 858 return true; 859 } 860 861 public SubObj getAnotherSubObj() { 862 return new SubObj(); 863 } 864 865 public static boolean ourStaticMethod() { 866 return true; 867 } 868 869 public static class InnerStaticClass { 870 public static final int finalField = 3; 871 public static final int finalStaticField = 3; 872 } 873 } 874 875 public static class SubObj { 876 public final int finalField = 5; 877 } 878 879} 880