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