1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.databinding.tool.solver;
17
18import android.databinding.tool.expr.Expr;
19import android.databinding.tool.util.Preconditions;
20
21import org.jetbrains.annotations.NotNull;
22import org.jetbrains.annotations.Nullable;
23
24import java.util.ArrayList;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30
31/**
32 * Represents all possible outcomes of an expressions with its branching.
33 */
34public class ExecutionPath {
35    @Nullable //null for root and branches
36    private final Expr mExpr;
37    @NotNull
38    private List<ExecutionPath> mChildren = new ArrayList<ExecutionPath>();
39
40    @Nullable
41    private ExecutionBranch mTrueBranch;
42
43    @Nullable
44    private ExecutionBranch mFalseBranch;
45
46    // values that we know due to branching
47    private Map<Expr, Boolean> mKnownValues = new HashMap<Expr, Boolean>();
48
49    // expressions that are available right now
50    private Set<Expr> mScopeExpressions = new HashSet<Expr>();
51
52    private final boolean mIsAlreadyEvaluated;
53
54    public static ExecutionPath createRoot() {
55        return new ExecutionPath(null, false);
56    }
57
58    private ExecutionPath(@Nullable Expr expr, boolean isAlreadyEvaluated) {
59        mExpr = expr;
60        mIsAlreadyEvaluated = isAlreadyEvaluated;
61    }
62
63    @Nullable
64    public ExecutionPath addBranch(Expr pred, boolean expectedValue) {
65        // TODO special predicates like Symbol(true, false)
66        Preconditions.checkNull(expectedValue ? mTrueBranch : mFalseBranch,
67                "Cannot add two " + expectedValue + "branches");
68        final Boolean knownValue = mKnownValues.get(pred);
69        if (knownValue != null) {
70            // we know the result. cut the branch
71            if (expectedValue == knownValue) {
72                // just add as a path
73                return addPath(null);
74            } else {
75                // drop path. this cannot happen
76                return null;
77            }
78        } else {
79            ExecutionPath path = createPath(null);
80            ExecutionBranch edge = new ExecutionBranch(path, pred, expectedValue);
81            path.mKnownValues.put(pred, expectedValue);
82            if (expectedValue) {
83                if (mFalseBranch != null) {
84                    Preconditions.check(mFalseBranch.getConditional() == pred, "Cannot add"
85                            + " branches w/ different conditionals.");
86                }
87                mTrueBranch = edge;
88            } else {
89                if (mTrueBranch != null) {
90                    Preconditions.check(mTrueBranch.getConditional() == pred, "Cannot add"
91                            + " branches w/ different conditionals.");
92                }
93                mFalseBranch = edge;
94            }
95            return path;
96        }
97    }
98
99    private ExecutionPath createPath(@Nullable Expr expr) {
100        ExecutionPath path = new ExecutionPath(expr, expr == null ||
101                mScopeExpressions.contains(expr));
102        // now pass down all values etc
103        path.mKnownValues.putAll(mKnownValues);
104        path.mScopeExpressions.addAll(mScopeExpressions);
105        return path;
106    }
107
108    @NotNull
109    public ExecutionPath addPath(@Nullable Expr expr) {
110        Preconditions.checkNull(mFalseBranch, "Cannot add path after branches are set");
111        Preconditions.checkNull(mTrueBranch, "Cannot add path after branches are set");
112        final ExecutionPath path = createPath(expr);
113        if (expr != null) {
114            mScopeExpressions.add(expr);
115            path.mScopeExpressions.add(expr);
116        }
117        mChildren.add(path);
118        return path;
119    }
120
121    public void debug(StringBuilder builder, int offset) {
122        offset(builder, offset);
123        if (mExpr != null || !mIsAlreadyEvaluated) {
124            builder.append("expr:").append(mExpr == null ? "root" : mExpr.getUniqueKey());
125            builder.append(" isRead:").append(mIsAlreadyEvaluated);
126        } else {
127            builder.append("branch");
128        }
129        if (!mKnownValues.isEmpty()) {
130            builder.append(" I know:");
131            for (Map.Entry<Expr, Boolean> entry : mKnownValues.entrySet()) {
132                builder.append(" ");
133                builder.append(entry.getKey().getUniqueKey());
134                builder.append(" is ").append(entry.getValue());
135            }
136        }
137        for (ExecutionPath path : mChildren) {
138            builder.append("\n");
139            path.debug(builder, offset);
140        }
141        if (mTrueBranch != null) {
142            debug(builder, mTrueBranch, offset);
143        }
144        if (mFalseBranch != null) {
145            debug(builder, mFalseBranch, offset);
146        }
147    }
148
149    @Nullable
150    public Expr getExpr() {
151        return mExpr;
152    }
153
154    @NotNull
155    public List<ExecutionPath> getChildren() {
156        return mChildren;
157    }
158
159    @Nullable
160    public ExecutionBranch getTrueBranch() {
161        return mTrueBranch;
162    }
163
164    @Nullable
165    public ExecutionBranch getFalseBranch() {
166        return mFalseBranch;
167    }
168
169    public boolean isAlreadyEvaluated() {
170        return mIsAlreadyEvaluated;
171    }
172
173    private void debug(StringBuilder builder, ExecutionBranch branch, int offset) {
174        builder.append("\n");
175        offset(builder, offset);
176        builder.append("if ")
177                .append(branch.getConditional().getUniqueKey())
178                .append(" is ").append(branch.getExpectedCondition()).append("\n");
179        branch.getPath().debug(builder, offset + 1);
180    }
181
182    private void offset(StringBuilder builder, int offset) {
183        for (int i = 0; i < offset; i++) {
184            builder.append("  ");
185        }
186    }
187
188    public Map<Expr, Boolean> getKnownValues() {
189        return mKnownValues;
190    }
191}
192