/* * 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.antlr.v4.runtime.misc.Nullable; import android.databinding.tool.processing.ErrorMessages; import android.databinding.tool.processing.Scope; import android.databinding.tool.processing.scopes.LocationScopeProvider; import android.databinding.tool.processing.scopes.ScopeProvider; import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.store.Location; import android.databinding.tool.util.L; import android.databinding.tool.util.Preconditions; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.List; abstract public class Expr implements VersionProvider, LocationScopeProvider { public static final int NO_ID = -1; protected List mChildren = new ArrayList(); // any expression that refers to this. Useful if this expr is duplicate and being replaced private List mParents = new ArrayList(); private Boolean mIsDynamic; private ModelClass mResolvedType; private String mUniqueKey; private List mDependencies; private List mDependants = new ArrayList(); private int mId = NO_ID; private int mRequirementId = NO_ID; private int mVersion = 0; // means this expression can directly be invalidated by the user private boolean mCanBeInvalidated = false; @Nullable private List mLocations = new ArrayList<>(); /** * This set denotes the times when this expression is invalid. * If it is an Identifier expression, it is its index * If it is a composite expression, it is the union of invalid flags of its descendants */ private BitSet mInvalidFlags; /** * Set when this expression is registered to a model */ private ExprModel mModel; /** * This set denotes the times when this expression must be read. * * It is the union of invalidation flags of all of its non-conditional dependants. */ BitSet mShouldReadFlags; BitSet mReadSoFar = new BitSet();// i've read this variable for these flags /** * calculated on initialization, assuming all conditionals are true */ BitSet mShouldReadWithConditionals; private boolean mIsBindingExpression; /** * Used by generators when this expression is resolved. */ private boolean mRead; private boolean mIsUsed = false; Expr(Iterable children) { for (Expr expr : children) { mChildren.add(expr); } addParents(); } Expr(Expr... children) { Collections.addAll(mChildren, children); addParents(); } public int getId() { Preconditions.check(mId != NO_ID, "if getId is called on an expression, it should have" + " an id: %s", this); return mId; } public void setId(int id) { Preconditions.check(mId == NO_ID, "ID is already set on %s", this); mId = id; } public void addLocation(Location location) { mLocations.add(location); } public List getLocations() { return mLocations; } public ExprModel getModel() { return mModel; } public BitSet getInvalidFlags() { if (mInvalidFlags == null) { mInvalidFlags = resolveInvalidFlags(); } return mInvalidFlags; } private BitSet resolveInvalidFlags() { BitSet bitSet = (BitSet) mModel.getInvalidateAnyBitSet().clone(); if (mCanBeInvalidated) { bitSet.set(getId(), true); } for (Dependency dependency : getDependencies()) { // TODO optional optimization: do not invalidate for conditional flags bitSet.or(dependency.getOther().getInvalidFlags()); } return bitSet; } public void setBindingExpression(boolean isBindingExpression) { mIsBindingExpression = isBindingExpression; } public boolean isBindingExpression() { return mIsBindingExpression; } public boolean canBeEvaluatedToAVariable() { return true; // anything except arg expr can be evaluated to a variable } public boolean isObservable() { return getResolvedType().isObservable(); } public boolean resolveListeners(ModelClass valueType) { boolean resetResolvedType = false; for (Expr child : getChildren()) { if (child.resolveListeners(valueType)) { resetResolvedType = true; } } if (resetResolvedType) { mResolvedType = null; } return resetResolvedType; } protected void resetResolvedType() { mResolvedType = null; } public BitSet getShouldReadFlags() { if (mShouldReadFlags == null) { getShouldReadFlagsWithConditionals(); mShouldReadFlags = resolveShouldReadFlags(); } return mShouldReadFlags; } public BitSet getShouldReadFlagsWithConditionals() { if (mShouldReadWithConditionals == null) { mShouldReadWithConditionals = resolveShouldReadWithConditionals(); } return mShouldReadWithConditionals; } public void setModel(ExprModel model) { mModel = model; } private BitSet resolveShouldReadWithConditionals() { // ensure we have invalid flags BitSet bitSet = new BitSet(); // if i'm invalid, that DOES NOT mean i should be read :/. if (mIsBindingExpression) { bitSet.or(getInvalidFlags()); } for (Dependency dependency : getDependants()) { // first traverse non-conditionals because we'll avoid adding conditionals if we are get because of these anyways if (dependency.getCondition() == null) { bitSet.or(dependency.getDependant().getShouldReadFlagsWithConditionals()); } else { bitSet.set(dependency.getDependant() .getRequirementFlagIndex(dependency.getExpectedOutput())); } } return bitSet; } private BitSet resolveShouldReadFlags() { // ensure we have invalid flags BitSet bitSet = new BitSet(); if (isRead()) { return bitSet; } if (mIsBindingExpression) { bitSet.or(getInvalidFlags()); } for (Dependency dependency : getDependants()) { final boolean isUnreadElevated = isUnreadElevated(dependency); if (dependency.isConditional()) { continue; // will be resolved later when conditional is elevated } if (isUnreadElevated) { bitSet.set(dependency.getDependant() .getRequirementFlagIndex(dependency.getExpectedOutput())); } else { bitSet.or(dependency.getDependant().getShouldReadFlags()); } } bitSet.and(mShouldReadWithConditionals); bitSet.andNot(mReadSoFar); return bitSet; } private static boolean isUnreadElevated(Dependency input) { return input.isElevated() && !input.getDependant().isRead(); } private void addParents() { for (Expr expr : mChildren) { expr.mParents.add(this); } } public void onSwappedWith(Expr existing) { for (Expr child : mChildren) { child.onParentSwapped(this, existing); } } private void onParentSwapped(Expr oldParent, Expr newParent) { Preconditions.check(mParents.remove(oldParent), "trying to remove non-existent parent %s" + " from %s", oldParent, mParents); mParents.add(newParent); } public List getChildren() { return mChildren; } public List getParents() { return mParents; } /** * Whether the result of this expression can change or not. * * For example, 3 + 5 can not change vs 3 + x may change. * * Default implementations checks children and returns true if any of them returns true * * @return True if the result of this expression may change due to variables */ public boolean isDynamic() { if (mIsDynamic == null) { mIsDynamic = isAnyChildDynamic(); } return mIsDynamic; } private boolean isAnyChildDynamic() { for (Expr expr : mChildren) { if (expr.isDynamic()) { return true; } } return false; } public ModelClass getResolvedType() { if (mResolvedType == null) { // TODO not get instance try { Scope.enter(this); mResolvedType = resolveType(ModelAnalyzer.getInstance()); if (mResolvedType == null) { L.e(ErrorMessages.CANNOT_RESOLVE_TYPE, this); } } finally { Scope.exit(); } } return mResolvedType; } abstract protected ModelClass resolveType(ModelAnalyzer modelAnalyzer); abstract protected List constructDependencies(); /** * Creates a dependency for each dynamic child. Should work for any expression besides * conditionals. */ protected List constructDynamicChildrenDependencies() { List dependencies = new ArrayList(); for (Expr node : mChildren) { if (!node.isDynamic()) { continue; } dependencies.add(new Dependency(this, node)); } return dependencies; } public final List getDependencies() { if (mDependencies == null) { mDependencies = constructDependencies(); } return mDependencies; } void addDependant(Dependency dependency) { mDependants.add(dependency); } public List getDependants() { return mDependants; } protected static final String KEY_JOIN = "~"; /** * Returns a unique string key that can identify this expression. * * It must take into account any dependencies * * @return A unique identifier for this expression */ public final String getUniqueKey() { if (mUniqueKey == null) { mUniqueKey = computeUniqueKey(); Preconditions.checkNotNull(mUniqueKey, "if there are no children, you must override computeUniqueKey"); Preconditions.check(!mUniqueKey.trim().equals(""), "if there are no children, you must override computeUniqueKey"); } return mUniqueKey; } protected String computeUniqueKey() { return computeChildrenKey(); } protected final String computeChildrenKey() { return join(mChildren); } public void enableDirectInvalidation() { mCanBeInvalidated = true; } public boolean canBeInvalidated() { return mCanBeInvalidated; } public void trimShouldReadFlags(BitSet bitSet) { mShouldReadFlags.andNot(bitSet); } public boolean isConditional() { return false; } public int getRequirementId() { return mRequirementId; } public void setRequirementId(int requirementId) { mRequirementId = requirementId; } /** * This is called w/ a dependency of mine. * Base method should thr */ public int getRequirementFlagIndex(boolean expectedOutput) { Preconditions.check(mRequirementId != NO_ID, "If this is an expression w/ conditional" + " dependencies, it must be assigned a requirement ID"); return expectedOutput ? mRequirementId + 1 : mRequirementId; } public boolean hasId() { return mId != NO_ID; } public void markFlagsAsRead(BitSet flags) { mReadSoFar.or(flags); } public boolean isRead() { return mRead; } public boolean considerElevatingConditionals(Expr justRead) { boolean elevated = false; for (Dependency dependency : mDependencies) { if (dependency.isConditional() && dependency.getCondition() == justRead) { dependency.elevate(); elevated = true; } } return elevated; } public void invalidateReadFlags() { mShouldReadFlags = null; mVersion ++; } @Override public int getVersion() { return mVersion; } public boolean hasNestedCannotRead() { if (isRead()) { return false; } if (getShouldReadFlags().isEmpty()) { return true; } for (Dependency dependency : getDependencies()) { if (hasNestedCannotRead(dependency)) { return true; } } return false; } private static boolean hasNestedCannotRead(Dependency input) { return input.isConditional() || input.getOther().hasNestedCannotRead(); } public boolean markAsReadIfDone() { if (mRead) { return false; } // TODO avoid clone, we can calculate this iteratively BitSet clone = (BitSet) mShouldReadWithConditionals.clone(); clone.andNot(mReadSoFar); mRead = clone.isEmpty(); if (!mRead && !mReadSoFar.isEmpty()) { // check if remaining dependencies can be satisfied w/ existing values // for predicate flags, this expr may already be calculated to get the predicate // to detect them, traverse them later on, see which flags should be calculated to calculate // them. If any of them is completely covered w/ our non-conditional flags, no reason // to add them to the list since we'll already be calculated due to our non-conditional // flags for (int i = clone.nextSetBit(0); i != -1; i = clone.nextSetBit(i + 1)) { final Expr expr = mModel.findFlagExpression(i); if (expr == null || !expr.isConditional()) { continue; } final BitSet readForConditional = expr.findConditionalFlags(); // to calculate that conditional, i should've read /readForConditional/ flags // if my read-so-far bits has any common w/ that; that means i would've already // read myself clone.andNot(readForConditional); final BitSet invalidFlags = (BitSet) getInvalidFlags().clone(); invalidFlags.andNot(readForConditional); mRead = invalidFlags.isEmpty() || clone.isEmpty(); if (mRead) { break; } } } if (mRead) { mShouldReadFlags = null; // if we've been marked as read, clear should read flags } return mRead; } BitSet mConditionalFlags; private BitSet findConditionalFlags() { Preconditions.check(isConditional(), "should not call this on a non-conditional expr"); if (mConditionalFlags == null) { mConditionalFlags = new BitSet(); resolveConditionalFlags(mConditionalFlags); } return mConditionalFlags; } private void resolveConditionalFlags(BitSet flags) { flags.or(getPredicateInvalidFlags()); // if i have only 1 dependency which is conditional, traverse it as well if (getDependants().size() == 1) { final Dependency dependency = getDependants().get(0); if (dependency.getCondition() != null) { flags.or(dependency.getDependant().findConditionalFlags()); flags.set(dependency.getDependant() .getRequirementFlagIndex(dependency.getExpectedOutput())); } } } @Override public String toString() { return getUniqueKey(); } public BitSet getReadSoFar() { return mReadSoFar; } private Node mCalculationPaths = null; protected Node getAllCalculationPaths() { if (mCalculationPaths == null) { Node node = new Node(); // TODO distant parent w/ conditionals are still not traversed :/ if (isConditional()) { node.mBitSet.or(getPredicateInvalidFlags()); } else { node.mBitSet.or(getInvalidFlags()); } for (Dependency dependency : getDependants()) { final Expr dependant = dependency.getDependant(); if (dependency.getCondition() != null) { Node cond = new Node(); cond.setConditionFlag( dependant.getRequirementFlagIndex(dependency.getExpectedOutput())); cond.mParents.add(dependant.getAllCalculationPaths()); } else { node.mParents.add(dependant.getAllCalculationPaths()); } } mCalculationPaths = node; } return mCalculationPaths; } public String getDefaultValue() { return ModelAnalyzer.getInstance().getDefaultValue(getResolvedType().toJavaCode()); } protected BitSet getPredicateInvalidFlags() { throw new IllegalStateException( "must override getPredicateInvalidFlags in " + getClass().getSimpleName()); } /** * Used by code generation */ public boolean shouldReadNow(final List justRead) { if (getShouldReadFlags().isEmpty()) { return false; } for (Dependency input : getDependencies()) { boolean dependencyReady = input.getOther().isRead() || (justRead != null && justRead.contains(input.getOther())); if(!dependencyReady) { return false; } } return true; } public boolean isEqualityCheck() { return false; } public void setIsUsed(boolean isUsed) { mIsUsed = isUsed; for (Expr child : getChildren()) { child.setIsUsed(isUsed); } } public boolean isUsed() { return mIsUsed; } public void updateExpr(ModelAnalyzer modelAnalyzer) { for (Expr child : mChildren) { child.updateExpr(modelAnalyzer); } } protected static String join(String... items) { StringBuilder result = new StringBuilder(); for (int i = 0; i < items.length; i ++) { if (i > 0) { result.append(KEY_JOIN); } result.append(items[i]); } return result.toString(); } protected static String join(List items) { StringBuilder result = new StringBuilder(); for (int i = 0; i < items.size(); i ++) { if (i > 0) { result.append(KEY_JOIN); } result.append(items.get(i).getUniqueKey()); } return result.toString(); } protected String asPackage() { return null; } @Override public List provideScopeLocation() { return mLocations; } static class Node { BitSet mBitSet = new BitSet(); List mParents = new ArrayList(); int mConditionFlag = -1; public boolean areAllPathsSatisfied(BitSet readSoFar) { if (mConditionFlag != -1) { return readSoFar.get(mConditionFlag) || mParents.get(0) .areAllPathsSatisfied(readSoFar); } else { final BitSet clone = (BitSet) readSoFar.clone(); clone.and(mBitSet); if (!clone.isEmpty()) { return true; } if (mParents.isEmpty()) { return false; } for (Node parent : mParents) { if (!parent.areAllPathsSatisfied(clone)) { return false; } } return true; } } public void setConditionFlag(int requirementFlagIndex) { mConditionFlag = requirementFlagIndex; } } }