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