ExprModel.java revision 0c2ed0cbaee2f206e926bfc780b05e9f1e52b551
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.Optional;
20import com.google.common.base.Preconditions;
21import com.google.common.base.Predicate;
22import com.google.common.collect.Iterables;
23import com.google.common.collect.Lists;
24
25import android.databinding.tool.reflection.ModelAnalyzer;
26import android.databinding.tool.reflection.ModelClass;
27import android.databinding.tool.util.L;
28import android.databinding.tool.writer.FlagSet;
29
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.BitSet;
33import java.util.HashMap;
34import java.util.List;
35import java.util.Map;
36
37public class ExprModel {
38
39    Map<String, Expr> mExprMap = new HashMap<String, Expr>();
40
41    List<Expr> mBindingExpressions = new ArrayList<Expr>();
42
43    private int mInvalidateableFieldLimit = 0;
44
45    private int mRequirementIdCount = 0;
46
47    // each arg list receives a unique id even if it is the same arguments and method.
48    private int mArgListIdCounter = 0;
49
50    private static final String TRUE_KEY_SUFFIX = "== true";
51    private static final String FALSE_KEY_SUFFIX = "== false";
52
53    /**
54     * Any expression can be invalidated by invalidating this flag.
55     */
56    private BitSet mInvalidateAnyFlags;
57
58    /**
59     * Used by code generation. Keeps the list of expressions that are waiting to be evaluated.
60     */
61    private List<Expr> mPendingExpressions;
62
63    /**
64     * Used for converting flags into identifiers while debugging.
65     */
66    private String[] mFlagMapping;
67
68    private BitSet mInvalidateableFlags;
69    private BitSet mConditionalFlags;
70
71    private int mFlagBucketCount;// how many buckets we use to identify flags
72
73    private List<Expr> mObservables;
74
75    private boolean mSealed = false;
76
77    private Map<String, String> mImports = new HashMap<String, String>();
78
79    /**
80     * Adds the expression to the list of expressions and returns it.
81     * If it already exists, returns existing one.
82     *
83     * @param expr The new parsed expression
84     * @return The expression itself or another one if the same thing was parsed before
85     */
86    public <T extends Expr> T register(T expr) {
87        Preconditions.checkState(!mSealed, "Cannot add expressions to a model after it is sealed");
88        T existing = (T) mExprMap.get(expr.getUniqueKey());
89        if (existing != null) {
90            Preconditions.checkState(expr.getParents().isEmpty(),
91                    "If an expression already exists, it should've never been added to a parent,"
92                            + "if thats the case, somewhere we are creating an expression w/o"
93                            + "calling expression model");
94            // tell the expr that it is being swapped so that if it was added to some other expr
95            // as a parent, those can swap their references
96            expr.onSwappedWith(existing);
97            return existing;
98        }
99        mExprMap.put(expr.getUniqueKey(), expr);
100        expr.setModel(this);
101        return expr;
102    }
103
104    public void unregister(Expr expr) {
105        mExprMap.remove(expr.getUniqueKey());
106    }
107
108    public Map<String, Expr> getExprMap() {
109        return mExprMap;
110    }
111
112    public int size() {
113        return mExprMap.size();
114    }
115
116    public ComparisonExpr comparison(String op, Expr left, Expr right) {
117        return register(new ComparisonExpr(op, left, right));
118    }
119
120    public InstanceOfExpr instanceOfOp(Expr expr, String type) {
121        return register(new InstanceOfExpr(expr, type));
122    }
123
124    public FieldAccessExpr field(Expr parent, String name) {
125        return register(new FieldAccessExpr(parent, name));
126    }
127
128    public FieldAccessExpr observableField(Expr parent, String name) {
129        return register(new FieldAccessExpr(parent, name, true));
130    }
131
132    public SymbolExpr symbol(String text, Class type) {
133        return register(new SymbolExpr(text, type));
134    }
135
136    public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) {
137        return register(new TernaryExpr(pred, ifTrue, ifFalse));
138    }
139
140    public IdentifierExpr identifier(String name) {
141        return register(new IdentifierExpr(name));
142    }
143
144    public StaticIdentifierExpr staticIdentifier(String name) {
145        return register(new StaticIdentifierExpr(name));
146    }
147
148    /**
149     * Creates a static identifier for the given class or returns the existing one.
150     */
151    public StaticIdentifierExpr staticIdentifierFor(final ModelClass modelClass) {
152        final String type = modelClass.getCanonicalName();
153        Optional<Expr> existing = Iterables.tryFind(mExprMap.values(), new Predicate<Expr>() {
154            @Override
155            public boolean apply(Expr input) {
156                if (!(input instanceof StaticIdentifierExpr)) {
157                    return false;
158                }
159                StaticIdentifierExpr id = (StaticIdentifierExpr) input;
160                return id.getUserDefinedType().equals(type);
161            }
162        });
163        if (existing.isPresent()) {
164            return (StaticIdentifierExpr) existing.get();
165        }
166
167        // does not exist. Find a name for it.
168        int cnt = 0;
169        int dotIndex = type.lastIndexOf(".");
170        String baseName;
171        Preconditions.checkArgument(dotIndex < type.length() - 1, "Invalid type %s", type);
172        if (dotIndex == -1) {
173            baseName = type;
174        } else {
175            baseName = type.substring(dotIndex + 1);
176        }
177        while (true) {
178            String candidate = cnt == 0 ? baseName : baseName + cnt;
179            if (!mImports.containsKey(candidate)) {
180                return addImport(candidate, type);
181            }
182            cnt ++;
183            Preconditions.checkState(cnt < 100, "Failed to create an import for " + type);
184        }
185    }
186
187    public MethodCallExpr methodCall(Expr target, String name, List<Expr> args) {
188        return register(new MethodCallExpr(target, name, args));
189    }
190
191    public MathExpr math(Expr left, String op, Expr right) {
192        return register(new MathExpr(left, op, right));
193    }
194
195    public TernaryExpr logical(Expr left, String op, Expr right) {
196        if ("&&".equals(op)) {
197            // left && right
198            // left ? right : false
199            return register(new TernaryExpr(left, right, symbol("false", boolean.class)));
200        } else {
201            // left || right
202            // left ? true : right
203            return register(new TernaryExpr(left, symbol("true", boolean.class), right));
204        }
205    }
206
207    public BitShiftExpr bitshift(Expr left, String op, Expr right) {
208        return register(new BitShiftExpr(left, op, right));
209    }
210
211    public UnaryExpr unary(String op, Expr expr) {
212        return register(new UnaryExpr(op, expr));
213    }
214
215    public Expr group(Expr grouped) {
216        return register(new GroupExpr(grouped));
217    }
218
219    public Expr resourceExpr(String packageName, String resourceType, String resourceName,
220            List<Expr> args) {
221        return register(new ResourceExpr(packageName, resourceType, resourceName, args));
222    }
223
224    public Expr bracketExpr(Expr variableExpr, Expr argExpr) {
225        return register(new BracketExpr(variableExpr, argExpr));
226    }
227
228    public Expr castExpr(String type, Expr expr) {
229        return register(new CastExpr(type, expr));
230    }
231
232    public List<Expr> getBindingExpressions() {
233        return mBindingExpressions;
234    }
235
236    public StaticIdentifierExpr addImport(String alias, String type) {
237        Preconditions.checkState(!mImports.containsKey(alias),
238                "%s has already been defined as %s", alias, type);
239        final StaticIdentifierExpr id = staticIdentifier(alias);
240        L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName());
241        id.setUserDefinedType(type);
242        mImports.put(alias, type);
243        return id;
244    }
245
246    public Map<String, String> getImports() {
247        return mImports;
248    }
249
250    /**
251     * The actual thingy that is set on the binding target.
252     *
253     * Input must be already registered
254     */
255    public Expr bindingExpr(Expr bindingExpr) {
256        Preconditions.checkArgument(mExprMap.containsKey(bindingExpr.getUniqueKey()),
257                "Main expression should already be registered");
258        if (!mBindingExpressions.contains(bindingExpr)) {
259            mBindingExpressions.add(bindingExpr);
260        }
261        return bindingExpr;
262    }
263
264    public List<Expr> getObservables() {
265        return mObservables;
266    }
267
268    public void seal() {
269        seal(null);
270    }
271    /**
272     * Give id to each expression. Will be useful if we serialize.
273     */
274    public void seal(ResolveListenersCallback resolveListeners) {
275        L.d("sealing model");
276        List<Expr> notifiableExpressions = new ArrayList<Expr>();
277        //ensure class analyzer. We need to know observables at this point
278        final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
279        updateExpressions(modelAnalyzer);
280
281        if (resolveListeners != null) {
282            resolveListeners.resolveListeners();
283        }
284
285        int counter = 0;
286        final Iterable<Expr> observables = filterObservables(modelAnalyzer);
287        List<String> flagMapping = Lists.newArrayList();
288        mObservables = Lists.newArrayList();
289        for (Expr expr : observables) {
290            // observables gets initial ids
291            flagMapping.add(expr.getUniqueKey());
292            expr.setId(counter++);
293            mObservables.add(expr);
294            notifiableExpressions.add(expr);
295            L.d("observable %s", expr.getUniqueKey());
296        }
297
298        // non-observable identifiers gets next ids
299        final Iterable<Expr> nonObservableIds = filterNonObservableIds(modelAnalyzer);
300        for (Expr expr : nonObservableIds) {
301            flagMapping.add(expr.getUniqueKey());
302            expr.setId(counter++);
303            notifiableExpressions.add(expr);
304            L.d("non-observable %s", expr.getUniqueKey());
305        }
306
307        // descendants of observables gets following ids
308        for (Expr expr : observables) {
309            for (Expr parent : expr.getParents()) {
310                if (parent.hasId()) {
311                    continue;// already has some id, means observable
312                }
313                // only fields earn an id
314                if (parent instanceof FieldAccessExpr) {
315                    FieldAccessExpr fae = (FieldAccessExpr) parent;
316                    L.d("checking field access expr %s. getter: %s", fae,fae.getGetter());
317                    if (fae.isDynamic() && fae.getGetter().canBeInvalidated()) {
318                        flagMapping.add(parent.getUniqueKey());
319                        parent.setId(counter++);
320                        notifiableExpressions.add(parent);
321                        L.d("notifiable field %s : %s for %s : %s", parent.getUniqueKey(),
322                                Integer.toHexString(System.identityHashCode(parent)),
323                                expr.getUniqueKey(),
324                                Integer.toHexString(System.identityHashCode(expr)));
325                    }
326                }
327            }
328        }
329
330        // non-dynamic binding expressions receive some ids so that they can be invalidated
331        L.d("list of binding expressions");
332        for (int i = 0; i < mBindingExpressions.size(); i++) {
333            L.d("[%d] %s", i, mBindingExpressions.get(i));
334        }
335        // we don't assign ids to constant binding expressions because now invalidateAll has its own
336        // flag.
337
338        for (Expr expr : notifiableExpressions) {
339            expr.enableDirectInvalidation();
340        }
341
342        // make sure all dependencies are resolved to avoid future race conditions
343        for (Expr expr : mExprMap.values()) {
344            expr.getDependencies();
345        }
346        final int invalidateAnyFlagIndex = counter ++;
347        flagMapping.add("INVALIDATE ANY");
348        mInvalidateableFieldLimit = counter;
349        mInvalidateableFlags = new BitSet();
350        for (int i = 0; i < mInvalidateableFieldLimit; i++) {
351            mInvalidateableFlags.set(i, true);
352        }
353
354        // make sure all dependencies are resolved to avoid future race conditions
355        for (Expr expr : mExprMap.values()) {
356            if (expr.isConditional()) {
357                L.d("requirement id for %s is %d", expr, counter);
358                expr.setRequirementId(counter);
359                flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX);
360                flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX);
361                counter += 2;
362            }
363        }
364        mConditionalFlags = new BitSet();
365        for (int i = mInvalidateableFieldLimit; i < counter; i++) {
366            mConditionalFlags.set(i, true);
367        }
368        mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2;
369
370        // everybody gets an id
371        for (Map.Entry<String, Expr> entry : mExprMap.entrySet()) {
372            final Expr value = entry.getValue();
373            if (!value.hasId()) {
374                value.setId(counter++);
375            }
376        }
377
378        mFlagMapping = new String[flagMapping.size()];
379        flagMapping.toArray(mFlagMapping);
380
381        mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize);
382        mInvalidateAnyFlags = new BitSet();
383        mInvalidateAnyFlags.set(invalidateAnyFlagIndex, true);
384
385        for (Expr expr : mExprMap.values()) {
386            expr.getShouldReadFlagsWithConditionals();
387        }
388
389        for (Expr expr : mExprMap.values()) {
390            // ensure all types are calculated
391            expr.getResolvedType();
392        }
393
394        mSealed = true;
395    }
396
397    /**
398     * Run updateExpr on each binding expression until no new expressions are added.
399     * <p>
400     * Some expressions (e.g. field access) may replace themselves and add/remove new dependencies
401     * so we need to make sure each expression's update is called at least once.
402     */
403    private void updateExpressions(ModelAnalyzer modelAnalyzer) {
404        int startSize = -1;
405        while (startSize != mExprMap.size()) {
406            startSize = mExprMap.size();
407            ArrayList<Expr> exprs = new ArrayList<Expr>(mBindingExpressions);
408            for (Expr expr : exprs) {
409                expr.updateExpr(modelAnalyzer);
410            }
411        }
412    }
413
414    public int getFlagBucketCount() {
415        return mFlagBucketCount;
416    }
417
418    public int getTotalFlagCount() {
419        return mRequirementIdCount * 2 + mInvalidateableFieldLimit;
420    }
421
422    public int getInvalidateableFieldLimit() {
423        return mInvalidateableFieldLimit;
424    }
425
426    public String[] getFlagMapping() {
427        return mFlagMapping;
428    }
429
430    public String getFlag(int id) {
431        return mFlagMapping[id];
432    }
433
434    private Iterable<Expr> filterNonObservableIds(final ModelAnalyzer modelAnalyzer) {
435        return Iterables.filter(mExprMap.values(), new Predicate<Expr>() {
436            @Override
437            public boolean apply(Expr input) {
438                return input instanceof IdentifierExpr
439                        && !input.hasId()
440                        && !input.isObservable()
441                        && input.isDynamic();
442            }
443        });
444    }
445
446    private Iterable<Expr> filterObservables(final ModelAnalyzer modelAnalyzer) {
447        return Iterables.filter(mExprMap.values(), new Predicate<Expr>() {
448            @Override
449            public boolean apply(Expr input) {
450                return input.isObservable();
451            }
452        });
453    }
454
455    public List<Expr> getPendingExpressions() {
456        if (mPendingExpressions == null) {
457            mPendingExpressions = Lists.newArrayList();
458            for (Expr expr : mExprMap.values()) {
459                if (!expr.isRead() && expr.isDynamic()) {
460                    mPendingExpressions.add(expr);
461                }
462            }
463        }
464        return mPendingExpressions;
465    }
466
467    public boolean markBitsRead() {
468        // each has should read flags, we set them back on them
469        List<Expr> markedSomeFlagsRead = Lists.newArrayList();
470        for (Expr expr : filterShouldRead(getPendingExpressions())) {
471            expr.markFlagsAsRead(expr.getShouldReadFlags());
472            markedSomeFlagsRead.add(expr);
473        }
474        return pruneDone(markedSomeFlagsRead);
475    }
476
477    private boolean pruneDone(List<Expr> markedSomeFlagsAsRead) {
478        boolean marked = true;
479        List<Expr> markedAsReadList = Lists.newArrayList();
480        while (marked) {
481            marked = false;
482            for (Expr expr : mExprMap.values()) {
483                if (expr.isRead()) {
484                    continue;
485                }
486                if (expr.markAsReadIfDone()) {
487                    L.d("marked %s as read ", expr.getUniqueKey());
488                    marked = true;
489                    markedAsReadList.add(expr);
490                    markedSomeFlagsAsRead.remove(expr);
491                }
492            }
493        }
494        boolean elevated = false;
495        for (Expr markedAsRead : markedAsReadList) {
496            for (Dependency dependency : markedAsRead.getDependants()) {
497                if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) {
498                    elevated = true;
499                }
500            }
501        }
502        for (Expr partialRead : markedSomeFlagsAsRead) {
503            boolean allPathsAreSatisfied = partialRead.getAllCalculationPaths()
504                    .areAllPathsSatisfied(partialRead.mReadSoFar);
505            if (!allPathsAreSatisfied) {
506                continue;
507            }
508            for (Dependency dependency : partialRead.getDependants()) {
509                if (dependency.getDependant().considerElevatingConditionals(partialRead)) {
510                    elevated = true;
511                }
512            }
513        }
514        if (elevated) {
515            // some conditionals are elevated. We should re-calculate flags
516            for (Expr expr : getPendingExpressions()) {
517                if (!expr.isRead()) {
518                    expr.invalidateReadFlags();
519                }
520            }
521            mPendingExpressions = null;
522        }
523        return elevated;
524    }
525
526    public static Iterable<Expr> filterShouldRead(Iterable<Expr> exprs) {
527        return toCollection(Iterables.filter(exprs, sShouldReadPred));
528    }
529
530    public static List<Expr> toCollection(Iterable<Expr> iterable) {
531        return Arrays.asList(Iterables.toArray(iterable, Expr.class));
532    }
533
534    private static final Predicate<Expr> sShouldReadPred = new Predicate<Expr>() {
535        @Override
536        public boolean apply(final Expr expr) {
537            return !expr.getShouldReadFlags().isEmpty() && !Iterables.any(
538                    expr.getDependencies(), new Predicate<Dependency>() {
539                        @Override
540                        public boolean apply(Dependency dependency) {
541                            final boolean result = dependency.isConditional() ||
542                                    dependency.getOther().hasNestedCannotRead();
543                            return result;
544                        }
545                    });
546        }
547    };
548
549    /**
550     * May return null if flag is equal to invalidate any flag.
551     */
552    public Expr findFlagExpression(int flag) {
553        if (mInvalidateAnyFlags.get(flag)) {
554            return null;
555        }
556        final String key = mFlagMapping[flag];
557        if (mExprMap.containsKey(key)) {
558            return mExprMap.get(key);
559        }
560        int falseIndex = key.indexOf(FALSE_KEY_SUFFIX);
561        if (falseIndex > -1) {
562            final String trimmed = key.substring(0, falseIndex);
563            return mExprMap.get(trimmed);
564        }
565        int trueIndex = key.indexOf(TRUE_KEY_SUFFIX);
566        if (trueIndex > -1) {
567            final String trimmed = key.substring(0, trueIndex);
568            return mExprMap.get(trimmed);
569        }
570        // log everything we call
571        StringBuilder error = new StringBuilder();
572        error.append("cannot find flag:").append(flag).append("\n");
573        error.append("invalidate any flag:").append(mInvalidateAnyFlags).append("\n");
574        error.append("key:").append(key).append("\n");
575        error.append("flag mapping:").append(Arrays.toString(mFlagMapping));
576        Preconditions.checkArgument(false, error.toString());
577        return null;
578    }
579
580    public BitSet getInvalidateAnyBitSet() {
581        return mInvalidateAnyFlags;
582    }
583
584    public Expr argListExpr(Iterable<Expr> expressions) {
585        return register(new ArgListExpr(mArgListIdCounter ++, expressions));
586    }
587
588    public interface ResolveListenersCallback {
589        void resolveListeners();
590    }
591}
592