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