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