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