/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.databinding.tool.expr;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import android.databinding.BaseObservable;
import android.databinding.tool.LayoutBinder;
import android.databinding.tool.MockLayoutBinder;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.reflection.java.JavaAnalyzer;
import android.databinding.tool.store.Location;
import android.databinding.tool.util.L;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class ExprModelTest {
private static class DummyExpr extends Expr {
String mKey;
public DummyExpr(String key, DummyExpr... children) {
super(children);
mKey = key;
}
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
return modelAnalyzer.findClass(Integer.class);
}
@Override
protected List<Dependency> constructDependencies() {
return constructDynamicChildrenDependencies();
}
@Override
protected String computeUniqueKey() {
return mKey + super.computeUniqueKey();
}
}
ExprModel mExprModel;
@Rule
public TestWatcher mTestWatcher = new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
if (mExprModel != null && mExprModel.getFlagMapping() != null) {
final String[] mapping = mExprModel.getFlagMapping();
for (int i = 0; i < mapping.length; i++) {
L.d("flag %d: %s", i, mapping[i]);
}
}
}
};
@Before
public void setUp() throws Exception {
JavaAnalyzer.initForTests();
mExprModel = new ExprModel();
}
@Test
public void testAddNormal() {
final DummyExpr d = new DummyExpr("a");
assertSame(d, mExprModel.register(d));
assertSame(d, mExprModel.register(d));
assertEquals(1, mExprModel.mExprMap.size());
}
@Test
public void testAddDupe1() {
final DummyExpr d = new DummyExpr("a");
assertSame(d, mExprModel.register(d));
assertSame(d, mExprModel.register(new DummyExpr("a")));
assertEquals(1, mExprModel.mExprMap.size());
}
@Test
public void testAddMultiple() {
mExprModel.register(new DummyExpr("a"));
mExprModel.register(new DummyExpr("b"));
assertEquals(2, mExprModel.mExprMap.size());
}
@Test
public void testAddWithChildren() {
DummyExpr a = new DummyExpr("a");
DummyExpr b = new DummyExpr("b");
DummyExpr c = new DummyExpr("c", a, b);
mExprModel.register(c);
DummyExpr a2 = new DummyExpr("a");
DummyExpr b2 = new DummyExpr("b");
DummyExpr c2 = new DummyExpr("c", a, b);
assertEquals(c, mExprModel.register(c2));
}
@Test
public void testShouldRead() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", "java.lang.String", null);
IdentifierExpr b = lb.addVariable("b", "java.lang.String", null);
IdentifierExpr c = lb.addVariable("c", "java.lang.String", null);
lb.parse("a == null ? b : c", null);
mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class));
lb.getModel().seal();
List<Expr> shouldRead = getShouldRead();
// a and a == null
assertEquals(2, shouldRead.size());
final List<Expr> readFirst = getReadFirst(shouldRead, null);
assertEquals(1, readFirst.size());
final Expr first = readFirst.get(0);
assertSame(a, first);
// now , assume we've read this
final BitSet shouldReadFlags = first.getShouldReadFlags();
assertNotNull(shouldReadFlags);
}
@Test
public void testTernaryWithPlus() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr user = lb
.addVariable("user", "android.databinding.tool.expr.ExprModelTest.User",
null);
MathExpr parsed = parse(lb, "user.name + \" \" + (user.lastName ?? \"\")", MathExpr.class);
mExprModel.seal();
List<Expr> toRead = getShouldRead();
List<Expr> readNow = getReadFirst(toRead);
assertEquals(1, readNow.size());
assertSame(user, readNow.get(0));
List<Expr> justRead = new ArrayList<Expr>();
justRead.add(user);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertEquals(2, readNow.size()); //user.name && user.lastName
justRead.addAll(readNow);
// user.lastname (T, F), user.name + " "
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertEquals(2, readNow.size()); //user.name && user.lastName
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertEquals(0, readNow.size());
mExprModel.markBitsRead();
toRead = getShouldRead();
assertEquals(2, toRead.size());
justRead.clear();
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertEquals(1, readNow.size());
assertSame(parsed.getRight(), readNow.get(0));
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertEquals(1, readNow.size());
assertSame(parsed, readNow.get(0));
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertEquals(0, readNow.size());
mExprModel.markBitsRead();
assertEquals(0, getShouldRead().size());
}
private List<Expr> filterOut(List<Expr> itr, final List<Expr> exclude) {
List<Expr> result = new ArrayList<Expr>();
for (Expr expr : itr) {
if (!exclude.contains(expr)) {
result.add(expr);
}
}
return result;
}
@Test
public void testTernaryInsideTernary() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr cond1 = lb.addVariable("cond1", "boolean", null);
IdentifierExpr cond2 = lb.addVariable("cond2", "boolean", null);
IdentifierExpr a = lb.addVariable("a", "boolean", null);
IdentifierExpr b = lb.addVariable("b", "boolean", null);
IdentifierExpr c = lb.addVariable("c", "boolean", null);
final TernaryExpr ternaryExpr = parse(lb, "cond1 ? cond2 ? a : b : c", TernaryExpr.class);
final TernaryExpr innerTernary = (TernaryExpr) ternaryExpr.getIfTrue();
mExprModel.seal();
List<Expr> toRead = getShouldRead();
assertEquals(1, toRead.size());
assertEquals(ternaryExpr.getPred(), toRead.get(0));
List<Expr> readNow = getReadFirst(toRead);
assertEquals(1, readNow.size());
assertEquals(ternaryExpr.getPred(), readNow.get(0));
int cond1True = ternaryExpr.getRequirementFlagIndex(true);
int cond1False = ternaryExpr.getRequirementFlagIndex(false);
// ok, it is read now.
mExprModel.markBitsRead();
// now it should read cond2 or c, depending on the flag from first
toRead = getShouldRead();
assertEquals(2, toRead.size());
assertExactMatch(toRead, ternaryExpr.getIfFalse(), innerTernary.getPred());
assertFlags(ternaryExpr.getIfFalse(), cond1False);
assertFlags(ternaryExpr.getIfTrue(), cond1True);
mExprModel.markBitsRead();
// now it should read a or b, innerTernary, outerTernary
toRead = getShouldRead();
assertExactMatch(toRead, innerTernary.getIfTrue(), innerTernary.getIfFalse(), ternaryExpr,
innerTernary);
assertFlags(innerTernary.getIfTrue(), innerTernary.getRequirementFlagIndex(true));
assertFlags(innerTernary.getIfFalse(), innerTernary.getRequirementFlagIndex(false));
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testRequirementFlags() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", "java.lang.String", null);
IdentifierExpr b = lb.addVariable("b", "java.lang.String", null);
IdentifierExpr c = lb.addVariable("c", "java.lang.String", null);
IdentifierExpr d = lb.addVariable("d", "java.lang.String", null);
IdentifierExpr e = lb.addVariable("e", "java.lang.String", null);
final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e", null);
assertTrue(aTernary instanceof TernaryExpr);
final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue();
assertTrue(bTernary instanceof TernaryExpr);
final Expr aIsNull = mExprModel
.comparison("==", a, mExprModel.symbol("null", Object.class));
final Expr bIsNull = mExprModel
.comparison("==", b, mExprModel.symbol("null", Object.class));
lb.getModel().seal();
List<Expr> shouldRead = getShouldRead();
// a and a == null
assertEquals(2, shouldRead.size());
assertFalse(a.getShouldReadFlags().isEmpty());
assertTrue(a.getShouldReadFlags().get(a.getId()));
assertTrue(b.getShouldReadFlags().isEmpty());
assertTrue(c.getShouldReadFlags().isEmpty());
assertTrue(d.getShouldReadFlags().isEmpty());
assertTrue(e.getShouldReadFlags().isEmpty());
List<Expr> readFirst = getReadFirst(shouldRead, null);
assertEquals(1, readFirst.size());
final Expr first = readFirst.get(0);
assertSame(a, first);
assertTrue(mExprModel.markBitsRead());
for (Expr expr : mExprModel.getPendingExpressions()) {
assertNull(expr.mShouldReadFlags);
}
shouldRead = getShouldRead();
assertExactMatch(shouldRead, e, b, bIsNull);
assertFlags(e, aTernary.getRequirementFlagIndex(false));
assertFlags(b, aTernary.getRequirementFlagIndex(true));
assertFlags(bIsNull, aTernary.getRequirementFlagIndex(true));
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertEquals(4, shouldRead.size());
assertTrue(shouldRead.contains(c));
assertTrue(shouldRead.contains(d));
assertTrue(shouldRead.contains(aTernary));
assertTrue(shouldRead.contains(bTernary));
assertTrue(c.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(true)));
assertEquals(1, c.getShouldReadFlags().cardinality());
assertTrue(d.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(false)));
assertEquals(1, d.getShouldReadFlags().cardinality());
assertTrue(bTernary.getShouldReadFlags().get(aTernary.getRequirementFlagIndex(true)));
assertEquals(1, bTernary.getShouldReadFlags().cardinality());
// +1 for invalidate all flag
assertEquals(6, aTernary.getShouldReadFlags().cardinality());
for (Expr expr : new Expr[]{a, b, c, d, e}) {
assertTrue(aTernary.getShouldReadFlags().get(expr.getId()));
}
readFirst = getReadFirst(shouldRead);
assertEquals(2, readFirst.size());
assertTrue(readFirst.contains(c));
assertTrue(readFirst.contains(d));
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testPostConditionalDependencies() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName(), null);
IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName(), null);
IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(), null);
IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(), null);
IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName(), null);
IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName(), null);
IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName(), null);
TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class);
TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx`"
+ " + u2.getCond(e) ", TernaryExpr.class);
Expr abCmp = abTernary.getPred();
Expr bcCmp = bcTernary.getPred();
Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred();
final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse();
Expr u2GetCondE = xxPlusU2getCondE.getRight();
Expr u1Name = abTernary.getIfTrue();
Expr u2Name = abTernary.getIfFalse();
Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue();
Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse();
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b, c, abCmp, bcCmp);
List<Expr> firstRead = getReadFirst(shouldRead);
assertExactMatch(firstRead, a, b, c);
assertFlags(a, a, b, u1, u2, u1Name, u2Name);
assertFlags(b, a, b, u1, u2, u1Name, u2Name, c, d, u1LastName, u2LastName, e);
assertFlags(c, b, c, u1, d, u1LastName, u2LastName, e);
assertFlags(abCmp, a, b, u1, u2, u1Name, u2Name);
assertFlags(bcCmp, b, c, u1, d, u1LastName, u2LastName, e);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
Expr[] batch = {d, e, u1, u2, u1GetCondD, u2GetCondE, xxPlusU2getCondE, abTernary,
abTernary.getIfTrue(), abTernary.getIfFalse()};
assertExactMatch(shouldRead, batch);
firstRead = getReadFirst(shouldRead);
assertExactMatch(firstRead, d, e, u1, u2);
assertFlags(d, bcTernary.getRequirementFlagIndex(true));
assertFlags(e, bcTernary.getRequirementFlagIndex(false));
assertFlags(u1, bcTernary.getRequirementFlagIndex(true),
abTernary.getRequirementFlagIndex(true));
assertFlags(u2, bcTernary.getRequirementFlagIndex(false),
abTernary.getRequirementFlagIndex(false));
assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true));
assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false));
assertFlags(xxPlusU2getCondE, bcTernary.getRequirementFlagIndex(false));
assertFlags(abTernary, a, b, u1, u2, u1Name, u2Name);
assertFlags(abTernary.getIfTrue(), abTernary.getRequirementFlagIndex(true));
assertFlags(abTernary.getIfFalse(), abTernary.getRequirementFlagIndex(false));
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
// actually, there is no real case to read u1 anymore because if b>c was not true,
// u1.getCond(d) will never be set. Right now, we don't have mechanism to figure this out
// and also it does not affect correctness (just an unnecessary if stmt)
assertExactMatch(shouldRead, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary);
firstRead = getReadFirst(shouldRead);
assertExactMatch(firstRead, u1LastName, u2);
assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true));
assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false));
assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false));
assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true));
assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e);
}
@Test
public void testCircularDependency() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(),
null);
IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(),
null);
final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, abTernary.getPred());
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, b, abTernary);
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testNestedCircularDependency() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(),
null);
IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(),
null);
IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName(),
null);
final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class);
final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue();
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, a3Ternary.getPred());
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, c, c4Ternary.getPred());
assertFlags(c, a3Ternary.getRequirementFlagIndex(true),
a3Ternary.getRequirementFlagIndex(false));
assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true));
}
@Test
public void testInterExprCircularDependency() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName(),
null);
IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName(),
null);
final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
final TernaryExpr abTernary2 = parse(lb, "b > 3 ? b : a", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b, abTernary.getPred(), abTernary2.getPred());
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, abTernary, abTernary2);
}
@Test
public void testInterExprCircularDependency2() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(),
null);
IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(),
null);
final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class);
final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b);
List<Expr> readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, a, b);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
// read a and b again, this time for their dependencies and also the rest since everything
// is ready to be read
assertExactMatch(shouldRead, a, b, abTernary, baTernary);
List<Expr> justRead = new ArrayList<Expr>();
readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, a, b);
Collections.addAll(justRead, a, b);
readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertExactMatch(readFirst, abTernary, baTernary);
assertFalse(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertEquals(0, shouldRead.size());
}
@Test
public void testInterExprCircularDependency3() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(),
null);
IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(),
null);
IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(),
null);
final TernaryExpr abTernary = parse(lb, "a ? b : c", TernaryExpr.class);
final TernaryExpr abTernary2 = parse(lb, "b ? a : c", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
// read a and b again, this time for their dependencies and also the rest since everything
// is ready to be read
assertExactMatch(shouldRead, a, b, c, abTernary, abTernary2);
mExprModel.markBitsRead();
shouldRead = getShouldRead();
assertEquals(0, shouldRead.size());
}
@Test
public void testInterExprCircularDependency4() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(),
null);
IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(),
null);
IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(),
null);
IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName(),
null);
final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class);
final TernaryExpr abTernary = parse(lb, "a ? b : true", TernaryExpr.class);
final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, c, a, b);
List<Expr> justRead = new ArrayList<Expr>();
List<Expr> readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, c, a, b);
Collections.addAll(justRead, a, b, c);
assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size());
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b, d, cTernary.getIfTrue(), cTernary, abTernary, baTernary);
justRead.clear();
readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, a, b, d);
Collections.addAll(justRead, a, b, d);
readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertExactMatch(readFirst, cTernary.getIfTrue(), abTernary, baTernary);
Collections.addAll(justRead, cTernary.getIfTrue(), abTernary, baTernary);
readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertExactMatch(readFirst, cTernary);
Collections.addAll(justRead, cTernary);
assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size());
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testInterExprDependencyNotReadyYet() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", boolean.class.getCanonicalName(), null);
IdentifierExpr b = lb.addVariable("b", boolean.class.getCanonicalName(), null);
IdentifierExpr c = lb.addVariable("c", boolean.class.getCanonicalName(), null);
IdentifierExpr d = lb.addVariable("d", boolean.class.getCanonicalName(), null);
IdentifierExpr e = lb.addVariable("e", boolean.class.getCanonicalName(), null);
final TernaryExpr cTernary = parse(lb, "c ? (a ? d : false) : false", TernaryExpr.class);
final TernaryExpr baTernary = parse(lb, "b ? a : false", TernaryExpr.class);
final TernaryExpr eaTernary = parse(lb, "e ? a : false", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, b, c, e);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, baTernary, eaTernary);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, d, cTernary.getIfTrue(), cTernary);
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testNoFlagsForNonBindingStatic() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("a", int.class.getCanonicalName(), null);
final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
mExprModel.seal();
// +1 for invalidate all flag
assertEquals(1, parsed.getRight().getInvalidFlags().cardinality());
// +1 for invalidate all flag
assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
// +1 for invalidate all flag
assertEquals(2, mExprModel.getInvalidateableFieldLimit());
}
@Test
public void testFlagsForBindingStatic() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("a", int.class.getCanonicalName(), null);
final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
mExprModel.seal();
assertTrue(staticParsed.isBindingExpression());
// +1 for invalidate all flag
assertEquals(1, staticParsed.getInvalidFlags().cardinality());
assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags());
// +1 for invalidate all flag
assertEquals(2, parsed.getLeft().getInvalidFlags().cardinality());
// +1 for invalidate all flag
assertEquals(2, mExprModel.getInvalidateableFieldLimit());
}
@Test
public void testFinalFieldOfAVariable() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr user = lb.addVariable("user", User.class.getCanonicalName(),
null);
Expr fieldGet = parse(lb, "user.finalField", FieldAccessExpr.class);
mExprModel.seal();
assertTrue(fieldGet.isDynamic());
// read user
assertExactMatch(getShouldRead(), user, fieldGet);
mExprModel.markBitsRead();
// no need to read user.finalField
assertEquals(0, getShouldRead().size());
}
@Test
public void testFinalFieldOfAField() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("user", User.class.getCanonicalName(), null);
Expr finalFieldGet = parse(lb, "user.subObj.finalField", FieldAccessExpr.class);
mExprModel.seal();
assertTrue(finalFieldGet.isDynamic());
Expr userSubObjGet = finalFieldGet.getChildren().get(0);
// read user
List<Expr> shouldRead = getShouldRead();
assertEquals(3, shouldRead.size());
assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
finalFieldGet);
mExprModel.markBitsRead();
// no need to read user.subObj.finalField because it is final
assertEquals(0, getShouldRead().size());
}
@Test
public void testFinalFieldOfAMethod() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("user", User.class.getCanonicalName(), null);
Expr finalFieldGet = parse(lb, "user.anotherSubObj.finalField", FieldAccessExpr.class);
mExprModel.seal();
assertTrue(finalFieldGet.isDynamic());
Expr userSubObjGet = finalFieldGet.getChildren().get(0);
// read user
List<Expr> shouldRead = getShouldRead();
assertEquals(3, shouldRead.size());
assertExactMatch(shouldRead, userSubObjGet.getChildren().get(0), userSubObjGet,
finalFieldGet);
mExprModel.markBitsRead();
// no need to read user.subObj.finalField because it is final
assertEquals(0, getShouldRead().size());
}
@Test
public void testFinalOfAClass() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
mExprModel.addImport("View", "android.view.View", null);
FieldAccessExpr fieldAccess = parse(lb, "View.VISIBLE", FieldAccessExpr.class);
assertFalse(fieldAccess.isDynamic());
mExprModel.seal();
assertEquals(0, getShouldRead().size());
}
@Test
public void testStaticFieldOfInstance() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("myView", "android.view.View", null);
FieldAccessExpr fieldAccess = parse(lb, "myView.VISIBLE", FieldAccessExpr.class);
assertFalse(fieldAccess.isDynamic());
mExprModel.seal();
assertEquals(0, getShouldRead().size());
final Expr child = fieldAccess.getChild();
assertTrue(child instanceof StaticIdentifierExpr);
StaticIdentifierExpr id = (StaticIdentifierExpr) child;
assertEquals(id.getResolvedType().getCanonicalName(), "android.view.View");
// on demand import
assertEquals("android.view.View", mExprModel.getImports().get("View"));
}
@Test
public void testOnDemandImportConflict() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final IdentifierExpr myView = lb.addVariable("u", "android.view.View",
null);
mExprModel.addImport("View", User.class.getCanonicalName(), null);
final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(myView.getResolvedType());
mExprModel.seal();
// on demand import with conflict
assertEquals("android.view.View", mExprModel.getImports().get("View1"));
assertEquals("View1", id.getName());
assertEquals("android.view.View", id.getUserDefinedType());
}
@Test
public void testOnDemandImportAlreadyImported() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final StaticIdentifierExpr ux = mExprModel.addImport("UX", User.class.getCanonicalName(),
null);
final IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName(),
null);
final StaticIdentifierExpr id = mExprModel.staticIdentifierFor(u.getResolvedType());
mExprModel.seal();
// on demand import with conflict
assertSame(ux, id);
}
@Test
public void testStaticMethodOfInstance() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("user", User.class.getCanonicalName(), null);
MethodCallExpr methodCall = parse(lb, "user.ourStaticMethod()", MethodCallExpr.class);
assertTrue(methodCall.isDynamic());
mExprModel.seal();
final Expr child = methodCall.getTarget();
assertTrue(child instanceof StaticIdentifierExpr);
StaticIdentifierExpr id = (StaticIdentifierExpr) child;
assertEquals(id.getResolvedType().getCanonicalName(), User.class.getCanonicalName());
}
@Test
public void testFinalOfStaticField() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
mExprModel.addImport("UX", User.class.getCanonicalName(), null);
FieldAccessExpr fieldAccess = parse(lb, "UX.innerStaticInstance.finalStaticField",
FieldAccessExpr.class);
assertFalse(fieldAccess.isDynamic());
mExprModel.seal();
// nothing to read since it is all final and static
assertEquals(0, getShouldRead().size());
}
@Test
public void testFinalOfFinalStaticField() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
mExprModel.addImport("User", User.class.getCanonicalName(), null);
FieldAccessExpr fieldAccess = parse(lb, "User.innerFinalStaticInstance.finalStaticField",
FieldAccessExpr.class);
assertFalse(fieldAccess.isDynamic());
mExprModel.seal();
assertEquals(0, getShouldRead().size());
}
@Test
public void testLocationTracking() {
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final String input = "a > 3 ? b : c";
TernaryExpr ternaryExpr = parse(lb, input, TernaryExpr.class);
final Location location = ternaryExpr.getLocations().get(0);
assertNotNull(location);
assertEquals(0, location.startLine);
assertEquals(0, location.startOffset);
assertEquals(0, location.endLine);
assertEquals(input.length() - 1, location.endOffset);
final ComparisonExpr comparison = (ComparisonExpr) ternaryExpr.getPred();
final Location predLoc = comparison.getLocations().get(0);
assertNotNull(predLoc);
assertEquals(0, predLoc.startLine);
assertEquals(0, predLoc.startOffset);
assertEquals(0, predLoc.endLine);
assertEquals(4, predLoc.endOffset);
final Location aLoc = comparison.getLeft().getLocations().get(0);
assertNotNull(aLoc);
assertEquals(0, aLoc.startLine);
assertEquals(0, aLoc.startOffset);
assertEquals(0, aLoc.endLine);
assertEquals(0, aLoc.endOffset);
final Location tLoc = comparison.getRight().getLocations().get(0);
assertNotNull(tLoc);
assertEquals(0, tLoc.startLine);
assertEquals(4, tLoc.startOffset);
assertEquals(0, tLoc.endLine);
assertEquals(4, tLoc.endOffset);
final Location bLoc = ternaryExpr.getIfTrue().getLocations().get(0);
assertNotNull(bLoc);
assertEquals(0, bLoc.startLine);
assertEquals(8, bLoc.startOffset);
assertEquals(0, bLoc.endLine);
assertEquals(8, bLoc.endOffset);
final Location cLoc = ternaryExpr.getIfFalse().getLocations().get(0);
assertNotNull(cLoc);
assertEquals(0, cLoc.startLine);
assertEquals(12, cLoc.startOffset);
assertEquals(0, cLoc.endLine);
assertEquals(12, cLoc.endOffset);
}
// TODO uncomment when we have inner static access
// @Test
// public void testFinalOfInnerStaticClass() {
// LayoutBinder lb = new MockLayoutBinder();
// mExprModel = lb.getModel();
// mExprModel.addImport("User", User.class.getCanonicalName());
// FieldAccessExpr fieldAccess = parse(lb, "User.InnerStaticClass.finalStaticField", FieldAccessExpr.class);
// assertFalse(fieldAccess.isDynamic());
// mExprModel.seal();
// assertEquals(0, getShouldRead().size());
// }
private void assertFlags(Expr a, int... flags) {
BitSet bitset = new BitSet();
for (int flag : flags) {
bitset.set(flag);
}
assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags());
}
private void assertFlags(Expr a, Expr... exprs) {
BitSet bitSet = a.getShouldReadFlags();
for (Expr expr : exprs) {
BitSet clone = (BitSet) bitSet.clone();
clone.and(expr.getInvalidFlags());
assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr
.getUniqueKey(), expr.getInvalidFlags(), clone);
}
BitSet composite = new BitSet();
for (Expr expr : exprs) {
composite.or(expr.getInvalidFlags());
}
assertEquals("composite flags should match", composite, bitSet);
}
private void assertExactMatch(List<Expr> iterable, Expr... exprs) {
int i = 0;
String log = Arrays.toString(iterable.toArray());
log("list", iterable);
for (Expr expr : exprs) {
assertTrue((i++) + ":must contain " + expr.getUniqueKey() + "\n" + log,
iterable.contains(expr));
}
i = 0;
for (Expr expr : iterable) {
assertTrue((i++) + ":must be expected " + expr.getUniqueKey(),
ArrayUtils.contains(exprs, expr));
}
}
private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) {
final Expr parsed = binder.parse(input, null);
assertTrue(klass.isAssignableFrom(parsed.getClass()));
return (T) parsed;
}
private void log(String s, List<Expr> iterable) {
L.d(s);
for (Expr e : iterable) {
L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(),
e.getShouldReadFlagsWithConditionals(), e.getReadSoFar());
}
L.d("end of %s", s);
}
private List<Expr> getReadFirst(List<Expr> shouldRead) {
return getReadFirst(shouldRead, null);
}
private List<Expr> getReadFirst(List<Expr> shouldRead, final List<Expr> justRead) {
List<Expr> result = new ArrayList<Expr>();
for (Expr expr : shouldRead) {
if (expr.shouldReadNow(justRead)) {
result.add(expr);
}
}
return result;
}
private List<Expr> getShouldRead() {
return mExprModel.filterShouldRead(mExprModel.getPendingExpressions());
}
public static class User extends BaseObservable {
String name;
String lastName;
public final int finalField = 5;
public static InnerStaticClass innerStaticInstance = new InnerStaticClass();
public static final InnerStaticClass innerFinalStaticInstance = new InnerStaticClass();
public SubObj subObj = new SubObj();
public String getName() {
return name;
}
public String getLastName() {
return lastName;
}
public boolean getCond(int i) {
return true;
}
public SubObj getAnotherSubObj() {
return new SubObj();
}
public static boolean ourStaticMethod() {
return true;
}
public static class InnerStaticClass {
public static final int finalField = 3;
public static final int finalStaticField = 3;
}
}
public static class SubObj {
public final int finalField = 5;
}
}