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