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