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