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