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