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