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