Expr.java revision 74f72d77b1db2b78ee6422da2ec94de12edcb6dc
1/*
2 * Copyright (C) 2015 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 */
16
17package android.databinding.tool.expr;
18
19import com.google.common.base.Function;
20import com.google.common.base.Joiner;
21import com.google.common.base.Preconditions;
22import com.google.common.base.Predicate;
23import com.google.common.collect.Iterables;
24import com.google.common.collect.Lists;
25
26import android.databinding.tool.reflection.ModelAnalyzer;
27import android.databinding.tool.reflection.ModelClass;
28
29import java.util.ArrayList;
30import java.util.BitSet;
31import java.util.Collections;
32import java.util.List;
33
34abstract public class Expr {
35
36    public static final int NO_ID = -1;
37    protected List<Expr> mChildren = new ArrayList<Expr>();
38
39    // any expression that refers to this. Useful if this expr is duplicate and being replaced
40    private List<Expr> mParents = new ArrayList<Expr>();
41
42    private Boolean mIsDynamic;
43
44    private ModelClass mResolvedType;
45
46    private String mUniqueKey;
47
48    private List<Dependency> mDependencies;
49
50    private List<Dependency> mDependants = Lists.newArrayList();
51
52    private int mId = NO_ID;
53
54    private int mRequirementId = NO_ID;
55
56    // means this expression can directly be invalidated by the user
57    private boolean mCanBeInvalidated = false;
58
59    /**
60     * This set denotes the times when this expression is invalid.
61     * If it is an Identifier expression, it is its index
62     * If it is a composite expression, it is the union of invalid flags of its descendants
63     */
64    private BitSet mInvalidFlags;
65
66    /**
67     * Set when this expression is registered to a model
68     */
69    private ExprModel mModel;
70
71    /**
72     * This set denotes the times when this expression must be read.
73     *
74     * It is the union of invalidation flags of all of its non-conditional dependants.
75     */
76    BitSet mShouldReadFlags;
77
78    BitSet mReadSoFar = new BitSet();// i've read this variable for these flags
79
80    /**
81     * calculated on initialization, assuming all conditionals are true
82     */
83    BitSet mShouldReadWithConditionals;
84
85    private boolean mIsBindingExpression;
86
87    /**
88     * Used by generators when this expression is resolved.
89     */
90    private boolean mRead;
91    private boolean mIsUsed = false;
92
93    Expr(Iterable<Expr> children) {
94        for (Expr expr : children) {
95            mChildren.add(expr);
96        }
97        addParents();
98    }
99
100    Expr(Expr... children) {
101        Collections.addAll(mChildren, children);
102        addParents();
103    }
104
105    public int getId() {
106        Preconditions.checkState(mId != NO_ID, "if getId is called on an expression, it should have"
107                + " and id");
108        return mId;
109    }
110
111    public void setId(int id) {
112        Preconditions.checkState(mId == NO_ID, "ID is already set on " + this);
113        mId = id;
114    }
115
116    public ExprModel getModel() {
117        return mModel;
118    }
119
120    public BitSet getInvalidFlags() {
121        if (mInvalidFlags == null) {
122            mInvalidFlags = resolveInvalidFlags();
123        }
124        return mInvalidFlags;
125    }
126
127    private BitSet resolveInvalidFlags() {
128        BitSet bitSet = new BitSet();
129        if (mCanBeInvalidated) {
130            bitSet.set(getId(), true);
131        }
132        for (Dependency dependency : getDependencies()) {
133            // TODO optional optimization: do not invalidate for conditional flags
134            bitSet.or(dependency.getOther().getInvalidFlags());
135        }
136        return bitSet;
137    }
138
139    public void setBindingExpression(boolean isBindingExpression) {
140        mIsBindingExpression = isBindingExpression;
141    }
142
143    public boolean isBindingExpression() {
144        return mIsBindingExpression;
145    }
146
147    public boolean isObservable() {
148        return getResolvedType().isObservable();
149    }
150
151    public BitSet getShouldReadFlags() {
152        if (mShouldReadFlags == null) {
153            getShouldReadFlagsWithConditionals();
154            mShouldReadFlags = resolveShouldReadFlags();
155        }
156        return mShouldReadFlags;
157    }
158
159    public BitSet getShouldReadFlagsWithConditionals() {
160        if (mShouldReadWithConditionals == null) {
161            mShouldReadWithConditionals = resolveShouldReadWithConditionals();
162        }
163        return mShouldReadWithConditionals;
164    }
165
166    public void setModel(ExprModel model) {
167        mModel = model;
168    }
169
170    private BitSet resolveShouldReadWithConditionals() {
171        // ensure we have invalid flags
172        BitSet bitSet = new BitSet();
173        // if i'm invalid, that DOES NOT mean i should be read :/.
174        if (mIsBindingExpression) {
175            bitSet.or(getInvalidFlags());
176        }
177
178        for (Dependency dependency : getDependants()) {
179            // first traverse non-conditionals because we'll avoid adding conditionals if we are get because of these anyways
180            if (dependency.getCondition() == null) {
181                bitSet.or(dependency.getDependant().getShouldReadFlagsWithConditionals());
182            } else {
183                bitSet.set(dependency.getDependant()
184                        .getRequirementFlagIndex(dependency.getExpectedOutput()));
185            }
186        }
187        return bitSet;
188    }
189
190    private BitSet resolveShouldReadFlags() {
191        // ensure we have invalid flags
192        BitSet bitSet = new BitSet();
193        if (isRead()) {
194            return bitSet;
195        }
196        if (mIsBindingExpression) {
197            bitSet.or(getInvalidFlags());
198        }
199        for (Dependency dependency : getDependants()) {
200            final boolean isElevated = unreadElevatedCheck.apply(dependency);
201            if (dependency.isConditional()) {
202                continue; // TODO
203            }
204            if (isElevated) {
205                // if i already have all flags that will require my dependant's predicate to
206                // be read, that means i'm already read thus can avoid adding its conditional
207                // dependency
208                if (!dependency.getDependant().getAllCalculationPaths().areAllPathsSatisfied(
209                        mReadSoFar)) {
210                    bitSet.set(dependency.getDependant()
211                            .getRequirementFlagIndex(dependency.getExpectedOutput()));
212                }
213            } else {
214                bitSet.or(dependency.getDependant().getShouldReadFlags());
215            }
216        }
217        bitSet.andNot(mReadSoFar);
218        // should read w/ conditionals does eleminate for unnecessary re-reads
219        bitSet.and(mShouldReadWithConditionals);
220        return bitSet;
221    }
222
223    Predicate<Dependency> unreadElevatedCheck = new Predicate<Dependency>() {
224        @Override
225        public boolean apply(Dependency input) {
226            return input.isElevated() && !input.getDependant().isRead();
227        }
228    };
229
230    private void addParents() {
231        for (Expr expr : mChildren) {
232            expr.mParents.add(this);
233        }
234    }
235
236    public void onSwappedWith(Expr existing) {
237        for (Expr child : mChildren) {
238            child.onParentSwapped(this, existing);
239        }
240    }
241
242    private void onParentSwapped(Expr oldParent, Expr newParent) {
243        Preconditions.checkState(mParents.remove(oldParent));
244        mParents.add(newParent);
245    }
246
247    public List<Expr> getChildren() {
248        return mChildren;
249    }
250
251    public List<Expr> getParents() {
252        return mParents;
253    }
254
255    /**
256     * Whether the result of this expression can change or not.
257     *
258     * For example, 3 + 5 can not change vs 3 + x may change.
259     *
260     * Default implementations checks children and returns true if any of them returns true
261     *
262     * @return True if the result of this expression may change due to variables
263     */
264    public boolean isDynamic() {
265        if (mIsDynamic == null) {
266            mIsDynamic = isAnyChildDynamic();
267        }
268        return mIsDynamic;
269    }
270
271    private boolean isAnyChildDynamic() {
272        return Iterables.any(mChildren, new Predicate<Expr>() {
273            @Override
274            public boolean apply(Expr input) {
275                return input.isDynamic();
276            }
277        });
278
279    }
280
281    public ModelClass getResolvedType() {
282        if (mResolvedType == null) {
283            // TODO not get instance
284            mResolvedType = resolveType(ModelAnalyzer.getInstance());
285        }
286        return mResolvedType;
287    }
288
289    abstract protected ModelClass resolveType(ModelAnalyzer modelAnalyzer);
290
291    abstract protected List<Dependency> constructDependencies();
292
293    /**
294     * Creates a dependency for each dynamic child. Should work for any expression besides
295     * conditionals.
296     */
297    protected List<Dependency> constructDynamicChildrenDependencies() {
298        List<Dependency> dependencies = new ArrayList<Dependency>();
299        for (Expr node : mChildren) {
300            if (!node.isDynamic()) {
301                continue;
302            }
303            dependencies.add(new Dependency(this, node));
304        }
305        return dependencies;
306    }
307
308    public final List<Dependency> getDependencies() {
309        if (mDependencies == null) {
310            mDependencies = constructDependencies();
311        }
312        return mDependencies;
313    }
314
315    void addDependant(Dependency dependency) {
316        mDependants.add(dependency);
317    }
318
319    public List<Dependency> getDependants() {
320        return mDependants;
321    }
322
323    protected static final String KEY_JOIN = "~";
324    protected static final Joiner sUniqueKeyJoiner = Joiner.on(KEY_JOIN);
325
326    /**
327     * Returns a unique string key that can identify this expression.
328     *
329     * It must take into account any dependencies
330     *
331     * @return A unique identifier for this expression
332     */
333    public final String getUniqueKey() {
334        if (mUniqueKey == null) {
335            mUniqueKey = computeUniqueKey();
336            Preconditions.checkNotNull(mUniqueKey,
337                    "if there are no children, you must override computeUniqueKey");
338            Preconditions.checkState(!mUniqueKey.trim().equals(""),
339                    "if there are no children, you must override computeUniqueKey");
340        }
341        return mUniqueKey;
342    }
343
344    protected String computeUniqueKey() {
345        return computeChildrenKey();
346    }
347
348    protected final String computeChildrenKey() {
349        return sUniqueKeyJoiner.join(Iterables.transform(mChildren, new Function<Expr, String>() {
350            @Override
351            public String apply(Expr input) {
352                return input.getUniqueKey();
353            }
354        }));
355    }
356
357    public void enableDirectInvalidation() {
358        mCanBeInvalidated = true;
359    }
360
361    public boolean canBeInvalidated() {
362        return mCanBeInvalidated;
363    }
364
365    public void trimShouldReadFlags(BitSet bitSet) {
366        mShouldReadFlags.andNot(bitSet);
367    }
368
369    public boolean isConditional() {
370        return false;
371    }
372
373    public int getRequirementId() {
374        return mRequirementId;
375    }
376
377    public void setRequirementId(int requirementId) {
378        mRequirementId = requirementId;
379    }
380
381    /**
382     * This is called w/ a dependency of mine.
383     * Base method should thr
384     */
385    public int getRequirementFlagIndex(boolean expectedOutput) {
386        Preconditions.checkState(mRequirementId != NO_ID, "If this is an expression w/ conditional"
387                + " dependencies, it must be assigned a requirement ID");
388        return expectedOutput ? mRequirementId + 1 : mRequirementId;
389    }
390
391    public boolean hasId() {
392        return mId != NO_ID;
393    }
394
395    public void markFlagsAsRead(BitSet flags) {
396        mReadSoFar.or(flags);
397    }
398
399    public boolean isRead() {
400        return mRead;
401    }
402
403    public boolean considerElevatingConditionals(Expr justRead) {
404        boolean elevated = false;
405        for (Dependency dependency : mDependencies) {
406            if (dependency.isConditional() && dependency.getCondition() == justRead) {
407                dependency.elevate();
408                elevated = true;
409            }
410        }
411        return elevated;
412    }
413
414    public void invalidateReadFlags() {
415        mShouldReadFlags = null;
416    }
417
418    public boolean hasNestedCannotRead() {
419        if (isRead()) {
420            return false;
421        }
422        if (getShouldReadFlags().isEmpty()) {
423            return true;
424        }
425        return Iterables.any(getDependencies(), hasNestedCannotRead);
426    }
427
428    Predicate<Dependency> hasNestedCannotRead = new Predicate<Dependency>() {
429        @Override
430        public boolean apply(Dependency input) {
431            return input.isConditional() || input.getOther().hasNestedCannotRead();
432        }
433    };
434
435    public boolean markAsReadIfDone() {
436        if (mRead) {
437            return false;
438        }
439        // TODO avoid clone, we can calculate this iteratively
440        BitSet clone = (BitSet) mShouldReadWithConditionals.clone();
441
442        clone.andNot(mReadSoFar);
443        mRead = clone.isEmpty();
444        if (!mRead && !mReadSoFar.isEmpty()) {
445            // check if remaining dependencies can be satisfied w/ existing values
446            // for predicate flags, this expr may already be calculated to get the predicate
447            // to detect them, traverse them later on, see which flags should be calculated to calculate
448            // them. If any of them is completely covered w/ our non-conditional flags, no reason
449            // to add them to the list since we'll already be calculated due to our non-conditional
450            // flags
451
452            for (int i = clone.nextSetBit(0); i != -1; i = clone.nextSetBit(i + 1)) {
453                final Expr expr = mModel.findFlagExpression(i);
454                if (!expr.isConditional()) {
455                    continue;
456                }
457                final BitSet readForConditional = expr.findConditionalFlags();
458                // to calculate that conditional, i should've read /readForConditional/ flags
459                // if my read-so-far bits has any common w/ that; that means i would've already
460                // read myself
461                clone.andNot(readForConditional);
462                final BitSet invalidFlags = (BitSet) getInvalidFlags().clone();
463                invalidFlags.andNot(readForConditional);
464                mRead = invalidFlags.isEmpty() || clone.isEmpty();
465            }
466
467        }
468        if (mRead) {
469            mShouldReadFlags = null; // if we've been marked as read, clear should read flags
470        }
471        return mRead;
472    }
473
474    BitSet mConditionalFlags;
475
476    private BitSet findConditionalFlags() {
477        Preconditions.checkState(isConditional(), "should not call this on a non-conditional expr");
478        if (mConditionalFlags == null) {
479            mConditionalFlags = new BitSet();
480            resolveConditionalFlags(mConditionalFlags);
481        }
482        return mConditionalFlags;
483    }
484
485    private void resolveConditionalFlags(BitSet flags) {
486        flags.or(getPredicateInvalidFlags());
487        // if i have only 1 dependency which is conditional, traverse it as well
488        if (getDependants().size() == 1) {
489            final Dependency dependency = getDependants().get(0);
490            if (dependency.getCondition() != null) {
491                flags.or(dependency.getDependant().findConditionalFlags());
492                flags.set(dependency.getDependant()
493                        .getRequirementFlagIndex(dependency.getExpectedOutput()));
494            }
495        }
496    }
497
498
499    @Override
500    public String toString() {
501        return getUniqueKey();
502    }
503
504    public BitSet getReadSoFar() {
505        return mReadSoFar;
506    }
507
508    private Node mCalculationPaths = null;
509
510    protected Node getAllCalculationPaths() {
511        if (mCalculationPaths == null) {
512            Node node = new Node();
513            // TODO distant parent w/ conditionals are still not traversed :/
514            if (isConditional()) {
515                node.mBitSet.or(getPredicateInvalidFlags());
516            } else {
517                node.mBitSet.or(getInvalidFlags());
518            }
519            for (Dependency dependency : getDependants()) {
520                final Expr dependant = dependency.getDependant();
521                if (dependency.getCondition() != null) {
522                    Node cond = new Node();
523                    cond.setConditionFlag(
524                            dependant.getRequirementFlagIndex(dependency.getExpectedOutput()));
525                    cond.mParents.add(dependant.getAllCalculationPaths());
526                } else {
527                    node.mParents.add(dependant.getAllCalculationPaths());
528                }
529            }
530            mCalculationPaths = node;
531        }
532        return mCalculationPaths;
533    }
534
535    public String getDefaultValue() {
536        return ModelAnalyzer.getInstance().getDefaultValue(getResolvedType().toJavaCode());
537    }
538
539    protected BitSet getPredicateInvalidFlags() {
540        throw new IllegalStateException(
541                "must override getPredicateInvalidFlags in " + getClass().getSimpleName());
542    }
543
544    /**
545     * Used by code generation
546     */
547    public boolean shouldReadNow(final Iterable<Expr> justRead) {
548        return !getShouldReadFlags().isEmpty() &&
549                !Iterables.any(getDependencies(), new Predicate<Dependency>() {
550                    @Override
551                    public boolean apply(Dependency input) {
552                        return !(input.getOther().isRead() || (justRead != null && Iterables
553                                .contains(justRead, input.getOther())));
554                    }
555                });
556    }
557
558    public boolean isEqualityCheck() {
559        return false;
560    }
561
562    public void setIsUsed(boolean isUsed) {
563        mIsUsed = isUsed;
564    }
565
566    public boolean isUsed() {
567        return mIsUsed;
568    }
569
570    public void updateExpr(ModelAnalyzer modelAnalyzer) {
571        for (Expr child : mChildren) {
572            child.updateExpr(modelAnalyzer);
573        }
574    }
575
576    protected String asPackage() {
577        return null;
578    }
579
580    static class Node {
581
582        BitSet mBitSet = new BitSet();
583        List<Node> mParents = new ArrayList<Node>();
584        int mConditionFlag = -1;
585
586        public boolean areAllPathsSatisfied(BitSet readSoFar) {
587            if (mConditionFlag != -1) {
588                return readSoFar.get(mConditionFlag) || mParents.get(0)
589                        .areAllPathsSatisfied(readSoFar);
590            } else {
591                final BitSet clone = (BitSet) readSoFar.clone();
592                readSoFar.and(mBitSet);
593                if (!readSoFar.isEmpty()) {
594                    return true;
595                }
596                if (mParents.isEmpty()) {
597                    return false;
598                }
599                for (Node parent : mParents) {
600                    if (!parent.areAllPathsSatisfied(readSoFar)) {
601                        return false;
602                    }
603                }
604                return true;
605            }
606        }
607
608        public void setConditionFlag(int requirementFlagIndex) {
609            mConditionFlag = requirementFlagIndex;
610        }
611    }
612}
613