blob: a661c30fa4755aff00d06ffaed8c26ad3c9e68fc [file] [log] [blame]
/*
* 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 android.databinding.Bindable;
import android.databinding.Observable;
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 android.databinding.tool.writer.KCode;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
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.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("Duplicates")
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();
}
@Override
protected KCode generateCode() {
return new KCode();
}
@Override
public Expr cloneToModel(ExprModel model) {
return this;
}
@Override
protected String getInvertibleError() {
return "DummyExpr cannot be 2-way.";
}
}
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() {
MockLayoutBinder 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, 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 testReadConstantTernary() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", "java.lang.String", null);
IdentifierExpr b = lb.addVariable("b", "java.lang.String", null);
TernaryExpr ternaryExpr = parse(lb, "true ? a : b", TernaryExpr.class);
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, ternaryExpr.getPred());
List<Expr> first = getReadFirst(shouldRead);
assertExactMatch(first, ternaryExpr.getPred());
mExprModel.markBitsRead();
shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b, ternaryExpr);
first = getReadFirst(shouldRead);
assertExactMatch(first, a, b);
List<Expr> justRead = new ArrayList<Expr>();
justRead.add(a);
justRead.add(b);
first = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertExactMatch(first, ternaryExpr);
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testTernaryWithPlus() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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, 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() {
MockLayoutBinder 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();
assertExactMatch(shouldRead, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary);
firstRead = getReadFirst(shouldRead);
assertExactMatch(firstRead, u1LastName, u2);
assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false));
assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true));
assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false));
assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true));
assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e);
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testCircularDependency() {
MockLayoutBinder 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() {
MockLayoutBinder 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 testInterExprDependency() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr u = lb.addVariable("u", User.class.getCanonicalName(),
null);
final Expr uComment = parse(lb, "u.comment", FieldAccessExpr.class);
final TernaryExpr uTernary = parse(lb, "u.getUseComment ? u.comment : `xx`", TernaryExpr.class);
mExprModel.seal();
assertTrue(uTernary.getPred().canBeInvalidated());
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, u, uComment, uTernary.getPred());
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, uComment, uTernary);
}
@Test
public void testInterExprCircularDependency() {
MockLayoutBinder 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() {
MockLayoutBinder 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);
assertFlags(a, a, b);
assertFlags(b, a, b);
List<Expr> readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, a, b);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, abTernary, baTernary);
readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, abTernary, baTernary);
assertFalse(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertEquals(0, shouldRead.size());
}
@Test
public void testInterExprCircularDependency3() {
MockLayoutBinder 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, c, abTernary, abTernary2);
mExprModel.markBitsRead();
shouldRead = getShouldRead();
assertEquals(0, shouldRead.size());
}
@Test
public void testInterExprCircularDependency4() {
MockLayoutBinder 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();
// check if a,b or c should be read. these are easily calculated from binding expressions'
// invalidation
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();
// if a and b are not invalid, a won't be read in the first step. But if c's expression
// is invalid and c == true, a must be read. Depending on a, d might be read as well.
// don't need to read b anymore because `a ? b : true` and `b ? a : false` has the same
// invalidation flags.
assertExactMatch(shouldRead, a, baTernary);
justRead.clear();
readFirst = getReadFirst(shouldRead);
// first must read `a`.
assertExactMatch(readFirst, a);
Collections.addAll(justRead, a);
readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertExactMatch(readFirst, baTernary);
Collections.addAll(justRead, baTernary);
readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertEquals(0, filterOut(getReadFirst(shouldRead, justRead), justRead).size());
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
// now we can read abTernary as well which had a conditional dependency on a but we did not
// elevate its dependency since we had other expressions elevated already
// now we can read adf ternary and c ternary
justRead.clear();
assertExactMatch(shouldRead, abTernary, d, cTernary.getIfTrue(), cTernary);
readFirst = getReadFirst(shouldRead);
assertExactMatch(readFirst, d, abTernary);
Collections.addAll(justRead, d, abTernary);
readFirst = filterOut(getReadFirst(shouldRead, justRead), justRead);
assertExactMatch(readFirst, cTernary.getIfTrue());
Collections.addAll(justRead, cTernary.getIfTrue());
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 testInterExprDeepDependency() {
MockLayoutBinder 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 t1 = parse(lb, "c ? (a ? b : true) : false", TernaryExpr.class);
final TernaryExpr t2 = parse(lb, "c ? (b ? a : false) : true", TernaryExpr.class);
final TernaryExpr abTernary = (TernaryExpr) t1.getIfTrue();
final TernaryExpr baTernary = (TernaryExpr) t2.getIfTrue();
mExprModel.seal();
List<Expr> shouldRead = getShouldRead();
assertExactMatch(shouldRead, c);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b);
assertTrue(mExprModel.markBitsRead());
shouldRead = getShouldRead();
assertExactMatch(shouldRead, a, b, t1.getIfTrue(), t2.getIfTrue(), t1, t2);
assertFlags(b, abTernary.getRequirementFlagIndex(true));
assertFlags(a, baTernary.getRequirementFlagIndex(true));
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testInterExprDependencyNotReadyYet() {
MockLayoutBinder 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 testInvalidateConditional() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final IdentifierExpr foo = lb.addVariable("foo", Foo.class.getCanonicalName(), null);
final IdentifierExpr c = lb.addVariable("c", "int", null);
final TernaryExpr ternary = parse(lb, "foo.a > 0 && (foo.b > 0 || c > 0)",
TernaryExpr.class);
final ComparisonExpr fooB0 = parse(lb, "foo.b > 0", ComparisonExpr.class);
final FieldAccessExpr fooB = (FieldAccessExpr) fooB0.getLeft();
mExprModel.seal();
final ComparisonExpr fooA0 = (ComparisonExpr) ternary.getPred();
final FieldAccessExpr fooA = (FieldAccessExpr) fooA0.getLeft();
// foo.b > 0 || c > 0
final TernaryExpr ternaryIfTrue = (TernaryExpr) ternary.getIfTrue();
final ComparisonExpr c0 = (ComparisonExpr) ternaryIfTrue.getIfFalse();
List<Expr> toRead = getShouldRead();
assertExactMatch(toRead, foo, fooA, fooA0, fooB, fooB0);
List<Expr> justRead = new ArrayList<Expr>();
List<Expr> readNow = getReadFirst(toRead, justRead);
assertExactMatch(readNow, foo);
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, fooA, fooB);
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, fooA0, fooB0);
justRead.addAll(readNow);
assertTrue(mExprModel.markBitsRead());
justRead.clear();
toRead = getShouldRead();
// there is a second path to calculate fooB, fooB0 where fooB is not invalid but fooA or
// c is invalid.
assertExactMatch(toRead, fooB, fooB0);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, fooB);
justRead.add(fooB);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, fooB0);
assertTrue(mExprModel.markBitsRead());
justRead.clear();
toRead = getShouldRead();
assertExactMatch(toRead, c, c0, ternary.getIfTrue(), ternary);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, c);
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, c0);
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, ternary.getIfTrue());
justRead.addAll(readNow);
readNow = filterOut(getReadFirst(toRead, justRead), justRead);
assertExactMatch(readNow, ternary);
justRead.addAll(readNow);
assertFalse(mExprModel.markBitsRead());
}
@Test
public void testNoFlagsForNonBindingStatic() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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.getTarget();
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() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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 testVoid() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final LambdaExpr lambda = parse(lb, "(v) -> cond1 ? obj4.clicked(v) : void", LambdaExpr.class);
assertNotSame(mExprModel, lambda.getCallbackExprModel());
}
@Test
public void testVoid2() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final LambdaExpr lambda = parse(lb, "(v) -> cond1 ? obj4.clicked(v) : Void", LambdaExpr.class);
assertNotSame(mExprModel, lambda.getCallbackExprModel());
}
@Test
public void testShruggy() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final LambdaExpr lambda = parse(lb, "(v) -> cond1 ? obj4.clicked(v) : ¯\\_(ツ)_/¯", LambdaExpr.class);
assertNotSame(mExprModel, lambda.getCallbackExprModel());
}
@Test
public void testParseNoArgLambda() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final LambdaExpr lambda = parse(lb, "() -> user.name", LambdaExpr.class);
assertNotSame(mExprModel, lambda.getCallbackExprModel());
}
@Test
public void testParseLambdaWithSingleArg() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final LambdaExpr lambda = parse(lb, "a -> user.name", LambdaExpr.class);
assertNotSame(mExprModel, lambda.getCallbackExprModel());
assertTrue("should have user", hasIdentifier(mExprModel, "user"));
assertTrue("should have user", hasIdentifier(lambda.getCallbackExprModel(), "user"));
assertSame("should have the same user", getIdentifier(mExprModel, "user"),
getIdentifier(lambda.getCallbackExprModel(), "user"));
assertTrue("should have a", hasCallbackIdentifier(lambda.getCallbackExprModel(), 0, "a"));
assertFalse("should not have a", hasCallbackIdentifier(mExprModel, 0, "a"));
}
@Test
public void testParseLambdaWithArguments() {
MockLayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
final LambdaExpr lambda = parse(lb, "(a, b, c, d) -> user.name", LambdaExpr.class);
final CallbackExprModel callbackModel = lambda.getCallbackExprModel();
assertNotSame(mExprModel, callbackModel);
assertTrue("should have user", hasIdentifier(mExprModel, "user"));
int index = 0;
for (String s : new String[]{"a", "b", "c", "d"}) {
assertTrue("should have " + s, hasCallbackIdentifier(callbackModel, index, s));
assertFalse("should not have " + s, hasIdentifier(mExprModel, s));
assertFalse("should not have " + s, hasCallbackIdentifier(mExprModel, index, s));
index ++;
}
}
private boolean hasIdentifier(ExprModel model, String name) {
return getIdentifier(model, name) != null;
}
private Expr getIdentifier(ExprModel model, String name) {
return model.getExprMap().get(new IdentifierExpr(name).getUniqueKey());
}
private boolean hasCallbackIdentifier(ExprModel model, int index, String name) {
return getCallbackIdentifier(model, index, name) != null;
}
private Expr getCallbackIdentifier(ExprModel model, int index, String name) {
return model.getExprMap().get(new CallbackArgExpr(index, name).getUniqueKey());
}
@Test
public void testFinalOfStaticField() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
MockLayoutBinder 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() {
// MockLayoutBinder 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 listLog = Arrays.toString(iterable.toArray());
String itemsLog = Arrays.toString(exprs);
String log = "list: " + listLog + "\nitems: " + itemsLog;
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() + "\n" + log,
Arrays.asList(exprs).contains(expr));
}
}
private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) {
final Expr parsed = binder.parse(input, null, 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 ExprModel.filterShouldRead(mExprModel.getPendingExpressions());
}
public static class Foo {
public final int a = 1;
public final int b = 1;
}
public static class User implements Observable {
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 String comment;
@Bindable
public boolean getUseComment() {
return true;
}
@Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
}
@Override
public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
}
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;
}
}