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