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