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