ExprModelTest.java revision 019c36b97c7c172ac03997f6bf170e65b2ed7fe4
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 testNoFlagsForNonBindingStatic() {
446        LayoutBinder lb = new MockLayoutBinder();
447        mExprModel = lb.getModel();
448        lb.addVariable("a", int.class.getCanonicalName());
449        final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
450        mExprModel.seal();
451        // +1 for invalidate all flag
452        assertEquals(1, parsed.getRight().getInvalidFlags().cardinality());
453        // +1 for invalidate all flag
454        assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
455        // +1 for invalidate all flag
456        assertEquals(2, mExprModel.getInvalidateableFieldLimit());
457    }
458
459    @Test
460    public void testFlagsForBindingStatic() {
461        LayoutBinder lb = new MockLayoutBinder();
462        mExprModel = lb.getModel();
463        lb.addVariable("a", int.class.getCanonicalName());
464        final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
465        final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
466        mExprModel.seal();
467        assertTrue(staticParsed.isBindingExpression());
468        // +1 for invalidate all flag
469        assertEquals(2, staticParsed.getInvalidFlags().cardinality());
470        assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags());
471        // +1 for invalidate all flag
472        assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
473        // +1 for invalidate all flag
474        assertEquals(3, mExprModel.getInvalidateableFieldLimit());
475    }
476
477    @Test
478    public void testFinalFieldOfAVariable() {
479        LayoutBinder lb = new MockLayoutBinder();
480        mExprModel = lb.getModel();
481        lb.addVariable("user", User.class.getCanonicalName());
482        Expr fieldGet = parse(lb, "user.finalField", FieldAccessExpr.class);
483        mExprModel.seal();
484        assertTrue(fieldGet.isDynamic());
485        // read user
486        assertSame(fieldGet.getChildren().get(0), Iterables.getFirst(getShouldRead(), null));
487        mExprModel.markBitsRead();
488        // no need to read user.finalField
489        assertEquals(0, Iterables.size(getShouldRead()));
490    }
491
492    @Test
493    public void testFinalFieldOfAField() {
494        LayoutBinder lb = new MockLayoutBinder();
495        mExprModel = lb.getModel();
496        lb.addVariable("user", User.class.getCanonicalName());
497        Expr finalFieldGet = parse(lb, "user.subObj.finalField", FieldAccessExpr.class);
498        mExprModel.seal();
499        assertTrue(finalFieldGet.isDynamic());
500        Expr userSubObjGet = finalFieldGet.getChildren().get(0);
501        // read user
502        Iterable<Expr> shouldRead = getShouldRead();
503        assertEquals(3, Iterables.size(shouldRead));
504        assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
505                finalFieldGet);
506        mExprModel.markBitsRead();
507        // no need to read user.subObj.finalField because it is final
508        assertEquals(0, Iterables.size(getShouldRead()));
509    }
510
511    @Test
512    public void testFinalFieldOfAMethod() {
513        LayoutBinder lb = new MockLayoutBinder();
514        mExprModel = lb.getModel();
515        lb.addVariable("user", User.class.getCanonicalName());
516        Expr finalFieldGet = parse(lb, "user.anotherSubObj.finalField", FieldAccessExpr.class);
517        mExprModel.seal();
518        assertTrue(finalFieldGet.isDynamic());
519        Expr userSubObjGet = finalFieldGet.getChildren().get(0);
520        // read user
521        Iterable<Expr> shouldRead = getShouldRead();
522        assertEquals(3, Iterables.size(shouldRead));
523        assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
524                finalFieldGet);
525        mExprModel.markBitsRead();
526        // no need to read user.subObj.finalField because it is final
527        assertEquals(0, Iterables.size(getShouldRead()));
528    }
529
530    @Test
531    public void testFinalOfAClass() {
532        LayoutBinder lb = new MockLayoutBinder();
533        mExprModel = lb.getModel();
534        mExprModel.addImport("View", "android.view.View");
535        FieldAccessExpr fieldAccess = parse(lb, "View.VISIBLE", FieldAccessExpr.class);
536        assertFalse(fieldAccess.isDynamic());
537        mExprModel.seal();
538        assertEquals(0, Iterables.size(getShouldRead()));
539    }
540
541    @Test
542    public void testFinalOfStaticField() {
543        LayoutBinder lb = new MockLayoutBinder();
544        mExprModel = lb.getModel();
545        mExprModel.addImport("UX", User.class.getCanonicalName());
546        FieldAccessExpr fieldAccess = parse(lb, "UX.innerStaticInstance.finalStaticField", FieldAccessExpr.class);
547        assertFalse(fieldAccess.isDynamic());
548        mExprModel.seal();
549        assertExactMatch(getShouldRead(), fieldAccess.getChild());
550    }
551
552    @Test
553    public void testFinalOfFinalStaticField() {
554        LayoutBinder lb = new MockLayoutBinder();
555        mExprModel = lb.getModel();
556        mExprModel.addImport("User", User.class.getCanonicalName());
557        FieldAccessExpr fieldAccess = parse(lb, "User.innerFinalStaticInstance.finalStaticField", FieldAccessExpr.class);
558        assertFalse(fieldAccess.isDynamic());
559        mExprModel.seal();
560        assertEquals(0, Iterables.size(getShouldRead()));
561    }
562
563//    TODO uncomment when we have inner static access
564//    @Test
565//    public void testFinalOfInnerStaticClass() {
566//        LayoutBinder lb = new MockLayoutBinder();
567//        mExprModel = lb.getModel();
568//        mExprModel.addImport("User", User.class.getCanonicalName());
569//        FieldAccessExpr fieldAccess = parse(lb, "User.InnerStaticClass.finalStaticField", FieldAccessExpr.class);
570//        assertFalse(fieldAccess.isDynamic());
571//        mExprModel.seal();
572//        assertEquals(0, Iterables.size(getShouldRead()));
573//    }
574
575    private void assertFlags(Expr a, int... flags) {
576        BitSet bitset = new BitSet();
577        for (int flag : flags) {
578            bitset.set(flag);
579        }
580        assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags());
581    }
582
583    private void assertFlags(Expr a, Expr... exprs) {
584        BitSet bitSet = a.getShouldReadFlags();
585        for (Expr expr : exprs) {
586            BitSet clone = (BitSet) bitSet.clone();
587            clone.and(expr.getInvalidFlags());
588            assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr
589                    .getUniqueKey(), expr.getInvalidFlags(), clone);
590        }
591
592        BitSet composite = new BitSet();
593        for (Expr expr : exprs) {
594            composite.or(expr.getInvalidFlags());
595        }
596        assertEquals("composite flags should match", composite, bitSet);
597    }
598
599    private void assertExactMatch(Iterable<Expr> iterable, Expr... exprs) {
600        int i = 0;
601        log("list", iterable);
602        for (Expr expr : exprs) {
603            assertTrue((i++) + ":must contain " + expr.getUniqueKey(),
604                    Iterables.contains(iterable, expr));
605        }
606        i = 0;
607        for (Expr expr : iterable) {
608            assertTrue((i++) + ":must be expected " + expr.getUniqueKey(),
609                    ArrayUtils.contains(exprs, expr));
610        }
611    }
612
613    private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) {
614        final Expr parsed = binder.parse(input);
615        assertTrue(klass.isAssignableFrom(parsed.getClass()));
616        return (T) parsed;
617    }
618
619    private void log(String s, Iterable<Expr> iterable) {
620        L.d(s);
621        for (Expr e : iterable) {
622            L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(),
623                    e.getShouldReadFlagsWithConditionals(), e.getReadSoFar());
624        }
625        L.d("end of %s", s);
626    }
627
628    private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) {
629        return getReadFirst(shouldRead, null);
630    }
631
632    private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead, final Iterable<Expr> justRead) {
633        return Iterables.filter(shouldRead, new Predicate<Expr>() {
634            @Override
635            public boolean apply(Expr input) {
636                return input.shouldReadNow(justRead);
637            }
638        });
639    }
640
641    private Iterable<Expr> getShouldRead() {
642        return mExprModel.filterShouldRead(mExprModel.getPendingExpressions());
643    }
644
645    public static class User extends BaseObservable {
646
647        String name;
648
649        String lastName;
650
651        public final int finalField = 5;
652        public static InnerStaticClass innerStaticInstance = new InnerStaticClass();
653        public static final InnerStaticClass innerFinalStaticInstance = new InnerStaticClass();
654        public SubObj subObj = new SubObj();
655
656        public String getName() {
657            return name;
658        }
659
660        public String getLastName() {
661            return lastName;
662        }
663
664        public boolean getCond(int i) {
665            return true;
666        }
667
668        public SubObj getAnotherSubObj() {
669            return new SubObj();
670        }
671
672        public static class InnerStaticClass {
673            public static final int finalField = 3;
674            public static final int finalStaticField = 3;
675        }
676    }
677
678    public static class SubObj {
679        public final int finalField = 5;
680    }
681
682}
683