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