blob: 411ce4fed137b1197f226a958f436175a4918b82 [file] [log] [blame]
/*
* Copyright (C) 2016 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.solver;
import android.databinding.tool.expr.Expr;
import android.databinding.tool.util.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents all possible outcomes of an expressions with its branching.
*/
public class ExecutionPath {
@Nullable //null for root and branches
private final Expr mExpr;
@NotNull
private List<ExecutionPath> mChildren = new ArrayList<ExecutionPath>();
@Nullable
private ExecutionBranch mTrueBranch;
@Nullable
private ExecutionBranch mFalseBranch;
// values that we know due to branching
private Map<Expr, Boolean> mKnownValues = new HashMap<Expr, Boolean>();
// expressions that are available right now
private Set<Expr> mScopeExpressions = new HashSet<Expr>();
private final boolean mIsAlreadyEvaluated;
public static ExecutionPath createRoot() {
return new ExecutionPath(null, false);
}
private ExecutionPath(@Nullable Expr expr, boolean isAlreadyEvaluated) {
mExpr = expr;
mIsAlreadyEvaluated = isAlreadyEvaluated;
}
@Nullable
public ExecutionPath addBranch(Expr pred, boolean expectedValue) {
// TODO special predicates like Symbol(true, false)
Preconditions.checkNull(expectedValue ? mTrueBranch : mFalseBranch,
"Cannot add two " + expectedValue + "branches");
final Boolean knownValue = mKnownValues.get(pred);
if (knownValue != null) {
// we know the result. cut the branch
if (expectedValue == knownValue) {
// just add as a path
return addPath(null);
} else {
// drop path. this cannot happen
return null;
}
} else {
ExecutionPath path = createPath(null);
ExecutionBranch edge = new ExecutionBranch(path, pred, expectedValue);
path.mKnownValues.put(pred, expectedValue);
if (expectedValue) {
if (mFalseBranch != null) {
Preconditions.check(mFalseBranch.getConditional() == pred, "Cannot add"
+ " branches w/ different conditionals.");
}
mTrueBranch = edge;
} else {
if (mTrueBranch != null) {
Preconditions.check(mTrueBranch.getConditional() == pred, "Cannot add"
+ " branches w/ different conditionals.");
}
mFalseBranch = edge;
}
return path;
}
}
private ExecutionPath createPath(@Nullable Expr expr) {
ExecutionPath path = new ExecutionPath(expr, expr == null ||
mScopeExpressions.contains(expr));
// now pass down all values etc
path.mKnownValues.putAll(mKnownValues);
path.mScopeExpressions.addAll(mScopeExpressions);
return path;
}
@NotNull
public ExecutionPath addPath(@Nullable Expr expr) {
Preconditions.checkNull(mFalseBranch, "Cannot add path after branches are set");
Preconditions.checkNull(mTrueBranch, "Cannot add path after branches are set");
final ExecutionPath path = createPath(expr);
if (expr != null) {
mScopeExpressions.add(expr);
path.mScopeExpressions.add(expr);
}
mChildren.add(path);
return path;
}
public void debug(StringBuilder builder, int offset) {
offset(builder, offset);
if (mExpr != null || !mIsAlreadyEvaluated) {
builder.append("expr:").append(mExpr == null ? "root" : mExpr.getUniqueKey());
builder.append(" isRead:").append(mIsAlreadyEvaluated);
} else {
builder.append("branch");
}
if (!mKnownValues.isEmpty()) {
builder.append(" I know:");
for (Map.Entry<Expr, Boolean> entry : mKnownValues.entrySet()) {
builder.append(" ");
builder.append(entry.getKey().getUniqueKey());
builder.append(" is ").append(entry.getValue());
}
}
for (ExecutionPath path : mChildren) {
builder.append("\n");
path.debug(builder, offset);
}
if (mTrueBranch != null) {
debug(builder, mTrueBranch, offset);
}
if (mFalseBranch != null) {
debug(builder, mFalseBranch, offset);
}
}
@Nullable
public Expr getExpr() {
return mExpr;
}
@NotNull
public List<ExecutionPath> getChildren() {
return mChildren;
}
@Nullable
public ExecutionBranch getTrueBranch() {
return mTrueBranch;
}
@Nullable
public ExecutionBranch getFalseBranch() {
return mFalseBranch;
}
public boolean isAlreadyEvaluated() {
return mIsAlreadyEvaluated;
}
private void debug(StringBuilder builder, ExecutionBranch branch, int offset) {
builder.append("\n");
offset(builder, offset);
builder.append("if ")
.append(branch.getConditional().getUniqueKey())
.append(" is ").append(branch.getExpectedCondition()).append("\n");
branch.getPath().debug(builder, offset + 1);
}
private void offset(StringBuilder builder, int offset) {
for (int i = 0; i < offset; i++) {
builder.append(" ");
}
}
public Map<Expr, Boolean> getKnownValues() {
return mKnownValues;
}
}