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