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