blob: 058427e045ca902d3d362e276b45ded141bfd1bd [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.tool.BindingTarget;
import android.databinding.tool.InverseBinding;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.reflection.ModelMethod;
import android.databinding.tool.store.Location;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.writer.FlagSet;
import org.antlr.v4.runtime.ParserRuleContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExprModel {
Map<String, Expr> mExprMap = new HashMap<String, Expr>();
List<Expr> mBindingExpressions = new ArrayList<Expr>();
private int mInvalidateableFieldLimit = 0;
private int mRequirementIdCount = 0;
// each arg list receives a unique id even if it is the same arguments and method.
private int mArgListIdCounter = 0;
private static final String TRUE_KEY_SUFFIX = "== true";
private static final String FALSE_KEY_SUFFIX = "== false";
/**
* Any expression can be invalidated by invalidating this flag.
*/
private BitSet mInvalidateAnyFlags;
private int mInvalidateAnyFlagIndex;
/**
* Used by code generation. Keeps the list of expressions that are waiting to be evaluated.
*/
private List<Expr> mPendingExpressions;
/**
* Used for converting flags into identifiers while debugging.
*/
private String[] mFlagMapping;
private BitSet mInvalidateableFlags;
private BitSet mConditionalFlags;
private int mFlagBucketCount;// how many buckets we use to identify flags
private List<Expr> mObservables;
private boolean mSealed = false;
private Map<String, String> mImports = new HashMap<String, String>();
private ParserRuleContext mCurrentParserContext;
private Location mCurrentLocationInFile;
/**
* Adds the expression to the list of expressions and returns it.
* If it already exists, returns existing one.
*
* @param expr The new parsed expression
* @return The expression itself or another one if the same thing was parsed before
*/
public <T extends Expr> T register(T expr) {
Preconditions.check(!mSealed, "Cannot add expressions to a model after it is sealed");
Location location = null;
if (mCurrentParserContext != null) {
location = new Location(mCurrentParserContext);
location.setParentLocation(mCurrentLocationInFile);
}
T existing = (T) mExprMap.get(expr.getUniqueKey());
if (existing != null) {
Preconditions.check(expr.getParents().isEmpty(),
"If an expression already exists, it should've never been added to a parent,"
+ "if thats the case, somewhere we are creating an expression w/o"
+ "calling expression model");
// tell the expr that it is being swapped so that if it was added to some other expr
// as a parent, those can swap their references
expr.onSwappedWith(existing);
if (location != null) {
existing.addLocation(location);
}
return existing;
}
mExprMap.put(expr.getUniqueKey(), expr);
expr.setModel(this);
if (location != null) {
expr.addLocation(location);
}
return expr;
}
public void setCurrentParserContext(ParserRuleContext currentParserContext) {
mCurrentParserContext = currentParserContext;
}
public Map<String, Expr> getExprMap() {
return mExprMap;
}
public int size() {
return mExprMap.size();
}
public ComparisonExpr comparison(String op, Expr left, Expr right) {
return register(new ComparisonExpr(op, left, right));
}
public InstanceOfExpr instanceOfOp(Expr expr, String type) {
return register(new InstanceOfExpr(expr, type));
}
public FieldAccessExpr field(Expr parent, String name) {
return register(new FieldAccessExpr(parent, name));
}
public FieldAccessExpr observableField(Expr parent, String name) {
return register(new FieldAccessExpr(parent, name, true));
}
public SymbolExpr symbol(String text, Class type) {
return register(new SymbolExpr(text, type));
}
public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) {
return register(new TernaryExpr(pred, ifTrue, ifFalse));
}
public IdentifierExpr identifier(String name) {
return register(new IdentifierExpr(name));
}
public StaticIdentifierExpr staticIdentifier(String name) {
return register(new StaticIdentifierExpr(name));
}
public BuiltInVariableExpr builtInVariable(String name, String type, String accessCode) {
return register(new BuiltInVariableExpr(name, type, accessCode));
}
public ViewFieldExpr viewFieldExpr(BindingTarget bindingTarget) {
return register(new ViewFieldExpr(bindingTarget));
}
/**
* Creates a static identifier for the given class or returns the existing one.
*/
public StaticIdentifierExpr staticIdentifierFor(final ModelClass modelClass) {
final String type = modelClass.getCanonicalName();
// check for existing
for (Expr expr : mExprMap.values()) {
if (expr instanceof StaticIdentifierExpr) {
StaticIdentifierExpr id = (StaticIdentifierExpr) expr;
if (id.getUserDefinedType().equals(type)) {
return id;
}
}
}
// does not exist. Find a name for it.
int cnt = 0;
int dotIndex = type.lastIndexOf(".");
String baseName;
Preconditions.check(dotIndex < type.length() - 1, "Invalid type %s", type);
if (dotIndex == -1) {
baseName = type;
} else {
baseName = type.substring(dotIndex + 1);
}
while (true) {
String candidate = cnt == 0 ? baseName : baseName + cnt;
if (!mImports.containsKey(candidate)) {
return addImport(candidate, type, null);
}
cnt ++;
Preconditions.check(cnt < 100, "Failed to create an import for " + type);
}
}
public MethodCallExpr methodCall(Expr target, String name, List<Expr> args) {
return register(new MethodCallExpr(target, name, args));
}
public MathExpr math(Expr left, String op, Expr right) {
return register(new MathExpr(left, op, right));
}
public TernaryExpr logical(Expr left, String op, Expr right) {
if ("&&".equals(op)) {
// left && right
// left ? right : false
return register(new TernaryExpr(left, right, symbol("false", boolean.class)));
} else {
// left || right
// left ? true : right
return register(new TernaryExpr(left, symbol("true", boolean.class), right));
}
}
public BitShiftExpr bitshift(Expr left, String op, Expr right) {
return register(new BitShiftExpr(left, op, right));
}
public UnaryExpr unary(String op, Expr expr) {
return register(new UnaryExpr(op, expr));
}
public Expr group(Expr grouped) {
return register(new GroupExpr(grouped));
}
public Expr resourceExpr(String packageName, String resourceType, String resourceName,
List<Expr> args) {
return register(new ResourceExpr(packageName, resourceType, resourceName, args));
}
public Expr bracketExpr(Expr variableExpr, Expr argExpr) {
return register(new BracketExpr(variableExpr, argExpr));
}
public Expr castExpr(String type, Expr expr) {
return register(new CastExpr(type, expr));
}
public TwoWayListenerExpr twoWayListenerExpr(InverseBinding inverseBinding) {
return register(new TwoWayListenerExpr(inverseBinding));
}
public List<Expr> getBindingExpressions() {
return mBindingExpressions;
}
public StaticIdentifierExpr addImport(String alias, String type, Location location) {
Preconditions.check(!mImports.containsKey(alias),
"%s has already been defined as %s", alias, type);
final StaticIdentifierExpr id = staticIdentifier(alias);
L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName());
id.setUserDefinedType(type);
if (location != null) {
id.addLocation(location);
}
mImports.put(alias, type);
return id;
}
public Map<String, String> getImports() {
return mImports;
}
/**
* The actual thingy that is set on the binding target.
*
* Input must be already registered
*/
public Expr bindingExpr(Expr bindingExpr) {
Preconditions.check(mExprMap.containsKey(bindingExpr.getUniqueKey()),
"Main expression should already be registered");
if (!mBindingExpressions.contains(bindingExpr)) {
mBindingExpressions.add(bindingExpr);
}
return bindingExpr;
}
public void removeExpr(Expr expr) {
Preconditions.check(!mSealed, "Can't modify the expression list after sealing the model.");
mBindingExpressions.remove(expr);
mExprMap.remove(expr.computeUniqueKey());
}
public List<Expr> getObservables() {
return mObservables;
}
/**
* Give id to each expression. Will be useful if we serialize.
*/
public void seal() {
L.d("sealing model");
List<Expr> notifiableExpressions = new ArrayList<Expr>();
//ensure class analyzer. We need to know observables at this point
final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
updateExpressions(modelAnalyzer);
int counter = 0;
final Iterable<Expr> observables = filterObservables(modelAnalyzer);
List<String> flagMapping = new ArrayList<String>();
mObservables = new ArrayList<Expr>();
for (Expr expr : observables) {
// observables gets initial ids
flagMapping.add(expr.getUniqueKey());
expr.setId(counter++);
mObservables.add(expr);
notifiableExpressions.add(expr);
L.d("observable %s", expr.getUniqueKey());
}
// non-observable identifiers gets next ids
final Iterable<Expr> nonObservableIds = filterNonObservableIds(modelAnalyzer);
for (Expr expr : nonObservableIds) {
flagMapping.add(expr.getUniqueKey());
expr.setId(counter++);
notifiableExpressions.add(expr);
L.d("non-observable %s", expr.getUniqueKey());
}
// descendants of observables gets following ids
for (Expr expr : observables) {
for (Expr parent : expr.getParents()) {
if (parent.hasId()) {
continue;// already has some id, means observable
}
// only fields earn an id
if (parent instanceof FieldAccessExpr) {
FieldAccessExpr fae = (FieldAccessExpr) parent;
L.d("checking field access expr %s. getter: %s", fae,fae.getGetter());
if (fae.isDynamic() && fae.getGetter().canBeInvalidated()) {
flagMapping.add(parent.getUniqueKey());
parent.setId(counter++);
notifiableExpressions.add(parent);
L.d("notifiable field %s : %s for %s : %s", parent.getUniqueKey(),
Integer.toHexString(System.identityHashCode(parent)),
expr.getUniqueKey(),
Integer.toHexString(System.identityHashCode(expr)));
}
}
}
}
// now all 2-way bound view fields
for (Expr expr : mExprMap.values()) {
if (expr instanceof FieldAccessExpr) {
FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) expr;
if (fieldAccessExpr.getChild() instanceof ViewFieldExpr) {
flagMapping.add(fieldAccessExpr.getUniqueKey());
fieldAccessExpr.setId(counter++);
}
}
}
// non-dynamic binding expressions receive some ids so that they can be invalidated
L.d("list of binding expressions");
for (int i = 0; i < mBindingExpressions.size(); i++) {
L.d("[%d] %s", i, mBindingExpressions.get(i));
}
// we don't assign ids to constant binding expressions because now invalidateAll has its own
// flag.
for (Expr expr : notifiableExpressions) {
expr.enableDirectInvalidation();
}
// make sure all dependencies are resolved to avoid future race conditions
for (Expr expr : mExprMap.values()) {
expr.getDependencies();
}
mInvalidateAnyFlagIndex = counter ++;
flagMapping.add("INVALIDATE ANY");
mInvalidateableFieldLimit = counter;
mInvalidateableFlags = new BitSet();
for (int i = 0; i < mInvalidateableFieldLimit; i++) {
mInvalidateableFlags.set(i, true);
}
// make sure all dependencies are resolved to avoid future race conditions
for (Expr expr : mExprMap.values()) {
if (expr.isConditional()) {
L.d("requirement id for %s is %d", expr, counter);
expr.setRequirementId(counter);
flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX);
flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX);
counter += 2;
}
}
mConditionalFlags = new BitSet();
for (int i = mInvalidateableFieldLimit; i < counter; i++) {
mConditionalFlags.set(i, true);
}
mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2;
// everybody gets an id
for (Map.Entry<String, Expr> entry : mExprMap.entrySet()) {
final Expr value = entry.getValue();
if (!value.hasId()) {
value.setId(counter++);
}
}
mFlagMapping = new String[flagMapping.size()];
flagMapping.toArray(mFlagMapping);
mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize);
mInvalidateAnyFlags = new BitSet();
mInvalidateAnyFlags.set(mInvalidateAnyFlagIndex, true);
for (Expr expr : mExprMap.values()) {
expr.getShouldReadFlagsWithConditionals();
}
for (Expr expr : mExprMap.values()) {
// ensure all types are calculated
expr.getResolvedType();
}
mSealed = true;
}
/**
* Run updateExpr on each binding expression until no new expressions are added.
* <p>
* Some expressions (e.g. field access) may replace themselves and add/remove new dependencies
* so we need to make sure each expression's update is called at least once.
*/
private void updateExpressions(ModelAnalyzer modelAnalyzer) {
int startSize = -1;
while (startSize != mExprMap.size()) {
startSize = mExprMap.size();
ArrayList<Expr> exprs = new ArrayList<Expr>(mBindingExpressions);
for (Expr expr : exprs) {
expr.updateExpr(modelAnalyzer);
}
}
}
public int getFlagBucketCount() {
return mFlagBucketCount;
}
public int getTotalFlagCount() {
return mRequirementIdCount * 2 + mInvalidateableFieldLimit;
}
public int getInvalidateableFieldLimit() {
return mInvalidateableFieldLimit;
}
public String[] getFlagMapping() {
return mFlagMapping;
}
public String getFlag(int id) {
return mFlagMapping[id];
}
private List<Expr> filterNonObservableIds(final ModelAnalyzer modelAnalyzer) {
List<Expr> result = new ArrayList<Expr>();
for (Expr input : mExprMap.values()) {
if (input instanceof IdentifierExpr
&& !input.hasId()
&& !input.isObservable()
&& input.isDynamic()) {
result.add(input);
}
}
return result;
}
private Iterable<Expr> filterObservables(final ModelAnalyzer modelAnalyzer) {
List<Expr> result = new ArrayList<Expr>();
for (Expr input : mExprMap.values()) {
if (input.isObservable()) {
result.add(input);
}
}
return result;
}
public List<Expr> getPendingExpressions() {
if (mPendingExpressions == null) {
mPendingExpressions = new ArrayList<Expr>();
for (Expr expr : mExprMap.values()) {
// if an expression is NOT dynanic but has conditional dependants, still return it
// so that conditional flags can be set
if (!expr.isRead() && (expr.isDynamic() || expr.hasConditionalDependant())) {
mPendingExpressions.add(expr);
}
}
}
return mPendingExpressions;
}
public boolean markBitsRead() {
// each has should read flags, we set them back on them
List<Expr> markedSomeFlagsRead = new ArrayList<Expr>();
for (Expr expr : filterShouldRead(getPendingExpressions())) {
expr.markFlagsAsRead(expr.getShouldReadFlags());
markedSomeFlagsRead.add(expr);
}
return pruneDone(markedSomeFlagsRead);
}
private boolean pruneDone(List<Expr> markedSomeFlagsAsRead) {
boolean marked = true;
List<Expr> markedAsReadList = new ArrayList<Expr>();
while (marked) {
marked = false;
for (Expr expr : mExprMap.values()) {
if (expr.isRead()) {
continue;
}
if (expr.markAsReadIfDone()) {
L.d("marked %s as read ", expr.getUniqueKey());
marked = true;
markedAsReadList.add(expr);
markedSomeFlagsAsRead.remove(expr);
}
}
}
boolean elevated = false;
for (Expr markedAsRead : markedAsReadList) {
for (Dependency dependency : markedAsRead.getDependants()) {
if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) {
elevated = true;
}
}
}
for (Expr partialRead : markedSomeFlagsAsRead) {
// even if all paths are not satisfied, we can elevate certain conditional dependencies
// if all of their paths are satisfied.
for (Dependency dependency : partialRead.getDependants()) {
Expr dependant = dependency.getDependant();
if (dependant.isConditional() && dependant.getAllCalculationPaths()
.areAllPathsSatisfied(partialRead.mReadSoFar)) {
if (dependant.considerElevatingConditionals(partialRead)) {
elevated = true;
}
}
}
}
if (elevated) {
// some conditionals are elevated. We should re-calculate flags
for (Expr expr : getPendingExpressions()) {
if (!expr.isRead()) {
expr.invalidateReadFlags();
}
}
mPendingExpressions = null;
}
return elevated;
}
private static boolean hasConditionalOrNestedCannotReadDependency(Expr expr) {
for (Dependency dependency : expr.getDependencies()) {
if (dependency.isConditional() || dependency.getOther().hasNestedCannotRead()) {
return true;
}
}
return false;
}
public static List<Expr> filterShouldRead(Iterable<Expr> exprs) {
List<Expr> result = new ArrayList<Expr>();
for (Expr expr : exprs) {
if (!expr.getShouldReadFlags().isEmpty() &&
!hasConditionalOrNestedCannotReadDependency(expr)) {
result.add(expr);
}
}
return result;
}
/**
* May return null if flag is equal to invalidate any flag.
*/
public Expr findFlagExpression(int flag) {
if (mInvalidateAnyFlags.get(flag)) {
return null;
}
final String key = mFlagMapping[flag];
if (mExprMap.containsKey(key)) {
return mExprMap.get(key);
}
int falseIndex = key.indexOf(FALSE_KEY_SUFFIX);
if (falseIndex > -1) {
final String trimmed = key.substring(0, falseIndex);
return mExprMap.get(trimmed);
}
int trueIndex = key.indexOf(TRUE_KEY_SUFFIX);
if (trueIndex > -1) {
final String trimmed = key.substring(0, trueIndex);
return mExprMap.get(trimmed);
}
// log everything we call
StringBuilder error = new StringBuilder();
error.append("cannot find flag:").append(flag).append("\n");
error.append("invalidate any flag:").append(mInvalidateAnyFlags).append("\n");
error.append("key:").append(key).append("\n");
error.append("flag mapping:").append(Arrays.toString(mFlagMapping));
L.e(error.toString());
return null;
}
public BitSet getInvalidateAnyBitSet() {
return mInvalidateAnyFlags;
}
public int getInvalidateAnyFlagIndex() {
return mInvalidateAnyFlagIndex;
}
public Expr argListExpr(Iterable<Expr> expressions) {
return register(new ArgListExpr(mArgListIdCounter ++, expressions));
}
public void setCurrentLocationInFile(Location location) {
mCurrentLocationInFile = location;
}
public Expr listenerExpr(Expr expression, String name, ModelClass listenerType,
ModelMethod listenerMethod) {
return register(new ListenerExpr(expression, name, listenerType, listenerMethod));
}
}