• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.databinding.tool.expr;
18 
19 import android.databinding.Bindable;
20 import android.databinding.Observable;
21 import android.databinding.tool.LayoutBinder;
22 import android.databinding.tool.MockLayoutBinder;
23 import android.databinding.tool.reflection.ModelAnalyzer;
24 import android.databinding.tool.reflection.ModelClass;
25 import android.databinding.tool.reflection.java.JavaAnalyzer;
26 import android.databinding.tool.store.Location;
27 import android.databinding.tool.util.L;
28 import android.databinding.tool.writer.KCode;
29 
30 import org.junit.Before;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.junit.rules.TestWatcher;
34 import org.junit.runner.Description;
35 
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.BitSet;
39 import java.util.Collections;
40 import java.util.List;
41 
42 import static org.junit.Assert.assertEquals;
43 import static org.junit.Assert.assertFalse;
44 import static org.junit.Assert.assertNotNull;
45 import static org.junit.Assert.assertNotSame;
46 import static org.junit.Assert.assertNull;
47 import static org.junit.Assert.assertSame;
48 import static org.junit.Assert.assertTrue;
49 
50 @SuppressWarnings("Duplicates")
51 public class ExprModelTest {
52 
53     private static class DummyExpr extends Expr {
54 
55         String mKey;
56 
DummyExpr(String key, DummyExpr... children)57         public DummyExpr(String key, DummyExpr... children) {
58             super(children);
59             mKey = key;
60         }
61 
62         @Override
resolveType(ModelAnalyzer modelAnalyzer)63         protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
64             return modelAnalyzer.findClass(Integer.class);
65         }
66 
67         @Override
constructDependencies()68         protected List<Dependency> constructDependencies() {
69             return constructDynamicChildrenDependencies();
70         }
71 
72         @Override
computeUniqueKey()73         protected String computeUniqueKey() {
74             return mKey + super.computeUniqueKey();
75         }
76 
77         @Override
generateCode()78         protected KCode generateCode() {
79             return new KCode();
80         }
81 
82         @Override
cloneToModel(ExprModel model)83         public Expr cloneToModel(ExprModel model) {
84             return this;
85         }
86 
87         @Override
getInvertibleError()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
setUp()109     public void setUp() throws Exception {
110         JavaAnalyzer.initForTests();
111         mExprModel = new ExprModel();
112     }
113 
114     @Test
testAddNormal()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
testAddDupe1()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
testAddMultiple()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
testAddWithChildren()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
testShouldRead()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
testReadConstantTernary()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
testTernaryWithPlus()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 
filterOut(List<Expr> itr, final List<Expr> exclude)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
testTernaryInsideTernary()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
testRequirementFlags()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
testPostConditionalDependencies()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
testCircularDependency()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
testNestedCircularDependency()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
testInterExprDependency()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
testInterExprCircularDependency()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
testInterExprCircularDependency2()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
testInterExprCircularDependency3()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
testInterExprCircularDependency4()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
testInterExprDeepDependency()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
testInterExprDependencyNotReadyYet()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
testInvalidateConditional()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
testNoFlagsForNonBindingStatic()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
testFlagsForBindingStatic()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
testFinalFieldOfAVariable()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
testFinalFieldOfAField()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
testFinalFieldOfAMethod()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
testFinalOfAClass()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
testStaticFieldOfInstance()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
testOnDemandImportConflict()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
testOnDemandImportAlreadyImported()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
testStaticMethodOfInstance()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
testVoid()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
testVoid2()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
testShruggy()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
testParseNoArgLambda()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
testParseLambdaWithSingleArg()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
testParseLambdaWithArguments()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 
hasIdentifier(ExprModel model, String name)989     private boolean hasIdentifier(ExprModel model, String name) {
990         return getIdentifier(model, name) != null;
991     }
992 
getIdentifier(ExprModel model, String name)993     private Expr getIdentifier(ExprModel model, String name) {
994         return model.getExprMap().get(new IdentifierExpr(name).getUniqueKey());
995     }
996 
hasCallbackIdentifier(ExprModel model, int index, String name)997     private boolean hasCallbackIdentifier(ExprModel model, int index, String name) {
998         return getCallbackIdentifier(model, index, name) != null;
999     }
1000 
getCallbackIdentifier(ExprModel model, int index, String name)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
testFinalOfStaticField()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
testFinalOfFinalStaticField()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
testLocationTracking()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 
assertFlags(Expr a, int... flags)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 
assertFlags(Expr a, Expr... exprs)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 
assertExactMatch(List<Expr> iterable, Expr... exprs)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 
parse(LayoutBinder binder, String input, Class<T> klass)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 
log(String s, List<Expr> iterable)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 
getReadFirst(List<Expr> shouldRead)1149     private List<Expr> getReadFirst(List<Expr> shouldRead) {
1150         return getReadFirst(shouldRead, null);
1151     }
1152 
getReadFirst(List<Expr> shouldRead, final List<Expr> justRead)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 
getShouldRead()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 
getName()1186         public String getName() {
1187             return name;
1188         }
1189 
getLastName()1190         public String getLastName() {
1191             return lastName;
1192         }
1193 
getCond(int i)1194         public boolean getCond(int i) {
1195             return true;
1196         }
1197 
getAnotherSubObj()1198         public SubObj getAnotherSubObj() {
1199             return new SubObj();
1200         }
1201 
ourStaticMethod()1202         public static boolean ourStaticMethod() {
1203             return true;
1204         }
1205 
1206         public String comment;
1207 
1208         @Bindable
getUseComment()1209         public boolean getUseComment() {
1210             return true;
1211         }
1212 
1213         @Override
addOnPropertyChangedCallback(OnPropertyChangedCallback callback)1214         public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
1215 
1216         }
1217 
1218         @Override
removeOnPropertyChangedCallback(OnPropertyChangedCallback callback)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