ExprModelTest.java revision e52882df6130221462bf07f5f2b52de5c4b0f8de
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        LayoutBinder 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        LayoutBinder 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        LayoutBinder 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        LayoutBinder 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        LayoutBinder 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        // actually, 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
406        assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true));
407        assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false));
408        assertFlags(u2, 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
414    @Test
415    public void testCircularDependency() {
416        LayoutBinder lb = new MockLayoutBinder();
417        mExprModel = lb.getModel();
418        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(),
419                null);
420        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(),
421                null);
422        final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
423        mExprModel.seal();
424        List<Expr> shouldRead = getShouldRead();
425        assertExactMatch(shouldRead, a, abTernary.getPred());
426        assertTrue(mExprModel.markBitsRead());
427        shouldRead = getShouldRead();
428        assertExactMatch(shouldRead, b, abTernary);
429        assertFalse(mExprModel.markBitsRead());
430    }
431
432    @Test
433    public void testNestedCircularDependency() {
434        LayoutBinder lb = new MockLayoutBinder();
435        mExprModel = lb.getModel();
436        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(),
437                null);
438        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(),
439                null);
440        IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName(),
441                null);
442        final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class);
443        final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue();
444        mExprModel.seal();
445        List<Expr> shouldRead = getShouldRead();
446        assertExactMatch(shouldRead, a, a3Ternary.getPred());
447        assertTrue(mExprModel.markBitsRead());
448        shouldRead = getShouldRead();
449        assertExactMatch(shouldRead, c, c4Ternary.getPred());
450        assertFlags(c, a3Ternary.getRequirementFlagIndex(true),
451                a3Ternary.getRequirementFlagIndex(false));
452        assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true));
453    }
454
455    @Test
456    public void testInterExprDependency() {
457        LayoutBinder lb = new MockLayoutBinder();
458        mExprModel = lb.getModel();
459        IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName(),
460                null);
461        final Expr uComment = parse(lb, "u.comment", FieldAccessExpr.class);
462        final TernaryExpr uTernary = parse(lb, "u.useComment ? u.comment : `xx`", TernaryExpr.class);
463        mExprModel.seal();
464        List<Expr> shouldRead = getShouldRead();
465        assertExactMatch(shouldRead, u, uComment, uTernary.getPred());
466        assertTrue(mExprModel.markBitsRead());
467        shouldRead = getShouldRead();
468        assertExactMatch(shouldRead, u, uComment, uTernary);
469    }
470
471    @Test
472    public void testInterExprCircularDependency() {
473        LayoutBinder lb = new MockLayoutBinder();
474        mExprModel = lb.getModel();
475        IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(),
476                null);
477        IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(),
478                null);
479        final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
480        final TernaryExpr abTernary2 = parse(lb, "b > 3 ? b : a", TernaryExpr.class);
481        mExprModel.seal();
482        List<Expr> shouldRead = getShouldRead();
483        assertExactMatch(shouldRead, a, b, abTernary.getPred(), abTernary2.getPred());
484        assertTrue(mExprModel.markBitsRead());
485        shouldRead = getShouldRead();
486        assertExactMatch(shouldRead, abTernary, abTernary2);
487    }
488
489    @Test
490    public void testInterExprCircularDependency2() {
491        LayoutBinder lb = new MockLayoutBinder();
492        mExprModel = lb.getModel();
493        IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(),
494                null);
495        IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(),
496                null);
497        final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class);
498        final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class);
499        mExprModel.seal();
500        List<Expr> shouldRead = getShouldRead();
501        assertExactMatch(shouldRead, a, b);
502        List<Expr> readFirst = getReadFirst(shouldRead);
503        assertExactMatch(readFirst, a, b);
504        assertTrue(mExprModel.markBitsRead());
505        shouldRead = getShouldRead();
506        // read a and b again, this time for their dependencies and also the rest since everything
507        // is ready to be read
508        assertExactMatch(shouldRead, a, b, abTernary, baTernary);
509        List<Expr> justRead = new ArrayList<Expr>();
510        readFirst = getReadFirst(shouldRead);
511        assertExactMatch(readFirst, a, b);
512        Collections.addAll(justRead, a, b);
513        readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
514        assertExactMatch(readFirst, abTernary, baTernary);
515
516        assertFalse(mExprModel.markBitsRead());
517        shouldRead = getShouldRead();
518        assertEquals(0, shouldRead.size());
519    }
520
521    @Test
522    public void testInterExprCircularDependency3() {
523        LayoutBinder lb = new MockLayoutBinder();
524        mExprModel = lb.getModel();
525        IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(),
526                null);
527        IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(),
528                null);
529        IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(),
530                null);
531        final TernaryExpr abTernary = parse(lb, "a ? b : c", TernaryExpr.class);
532        final TernaryExpr abTernary2 = parse(lb, "b ? a : c", TernaryExpr.class);
533        mExprModel.seal();
534        List<Expr> shouldRead = getShouldRead();
535        assertExactMatch(shouldRead, a, b);
536        assertTrue(mExprModel.markBitsRead());
537        shouldRead = getShouldRead();
538        // read a and b again, this time for their dependencies and also the rest since everything
539        // is ready to be read
540        assertExactMatch(shouldRead, a, b, c, abTernary, abTernary2);
541        mExprModel.markBitsRead();
542        shouldRead = getShouldRead();
543        assertEquals(0, shouldRead.size());
544    }
545
546    @Test
547    public void testInterExprCircularDependency4() {
548        LayoutBinder lb = new MockLayoutBinder();
549        mExprModel = lb.getModel();
550        IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(),
551                null);
552        IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(),
553                null);
554        IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(),
555                null);
556        IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName(),
557                null);
558        final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class);
559        final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class);
560        final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class);
561        mExprModel.seal();
562        List<Expr> shouldRead = getShouldRead();
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        assertExactMatch(shouldRead, a, b, d, cTernary.getIfTrue(), cTernary, abTernary, baTernary);
573        justRead.clear();
574
575        readFirst = getReadFirst(shouldRead);
576        assertExactMatch(readFirst, a, b, d);
577        Collections.addAll(justRead, a, b, d);
578
579        readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
580        assertExactMatch(readFirst, cTernary.getIfTrue(), abTernary, baTernary);
581        Collections.addAll(justRead, cTernary.getIfTrue(), abTernary, baTernary);
582
583        readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
584        assertExactMatch(readFirst, cTernary);
585        Collections.addAll(justRead, cTernary);
586
587        assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size());
588
589        assertFalse(mExprModel.markBitsRead());
590    }
591
592    @Test
593    public void testInterExprDependencyNotReadyYet() {
594        LayoutBinder lb = new MockLayoutBinder();
595        mExprModel = lb.getModel();
596        IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), null);
597        IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), null);
598        IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(), null);
599        IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName(), null);
600        IdentifierExpr e = lb.addVariable("e", boolean.class.getCanonicalName(), null);
601        final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class);
602        final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class);
603        final TernaryExpr eaTernary = parse(lb, "e ? a : false", TernaryExpr.class);
604        mExprModel.seal();
605        List<Expr> shouldRead = getShouldRead();
606        assertExactMatch(shouldRead, b, c, e);
607        assertTrue(mExprModel.markBitsRead());
608        shouldRead = getShouldRead();
609        assertExactMatch(shouldRead, a, baTernary, eaTernary);
610        assertTrue(mExprModel.markBitsRead());
611        shouldRead = getShouldRead();
612        assertExactMatch(shouldRead, d, cTernary.getIfTrue(), cTernary);
613        assertFalse(mExprModel.markBitsRead());
614    }
615
616    @Test
617    public void testNoFlagsForNonBindingStatic() {
618        LayoutBinder lb = new MockLayoutBinder();
619        mExprModel = lb.getModel();
620        lb.addVariable("a", int.class.getCanonicalName(), null);
621        final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
622        mExprModel.seal();
623        // +1 for invalidate all flag
624        assertEquals(1, parsed.getRight().getInvalidFlags().cardinality());
625        // +1 for invalidate all flag
626        assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
627        // +1 for invalidate all flag
628        assertEquals(2, mExprModel.getInvalidateableFieldLimit());
629    }
630
631    @Test
632    public void testFlagsForBindingStatic() {
633        LayoutBinder lb = new MockLayoutBinder();
634        mExprModel = lb.getModel();
635        lb.addVariable("a", int.class.getCanonicalName(), null);
636        final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
637        final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
638        mExprModel.seal();
639        assertTrue(staticParsed.isBindingExpression());
640        // +1 for invalidate all flag
641        assertEquals(1, staticParsed.getInvalidFlags().cardinality());
642        assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags());
643        // +1 for invalidate all flag
644        assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
645        // +1 for invalidate all flag
646        assertEquals(2, mExprModel.getInvalidateableFieldLimit());
647    }
648
649    @Test
650    public void testFinalFieldOfAVariable() {
651        LayoutBinder lb = new MockLayoutBinder();
652        mExprModel = lb.getModel();
653        IdentifierExpr user = lb.addVariable("user", User.class.getCanonicalName(),
654                null);
655        Expr fieldGet = parse(lb, "user.finalField", FieldAccessExpr.class);
656        mExprModel.seal();
657        assertTrue(fieldGet.isDynamic());
658        // read user
659        assertExactMatch(getShouldRead(), user, fieldGet);
660        mExprModel.markBitsRead();
661        // no need to read user.finalField
662        assertEquals(0, getShouldRead().size());
663    }
664
665    @Test
666    public void testFinalFieldOfAField() {
667        LayoutBinder lb = new MockLayoutBinder();
668        mExprModel = lb.getModel();
669        lb.addVariable("user", User.class.getCanonicalName(), null);
670        Expr finalFieldGet = parse(lb, "user.subObj.finalField", FieldAccessExpr.class);
671        mExprModel.seal();
672        assertTrue(finalFieldGet.isDynamic());
673        Expr userSubObjGet = finalFieldGet.getChildren().get(0);
674        // read user
675        List<Expr> shouldRead = getShouldRead();
676        assertEquals(3, shouldRead.size());
677        assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
678                finalFieldGet);
679        mExprModel.markBitsRead();
680        // no need to read user.subObj.finalField because it is final
681        assertEquals(0, getShouldRead().size());
682    }
683
684    @Test
685    public void testFinalFieldOfAMethod() {
686        LayoutBinder lb = new MockLayoutBinder();
687        mExprModel = lb.getModel();
688        lb.addVariable("user", User.class.getCanonicalName(), null);
689        Expr finalFieldGet = parse(lb, "user.anotherSubObj.finalField", FieldAccessExpr.class);
690        mExprModel.seal();
691        assertTrue(finalFieldGet.isDynamic());
692        Expr userSubObjGet = finalFieldGet.getChildren().get(0);
693        // read user
694        List<Expr> shouldRead = getShouldRead();
695        assertEquals(3, shouldRead.size());
696        assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
697                finalFieldGet);
698        mExprModel.markBitsRead();
699        // no need to read user.subObj.finalField because it is final
700        assertEquals(0, getShouldRead().size());
701    }
702
703    @Test
704    public void testFinalOfAClass() {
705        LayoutBinder lb = new MockLayoutBinder();
706        mExprModel = lb.getModel();
707        mExprModel.addImport("View", "android.view.View", null);
708        FieldAccessExpr fieldAccess = parse(lb, "View.VISIBLE", FieldAccessExpr.class);
709        assertFalse(fieldAccess.isDynamic());
710        mExprModel.seal();
711        assertEquals(0, getShouldRead().size());
712    }
713
714    @Test
715    public void testStaticFieldOfInstance() {
716        LayoutBinder lb = new MockLayoutBinder();
717        mExprModel = lb.getModel();
718        lb.addVariable("myView", "android.view.View", null);
719        FieldAccessExpr fieldAccess = parse(lb, "myView.VISIBLE", FieldAccessExpr.class);
720        assertFalse(fieldAccess.isDynamic());
721        mExprModel.seal();
722        assertEquals(0, getShouldRead().size());
723        final Expr child = fieldAccess.getChild();
724        assertTrue(child instanceof StaticIdentifierExpr);
725        StaticIdentifierExpr id = (StaticIdentifierExpr) child;
726        assertEquals(id.getResolvedType().getCanonicalName(), "android.view.View");
727        // on demand import
728        assertEquals("android.view.View", mExprModel.getImports().get("View"));
729    }
730
731    @Test
732    public void testOnDemandImportConflict() {
733        LayoutBinder lb = new MockLayoutBinder();
734        mExprModel = lb.getModel();
735        final IdentifierExpr myView = lb.addVariable("u", "android.view.View",
736                null);
737        mExprModel.addImport("View", User.class.getCanonicalName(), null);
738        final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(myView.getResolvedType());
739        mExprModel.seal();
740        // on demand import with conflict
741        assertEquals("android.view.View", mExprModel.getImports().get("View1"));
742        assertEquals("View1", id.getName());
743        assertEquals("android.view.View", id.getUserDefinedType());
744    }
745
746    @Test
747    public void testOnDemandImportAlreadyImported() {
748        LayoutBinder lb = new MockLayoutBinder();
749        mExprModel = lb.getModel();
750        final StaticIdentifierExpr ux = mExprModel.addImport("UX", User.class.getCanonicalName(),
751                null);
752        final IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName(),
753                null);
754        final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(u.getResolvedType());
755        mExprModel.seal();
756        // on demand import with conflict
757        assertSame(ux, id);
758    }
759
760    @Test
761    public void testStaticMethodOfInstance() {
762        LayoutBinder lb = new MockLayoutBinder();
763        mExprModel = lb.getModel();
764        lb.addVariable("user", User.class.getCanonicalName(), null);
765        MethodCallExpr methodCall = parse(lb, "user.ourStaticMethod()", MethodCallExpr.class);
766        assertTrue(methodCall.isDynamic());
767        mExprModel.seal();
768        final Expr child = methodCall.getTarget();
769        assertTrue(child instanceof StaticIdentifierExpr);
770        StaticIdentifierExpr id = (StaticIdentifierExpr) child;
771        assertEquals(id.getResolvedType().getCanonicalName(), User.class.getCanonicalName());
772    }
773
774    @Test
775    public void testFinalOfStaticField() {
776        LayoutBinder lb = new MockLayoutBinder();
777        mExprModel = lb.getModel();
778        mExprModel.addImport("UX", User.class.getCanonicalName(), null);
779        FieldAccessExpr fieldAccess = parse(lb, "UX.innerStaticInstance.finalStaticField",
780                FieldAccessExpr.class);
781        assertFalse(fieldAccess.isDynamic());
782        mExprModel.seal();
783        // nothing to read since it is all final and static
784        assertEquals(0, getShouldRead().size());
785    }
786
787    @Test
788    public void testFinalOfFinalStaticField() {
789        LayoutBinder lb = new MockLayoutBinder();
790        mExprModel = lb.getModel();
791        mExprModel.addImport("User", User.class.getCanonicalName(), null);
792        FieldAccessExpr fieldAccess = parse(lb, "User.innerFinalStaticInstance.finalStaticField",
793                FieldAccessExpr.class);
794        assertFalse(fieldAccess.isDynamic());
795        mExprModel.seal();
796        assertEquals(0, getShouldRead().size());
797    }
798
799    @Test
800    public void testLocationTracking() {
801        LayoutBinder lb = new MockLayoutBinder();
802        mExprModel = lb.getModel();
803        final String input = "a > 3 ? b : c";
804        TernaryExpr ternaryExpr = parse(lb, input, TernaryExpr.class);
805        final Location location = ternaryExpr.getLocations().get(0);
806        assertNotNull(location);
807        assertEquals(0, location.startLine);
808        assertEquals(0, location.startOffset);
809        assertEquals(0, location.endLine);
810        assertEquals(input.length() - 1, location.endOffset);
811
812        final ComparisonExpr comparison = (ComparisonExpr) ternaryExpr.getPred();
813        final Location predLoc = comparison.getLocations().get(0);
814        assertNotNull(predLoc);
815        assertEquals(0, predLoc.startLine);
816        assertEquals(0, predLoc.startOffset);
817        assertEquals(0, predLoc.endLine);
818        assertEquals(4, predLoc.endOffset);
819
820        final Location aLoc = comparison.getLeft().getLocations().get(0);
821        assertNotNull(aLoc);
822        assertEquals(0, aLoc.startLine);
823        assertEquals(0, aLoc.startOffset);
824        assertEquals(0, aLoc.endLine);
825        assertEquals(0, aLoc.endOffset);
826
827        final Location tLoc = comparison.getRight().getLocations().get(0);
828        assertNotNull(tLoc);
829        assertEquals(0, tLoc.startLine);
830        assertEquals(4, tLoc.startOffset);
831        assertEquals(0, tLoc.endLine);
832        assertEquals(4, tLoc.endOffset);
833
834        final Location bLoc = ternaryExpr.getIfTrue().getLocations().get(0);
835        assertNotNull(bLoc);
836        assertEquals(0, bLoc.startLine);
837        assertEquals(8, bLoc.startOffset);
838        assertEquals(0, bLoc.endLine);
839        assertEquals(8, bLoc.endOffset);
840
841        final Location cLoc = ternaryExpr.getIfFalse().getLocations().get(0);
842        assertNotNull(cLoc);
843        assertEquals(0, cLoc.startLine);
844        assertEquals(12, cLoc.startOffset);
845        assertEquals(0, cLoc.endLine);
846        assertEquals(12, cLoc.endOffset);
847    }
848
849//    TODO uncomment when we have inner static access
850//    @Test
851//    public void testFinalOfInnerStaticClass() {
852//        LayoutBinder lb = new MockLayoutBinder();
853//        mExprModel = lb.getModel();
854//        mExprModel.addImport("User", User.class.getCanonicalName());
855//        FieldAccessExpr fieldAccess = parse(lb, "User.InnerStaticClass.finalStaticField", FieldAccessExpr.class);
856//        assertFalse(fieldAccess.isDynamic());
857//        mExprModel.seal();
858//        assertEquals(0, getShouldRead().size());
859//    }
860
861    private void assertFlags(Expr a, int... flags) {
862        BitSet bitset = new BitSet();
863        for (int flag : flags) {
864            bitset.set(flag);
865        }
866        assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags());
867    }
868
869    private void assertFlags(Expr a, Expr... exprs) {
870        BitSet bitSet = a.getShouldReadFlags();
871        for (Expr expr : exprs) {
872            BitSet clone = (BitSet) bitSet.clone();
873            clone.and(expr.getInvalidFlags());
874            assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr
875                    .getUniqueKey(), expr.getInvalidFlags(), clone);
876        }
877
878        BitSet composite = new BitSet();
879        for (Expr expr : exprs) {
880            composite.or(expr.getInvalidFlags());
881        }
882        assertEquals("composite flags should match", composite, bitSet);
883    }
884
885    private void assertExactMatch(List<Expr> iterable, Expr... exprs) {
886        int i = 0;
887        String log = Arrays.toString(iterable.toArray());
888        log("list", iterable);
889        for (Expr expr : exprs) {
890            assertTrue((i++) + ":must contain " + expr.getUniqueKey() + "\n" + log,
891                    iterable.contains(expr));
892        }
893        i = 0;
894        for (Expr expr : iterable) {
895            assertTrue((i++) + ":must be expected " + expr.getUniqueKey(),
896                    ArrayUtils.contains(exprs, expr));
897        }
898    }
899
900    private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) {
901        final Expr parsed = binder.parse(input, null);
902        assertTrue(klass.isAssignableFrom(parsed.getClass()));
903        return (T) parsed;
904    }
905
906    private void log(String s, List<Expr> iterable) {
907        L.d(s);
908        for (Expr e : iterable) {
909            L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(),
910                    e.getShouldReadFlagsWithConditionals(), e.getReadSoFar());
911        }
912        L.d("end of %s", s);
913    }
914
915    private List<Expr> getReadFirst(List<Expr> shouldRead) {
916        return getReadFirst(shouldRead, null);
917    }
918
919    private List<Expr> getReadFirst(List<Expr> shouldRead, final List<Expr> justRead) {
920        List<Expr> result = new ArrayList<Expr>();
921        for (Expr expr : shouldRead) {
922            if (expr.shouldReadNow(justRead)) {
923                result.add(expr);
924            }
925        }
926        return result;
927    }
928
929    private List<Expr> getShouldRead() {
930        return mExprModel.filterShouldRead(mExprModel.getPendingExpressions());
931    }
932
933    public static class User extends BaseObservable {
934
935        String name;
936
937        String lastName;
938
939        public final int finalField = 5;
940
941        public static InnerStaticClass innerStaticInstance = new InnerStaticClass();
942
943        public static final InnerStaticClass innerFinalStaticInstance = new InnerStaticClass();
944
945        public SubObj subObj = new SubObj();
946
947        public String getName() {
948            return name;
949        }
950
951        public String getLastName() {
952            return lastName;
953        }
954
955        public boolean getCond(int i) {
956            return true;
957        }
958
959        public SubObj getAnotherSubObj() {
960            return new SubObj();
961        }
962
963        public static boolean ourStaticMethod() {
964            return true;
965        }
966
967        public String comment;
968
969        @Bindable
970        public boolean useComment() {
971            return true;
972        }
973
974        public static class InnerStaticClass {
975
976            public static final int finalField = 3;
977
978            public static final int finalStaticField = 3;
979        }
980    }
981
982    public static class SubObj {
983
984        public final int finalField = 5;
985    }
986
987}
988