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