ExprModelTest.java revision 8533f27db6c31b0c295ae62d314dbf07ea640571
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.databinding.tool.expr;
18
19import com.google.common.base.Predicate;
20import com.google.common.collect.Iterables;
21
22import org.apache.commons.lang3.ArrayUtils;
23import org.apache.commons.lang3.NotImplementedException;
24import org.junit.Before;
25import org.junit.Rule;
26import org.junit.Test;
27import org.junit.rules.TestWatcher;
28import org.junit.runner.Description;
29
30import android.databinding.BaseObservable;
31import android.databinding.tool.LayoutBinder;
32import android.databinding.tool.MockLayoutBinder;
33import android.databinding.tool.reflection.ModelAnalyzer;
34import android.databinding.tool.reflection.ModelClass;
35import android.databinding.tool.reflection.java.JavaAnalyzer;
36import android.databinding.tool.util.L;
37
38import java.lang.reflect.Field;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.BitSet;
42import java.util.List;
43
44import static org.junit.Assert.assertEquals;
45import static org.junit.Assert.assertFalse;
46import static org.junit.Assert.assertNotNull;
47import static org.junit.Assert.assertNull;
48import static org.junit.Assert.assertSame;
49import static org.junit.Assert.assertTrue;
50
51public 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
78    ExprModel mExprModel;
79
80    @Rule
81    public TestWatcher mTestWatcher = new TestWatcher() {
82        @Override
83        protected void failed(Throwable e, Description description) {
84            if (mExprModel != null && mExprModel.getFlagMapping() != null) {
85                final String[] mapping = mExprModel.getFlagMapping();
86                for (int i = 0; i < mapping.length; i++) {
87                    L.d("flag %d: %s", i, mapping[i]);
88                }
89            }
90        }
91    };
92
93    @Before
94    public void setUp() throws Exception {
95        JavaAnalyzer.initForTests();
96        mExprModel = new ExprModel();
97    }
98
99    @Test
100    public void testAddNormal() {
101        final DummyExpr d = new DummyExpr("a");
102        assertSame(d, mExprModel.register(d));
103        assertSame(d, mExprModel.register(d));
104        assertEquals(1, mExprModel.mExprMap.size());
105    }
106
107    @Test
108    public void testAddDupe1() {
109        final DummyExpr d = new DummyExpr("a");
110        assertSame(d, mExprModel.register(d));
111        assertSame(d, mExprModel.register(new DummyExpr("a")));
112        assertEquals(1, mExprModel.mExprMap.size());
113    }
114
115    @Test
116    public void testAddMultiple() {
117        mExprModel.register(new DummyExpr("a"));
118        mExprModel.register(new DummyExpr("b"));
119        assertEquals(2, mExprModel.mExprMap.size());
120    }
121
122
123    @Test
124    public void testAddWithChildren() {
125        DummyExpr a = new DummyExpr("a");
126        DummyExpr b = new DummyExpr("b");
127        DummyExpr c = new DummyExpr("c", a, b);
128        mExprModel.register(c);
129        DummyExpr a2 = new DummyExpr("a");
130        DummyExpr b2 = new DummyExpr("b");
131        DummyExpr c2 = new DummyExpr("c", a, b);
132        assertEquals(c, mExprModel.register(c2));
133    }
134
135    @Test
136    public void testShouldRead() {
137        LayoutBinder lb = new MockLayoutBinder();
138        mExprModel = lb.getModel();
139        IdentifierExpr a = lb.addVariable("a", "java.lang.String");
140        IdentifierExpr b = lb.addVariable("b", "java.lang.String");
141        IdentifierExpr c = lb.addVariable("c", "java.lang.String");
142        lb.parse("a == null ? b : c");
143        mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class));
144        lb.getModel().seal();
145        Iterable<Expr> shouldRead = getShouldRead();
146        // a and a == null
147        assertEquals(2, Iterables.size(shouldRead));
148        final Iterable<Expr> readFirst = getReadFirst(shouldRead, null);
149        assertEquals(1, Iterables.size(readFirst));
150        final Expr first = Iterables.getFirst(readFirst, null);
151        assertSame(a, first);
152        // now , assume we've read this
153        final BitSet shouldReadFlags = first.getShouldReadFlags();
154        assertNotNull(shouldReadFlags);
155    }
156
157    @Test
158    public void testTernaryWithPlus() {
159        LayoutBinder lb = new MockLayoutBinder();
160        mExprModel = lb.getModel();
161        IdentifierExpr user = lb
162                .addVariable("user", "android.databinding.tool.expr.ExprModelTest.User");
163        MathExpr parsed = parse(lb, "user.name + \" \" + (user.lastName ?? \"\")", MathExpr.class);
164        mExprModel.seal();
165        Iterable<Expr> toRead = getShouldRead();
166        Iterable<Expr> readNow = getReadFirst(toRead);
167        assertEquals(1, Iterables.size(readNow));
168        assertSame(user, Iterables.getFirst(readNow, null));
169        List<Expr> justRead = new ArrayList<Expr>();
170        justRead.add(user);
171        readNow = filterOut(getReadFirst(toRead, justRead), justRead);
172        assertEquals(2, Iterables.size(readNow)); //user.name && user.lastName
173        Iterables.addAll(justRead, readNow);
174        // user.lastname (T, F), user.name + " "
175        readNow = filterOut(getReadFirst(toRead, justRead), justRead);
176        assertEquals(2, Iterables.size(readNow)); //user.name && user.lastName
177        Iterables.addAll(justRead, readNow);
178        readNow = filterOut(getReadFirst(toRead, justRead), justRead);
179        assertEquals(0, Iterables.size(readNow));
180        mExprModel.markBitsRead();
181
182        toRead = getShouldRead();
183        assertEquals(2, Iterables.size(toRead));
184        justRead.clear();
185        readNow = filterOut(getReadFirst(toRead, justRead), justRead);
186        assertEquals(1, Iterables.size(readNow));
187        assertSame(parsed.getRight(), Iterables.getFirst(readNow, null));
188        Iterables.addAll(justRead, readNow);
189
190        readNow = filterOut(getReadFirst(toRead, justRead), justRead);
191        assertEquals(1, Iterables.size(readNow));
192        assertSame(parsed, Iterables.getFirst(readNow, null));
193        Iterables.addAll(justRead, readNow);
194
195        readNow = filterOut(getReadFirst(toRead, justRead), justRead);
196        assertEquals(0, Iterables.size(readNow));
197        mExprModel.markBitsRead();
198        assertEquals(0, Iterables.size(getShouldRead()));
199    }
200
201    private List<Expr> filterOut(Iterable itr, final Iterable exclude) {
202        return Arrays.asList(Iterables.toArray(Iterables.filter(itr, new Predicate() {
203            @Override
204            public boolean apply(Object input) {
205                return !Iterables.contains(exclude, input);
206            }
207        }), Expr.class));
208    }
209
210    @Test
211    public void testTernaryInsideTernary() {
212        LayoutBinder lb = new MockLayoutBinder();
213        mExprModel = lb.getModel();
214        IdentifierExpr cond1 = lb.addVariable("cond1", "boolean");
215        IdentifierExpr cond2 = lb.addVariable("cond2", "boolean");
216
217        IdentifierExpr a = lb.addVariable("a", "boolean");
218        IdentifierExpr b = lb.addVariable("b", "boolean");
219        IdentifierExpr c = lb.addVariable("c", "boolean");
220
221        final TernaryExpr ternaryExpr = parse(lb, "cond1 ? cond2 ? a : b : c", TernaryExpr.class);
222        final TernaryExpr innerTernary = (TernaryExpr) ternaryExpr.getIfTrue();
223        mExprModel.seal();
224
225        Iterable<Expr> toRead = getShouldRead();
226        assertEquals(1, Iterables.size(toRead));
227        assertEquals(ternaryExpr.getPred(), Iterables.getFirst(toRead, null));
228
229        Iterable<Expr> readNow = getReadFirst(toRead);
230        assertEquals(1, Iterables.size(readNow));
231        assertEquals(ternaryExpr.getPred(), Iterables.getFirst(readNow, null));
232        int cond1True = ternaryExpr.getRequirementFlagIndex(true);
233        int cond1False = ternaryExpr.getRequirementFlagIndex(false);
234        // ok, it is read now.
235        mExprModel.markBitsRead();
236
237        // now it should read cond2 or c, depending on the flag from first
238        toRead = getShouldRead();
239        assertEquals(2, Iterables.size(toRead));
240        assertExactMatch(toRead, ternaryExpr.getIfFalse(), innerTernary.getPred());
241        assertFlags(ternaryExpr.getIfFalse(), cond1False);
242        assertFlags(ternaryExpr.getIfTrue(), cond1True);
243
244        mExprModel.markBitsRead();
245
246        // now it should read a or b, innerTernary, outerTernary
247        toRead = getShouldRead();
248        assertExactMatch(toRead, innerTernary.getIfTrue(), innerTernary.getIfFalse(), ternaryExpr,
249                innerTernary);
250        assertFlags(innerTernary.getIfTrue(), innerTernary.getRequirementFlagIndex(true));
251        assertFlags(innerTernary.getIfFalse(), innerTernary.getRequirementFlagIndex(false));
252        assertFalse(mExprModel.markBitsRead());
253    }
254
255    @Test
256    public void testRequirementFlags() {
257        LayoutBinder lb = new MockLayoutBinder();
258        mExprModel = lb.getModel();
259        IdentifierExpr a = lb.addVariable("a", "java.lang.String");
260        IdentifierExpr b = lb.addVariable("b", "java.lang.String");
261        IdentifierExpr c = lb.addVariable("c", "java.lang.String");
262        IdentifierExpr d = lb.addVariable("d", "java.lang.String");
263        IdentifierExpr e = lb.addVariable("e", "java.lang.String");
264        final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e");
265        assertTrue(aTernary instanceof TernaryExpr);
266        final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue();
267        assertTrue(bTernary instanceof TernaryExpr);
268        final Expr aIsNull = mExprModel
269                .comparison("==", a, mExprModel.symbol("null", Object.class));
270        final Expr bIsNull = mExprModel
271                .comparison("==", b, mExprModel.symbol("null", Object.class));
272        lb.getModel().seal();
273        Iterable<Expr> shouldRead = getShouldRead();
274        // a and a == null
275        assertEquals(2, Iterables.size(shouldRead));
276        assertFalse(a.getShouldReadFlags().isEmpty());
277        assertTrue(a.getShouldReadFlags().get(a.getId()));
278        assertTrue(b.getShouldReadFlags().isEmpty());
279        assertTrue(c.getShouldReadFlags().isEmpty());
280        assertTrue(d.getShouldReadFlags().isEmpty());
281        assertTrue(e.getShouldReadFlags().isEmpty());
282
283        Iterable<Expr> readFirst = getReadFirst(shouldRead, null);
284        assertEquals(1, Iterables.size(readFirst));
285        final Expr first = Iterables.getFirst(readFirst, null);
286        assertSame(a, first);
287        assertTrue(mExprModel.markBitsRead());
288        for (Expr expr : mExprModel.getPendingExpressions()) {
289            assertNull(expr.mShouldReadFlags);
290        }
291        shouldRead = getShouldRead();
292        assertExactMatch(shouldRead, e, b, bIsNull);
293
294        assertFlags(e, aTernary.getRequirementFlagIndex(false));
295
296        assertFlags(b, aTernary.getRequirementFlagIndex(true));
297        assertFlags(bIsNull, aTernary.getRequirementFlagIndex(true));
298        assertTrue(mExprModel.markBitsRead());
299        shouldRead = getShouldRead();
300        assertEquals(4, Iterables.size(shouldRead));
301        assertTrue(Iterables.contains(shouldRead, c));
302        assertTrue(Iterables.contains(shouldRead, d));
303        assertTrue(Iterables.contains(shouldRead, aTernary));
304        assertTrue(Iterables.contains(shouldRead, bTernary));
305
306        assertTrue(c.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(true)));
307        assertEquals(1, c.getShouldReadFlags().cardinality());
308
309        assertTrue(d.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(false)));
310        assertEquals(1, d.getShouldReadFlags().cardinality());
311
312        assertTrue(bTernary.getShouldReadFlags().get(aTernary.getRequirementFlagIndex(true)));
313        assertEquals(1, bTernary.getShouldReadFlags().cardinality());
314        // +1 for invalidate all flag
315        assertEquals(6, aTernary.getShouldReadFlags().cardinality());
316        for (Expr expr : new Expr[]{a, b, c, d, e}) {
317            assertTrue(aTernary.getShouldReadFlags().get(expr.getId()));
318        }
319
320        readFirst = getReadFirst(shouldRead);
321        assertEquals(2, Iterables.size(readFirst));
322        assertTrue(Iterables.contains(readFirst, c));
323        assertTrue(Iterables.contains(readFirst, d));
324        assertFalse(mExprModel.markBitsRead());
325    }
326
327    @Test
328    public void testPostConditionalDependencies() {
329        LayoutBinder lb = new MockLayoutBinder();
330        mExprModel = lb.getModel();
331
332        IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName());
333        IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName());
334        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
335        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
336        IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName());
337        IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName());
338        IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName());
339        TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class);
340        TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx`"
341                + " + u2.getCond(e) ", TernaryExpr.class);
342        Expr abCmp = abTernary.getPred();
343        Expr bcCmp = bcTernary.getPred();
344        Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred();
345        final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse();
346        Expr u2GetCondE = xxPlusU2getCondE.getRight();
347        Expr u1Name = abTernary.getIfTrue();
348        Expr u2Name = abTernary.getIfFalse();
349        Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue();
350        Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse();
351
352        mExprModel.seal();
353        Iterable<Expr> shouldRead = getShouldRead();
354
355        assertExactMatch(shouldRead, a, b, c, abCmp, bcCmp);
356
357        Iterable<Expr> firstRead = getReadFirst(shouldRead);
358
359        assertExactMatch(firstRead, a, b, c);
360
361        assertFlags(a, a, b, u1, u2, u1Name, u2Name);
362        assertFlags(b, a, b, u1, u2, u1Name, u2Name, c, d, u1LastName, u2LastName, e);
363        assertFlags(c, b, c, u1, d, u1LastName, u2LastName, e);
364        assertFlags(abCmp, a, b, u1, u2, u1Name, u2Name);
365        assertFlags(bcCmp, b, c, u1, d, u1LastName, u2LastName, e);
366
367        assertTrue(mExprModel.markBitsRead());
368
369        shouldRead = getShouldRead();
370        Expr[] batch = {d, e, u1, u2, u1GetCondD, u2GetCondE, xxPlusU2getCondE, abTernary,
371                abTernary.getIfTrue(), abTernary.getIfFalse()};
372        assertExactMatch(shouldRead, batch);
373        firstRead = getReadFirst(shouldRead);
374        assertExactMatch(firstRead, d, e, u1, u2);
375
376        assertFlags(d, bcTernary.getRequirementFlagIndex(true));
377        assertFlags(e, bcTernary.getRequirementFlagIndex(false));
378        assertFlags(u1, bcTernary.getRequirementFlagIndex(true),
379                abTernary.getRequirementFlagIndex(true));
380        assertFlags(u2, bcTernary.getRequirementFlagIndex(false),
381                abTernary.getRequirementFlagIndex(false));
382
383        assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true));
384        assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false));
385        assertFlags(xxPlusU2getCondE, bcTernary.getRequirementFlagIndex(false));
386        assertFlags(abTernary, a, b, u1, u2, u1Name, u2Name);
387        assertFlags(abTernary.getIfTrue(), abTernary.getRequirementFlagIndex(true));
388        assertFlags(abTernary.getIfFalse(), abTernary.getRequirementFlagIndex(false));
389
390        assertTrue(mExprModel.markBitsRead());
391
392        shouldRead = getShouldRead();
393        // actually, there is no real case to read u1 anymore because if b>c was not true,
394        // u1.getCond(d) will never be set. Right now, we don't have mechanism to figure this out
395        // and also it does not affect correctness (just an unnecessary if stmt)
396        assertExactMatch(shouldRead, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary);
397        firstRead = getReadFirst(shouldRead);
398        assertExactMatch(firstRead, u1LastName, u2);
399
400        assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true));
401        assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false));
402        assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false));
403
404        assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true));
405        assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e);
406    }
407
408    @Test
409    public void testCircularDependency() {
410        LayoutBinder lb = new MockLayoutBinder();
411        mExprModel = lb.getModel();
412        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
413        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
414        final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
415        mExprModel.seal();
416        Iterable<Expr> shouldRead = getShouldRead();
417        assertExactMatch(shouldRead, a, abTernary.getPred());
418        assertTrue(mExprModel.markBitsRead());
419        shouldRead = getShouldRead();
420        assertExactMatch(shouldRead, b, abTernary);
421        assertFalse(mExprModel.markBitsRead());
422    }
423
424    @Test
425    public void testNestedCircularDependency() {
426        LayoutBinder lb = new MockLayoutBinder();
427        mExprModel = lb.getModel();
428        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
429        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
430        IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName());
431        final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class);
432        final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue();
433        mExprModel.seal();
434        Iterable<Expr> shouldRead = getShouldRead();
435        assertExactMatch(shouldRead, a, a3Ternary.getPred());
436        assertTrue(mExprModel.markBitsRead());
437        shouldRead = getShouldRead();
438        assertExactMatch(shouldRead, c, c4Ternary.getPred());
439        assertFlags(c, a3Ternary.getRequirementFlagIndex(true),
440                a3Ternary.getRequirementFlagIndex(false));
441        assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true));
442    }
443
444    @Test
445    public void testInterExprCircularDependency() {
446        LayoutBinder lb = new MockLayoutBinder();
447        mExprModel = lb.getModel();
448        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
449        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
450        final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
451        final TernaryExpr abTernary2 = parse(lb, "b > 3 ? b : a", TernaryExpr.class);
452        mExprModel.seal();
453        Iterable<Expr> shouldRead = getShouldRead();
454        assertExactMatch(shouldRead, a, b, abTernary.getPred(), abTernary2.getPred());
455        assertTrue(mExprModel.markBitsRead());
456        shouldRead = getShouldRead();
457        assertExactMatch(shouldRead, abTernary, abTernary2);
458    }
459
460    @Test
461    public void testNoFlagsForNonBindingStatic() {
462        LayoutBinder lb = new MockLayoutBinder();
463        mExprModel = lb.getModel();
464        lb.addVariable("a", int.class.getCanonicalName());
465        final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
466        mExprModel.seal();
467        // +1 for invalidate all flag
468        assertEquals(1, parsed.getRight().getInvalidFlags().cardinality());
469        // +1 for invalidate all flag
470        assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
471        // +1 for invalidate all flag
472        assertEquals(2, mExprModel.getInvalidateableFieldLimit());
473    }
474
475    @Test
476    public void testFlagsForBindingStatic() {
477        LayoutBinder lb = new MockLayoutBinder();
478        mExprModel = lb.getModel();
479        lb.addVariable("a", int.class.getCanonicalName());
480        final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
481        final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
482        mExprModel.seal();
483        assertTrue(staticParsed.isBindingExpression());
484        // +1 for invalidate all flag
485        assertEquals(1, staticParsed.getInvalidFlags().cardinality());
486        assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags());
487        // +1 for invalidate all flag
488        assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
489        // +1 for invalidate all flag
490        assertEquals(2, mExprModel.getInvalidateableFieldLimit());
491    }
492
493    @Test
494    public void testFinalFieldOfAVariable() {
495        LayoutBinder lb = new MockLayoutBinder();
496        mExprModel = lb.getModel();
497        lb.addVariable("user", User.class.getCanonicalName());
498        Expr fieldGet = parse(lb, "user.finalField", FieldAccessExpr.class);
499        mExprModel.seal();
500        assertTrue(fieldGet.isDynamic());
501        // read user
502        assertSame(fieldGet.getChildren().get(0), Iterables.getFirst(getShouldRead(), null));
503        mExprModel.markBitsRead();
504        // no need to read user.finalField
505        assertEquals(0, Iterables.size(getShouldRead()));
506    }
507
508    @Test
509    public void testFinalFieldOfAField() {
510        LayoutBinder lb = new MockLayoutBinder();
511        mExprModel = lb.getModel();
512        lb.addVariable("user", User.class.getCanonicalName());
513        Expr finalFieldGet = parse(lb, "user.subObj.finalField", FieldAccessExpr.class);
514        mExprModel.seal();
515        assertTrue(finalFieldGet.isDynamic());
516        Expr userSubObjGet = finalFieldGet.getChildren().get(0);
517        // read user
518        Iterable<Expr> shouldRead = getShouldRead();
519        assertEquals(3, Iterables.size(shouldRead));
520        assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
521                finalFieldGet);
522        mExprModel.markBitsRead();
523        // no need to read user.subObj.finalField because it is final
524        assertEquals(0, Iterables.size(getShouldRead()));
525    }
526
527    @Test
528    public void testFinalFieldOfAMethod() {
529        LayoutBinder lb = new MockLayoutBinder();
530        mExprModel = lb.getModel();
531        lb.addVariable("user", User.class.getCanonicalName());
532        Expr finalFieldGet = parse(lb, "user.anotherSubObj.finalField", FieldAccessExpr.class);
533        mExprModel.seal();
534        assertTrue(finalFieldGet.isDynamic());
535        Expr userSubObjGet = finalFieldGet.getChildren().get(0);
536        // read user
537        Iterable<Expr> shouldRead = getShouldRead();
538        assertEquals(3, Iterables.size(shouldRead));
539        assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
540                finalFieldGet);
541        mExprModel.markBitsRead();
542        // no need to read user.subObj.finalField because it is final
543        assertEquals(0, Iterables.size(getShouldRead()));
544    }
545
546    @Test
547    public void testFinalOfAClass() {
548        LayoutBinder lb = new MockLayoutBinder();
549        mExprModel = lb.getModel();
550        mExprModel.addImport("View", "android.view.View");
551        FieldAccessExpr fieldAccess = parse(lb, "View.VISIBLE", FieldAccessExpr.class);
552        assertFalse(fieldAccess.isDynamic());
553        mExprModel.seal();
554        assertEquals(0, Iterables.size(getShouldRead()));
555    }
556
557    @Test
558    public void testFinalOfStaticField() {
559        LayoutBinder lb = new MockLayoutBinder();
560        mExprModel = lb.getModel();
561        mExprModel.addImport("UX", User.class.getCanonicalName());
562        FieldAccessExpr fieldAccess = parse(lb, "UX.innerStaticInstance.finalStaticField", FieldAccessExpr.class);
563        assertFalse(fieldAccess.isDynamic());
564        mExprModel.seal();
565        assertExactMatch(getShouldRead(), fieldAccess.getChild());
566    }
567
568    @Test
569    public void testFinalOfFinalStaticField() {
570        LayoutBinder lb = new MockLayoutBinder();
571        mExprModel = lb.getModel();
572        mExprModel.addImport("User", User.class.getCanonicalName());
573        FieldAccessExpr fieldAccess = parse(lb, "User.innerFinalStaticInstance.finalStaticField", FieldAccessExpr.class);
574        assertFalse(fieldAccess.isDynamic());
575        mExprModel.seal();
576        assertEquals(0, Iterables.size(getShouldRead()));
577    }
578
579//    TODO uncomment when we have inner static access
580//    @Test
581//    public void testFinalOfInnerStaticClass() {
582//        LayoutBinder lb = new MockLayoutBinder();
583//        mExprModel = lb.getModel();
584//        mExprModel.addImport("User", User.class.getCanonicalName());
585//        FieldAccessExpr fieldAccess = parse(lb, "User.InnerStaticClass.finalStaticField", FieldAccessExpr.class);
586//        assertFalse(fieldAccess.isDynamic());
587//        mExprModel.seal();
588//        assertEquals(0, Iterables.size(getShouldRead()));
589//    }
590
591    private void assertFlags(Expr a, int... flags) {
592        BitSet bitset = new BitSet();
593        for (int flag : flags) {
594            bitset.set(flag);
595        }
596        assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags());
597    }
598
599    private void assertFlags(Expr a, Expr... exprs) {
600        BitSet bitSet = a.getShouldReadFlags();
601        for (Expr expr : exprs) {
602            BitSet clone = (BitSet) bitSet.clone();
603            clone.and(expr.getInvalidFlags());
604            assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr
605                    .getUniqueKey(), expr.getInvalidFlags(), clone);
606        }
607
608        BitSet composite = new BitSet();
609        for (Expr expr : exprs) {
610            composite.or(expr.getInvalidFlags());
611        }
612        assertEquals("composite flags should match", composite, bitSet);
613    }
614
615    private void assertExactMatch(Iterable<Expr> iterable, Expr... exprs) {
616        int i = 0;
617        log("list", iterable);
618        for (Expr expr : exprs) {
619            assertTrue((i++) + ":must contain " + expr.getUniqueKey(),
620                    Iterables.contains(iterable, expr));
621        }
622        i = 0;
623        for (Expr expr : iterable) {
624            assertTrue((i++) + ":must be expected " + expr.getUniqueKey(),
625                    ArrayUtils.contains(exprs, expr));
626        }
627    }
628
629    private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) {
630        final Expr parsed = binder.parse(input);
631        assertTrue(klass.isAssignableFrom(parsed.getClass()));
632        return (T) parsed;
633    }
634
635    private void log(String s, Iterable<Expr> iterable) {
636        L.d(s);
637        for (Expr e : iterable) {
638            L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(),
639                    e.getShouldReadFlagsWithConditionals(), e.getReadSoFar());
640        }
641        L.d("end of %s", s);
642    }
643
644    private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) {
645        return getReadFirst(shouldRead, null);
646    }
647
648    private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead, final Iterable<Expr> justRead) {
649        return Iterables.filter(shouldRead, new Predicate<Expr>() {
650            @Override
651            public boolean apply(Expr input) {
652                return input.shouldReadNow(justRead);
653            }
654        });
655    }
656
657    private Iterable<Expr> getShouldRead() {
658        return mExprModel.filterShouldRead(mExprModel.getPendingExpressions());
659    }
660
661    public static class User extends BaseObservable {
662
663        String name;
664
665        String lastName;
666
667        public final int finalField = 5;
668        public static InnerStaticClass innerStaticInstance = new InnerStaticClass();
669        public static final InnerStaticClass innerFinalStaticInstance = new InnerStaticClass();
670        public SubObj subObj = new SubObj();
671
672        public String getName() {
673            return name;
674        }
675
676        public String getLastName() {
677            return lastName;
678        }
679
680        public boolean getCond(int i) {
681            return true;
682        }
683
684        public SubObj getAnotherSubObj() {
685            return new SubObj();
686        }
687
688        public static class InnerStaticClass {
689            public static final int finalField = 3;
690            public static final int finalStaticField = 3;
691        }
692    }
693
694    public static class SubObj {
695        public final int finalField = 5;
696    }
697
698}
699