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