• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.databinding.tool.expr;
18 
19 import android.databinding.tool.BindingTarget;
20 import android.databinding.tool.InverseBinding;
21 import android.databinding.tool.reflection.ModelAnalyzer;
22 import android.databinding.tool.reflection.ModelClass;
23 import android.databinding.tool.reflection.ModelMethod;
24 import android.databinding.tool.store.Location;
25 import android.databinding.tool.util.L;
26 import android.databinding.tool.util.Preconditions;
27 import android.databinding.tool.writer.FlagSet;
28 
29 import org.antlr.v4.runtime.ParserRuleContext;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.BitSet;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 
38 public 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      */
register(T expr)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 
setCurrentParserContext(ParserRuleContext currentParserContext)119     public void setCurrentParserContext(ParserRuleContext currentParserContext) {
120         mCurrentParserContext = currentParserContext;
121     }
122 
getExprMap()123     public Map<String, Expr> getExprMap() {
124         return mExprMap;
125     }
126 
size()127     public int size() {
128         return mExprMap.size();
129     }
130 
comparison(String op, Expr left, Expr right)131     public ComparisonExpr comparison(String op, Expr left, Expr right) {
132         return register(new ComparisonExpr(op, left, right));
133     }
134 
instanceOfOp(Expr expr, String type)135     public InstanceOfExpr instanceOfOp(Expr expr, String type) {
136         return register(new InstanceOfExpr(expr, type));
137     }
138 
field(Expr parent, String name)139     public FieldAccessExpr field(Expr parent, String name) {
140         return register(new FieldAccessExpr(parent, name));
141     }
142 
observableField(Expr parent, String name)143     public FieldAccessExpr observableField(Expr parent, String name) {
144         return register(new FieldAccessExpr(parent, name, true));
145     }
146 
symbol(String text, Class type)147     public SymbolExpr symbol(String text, Class type) {
148         return register(new SymbolExpr(text, type));
149     }
150 
ternary(Expr pred, Expr ifTrue, Expr ifFalse)151     public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) {
152         return register(new TernaryExpr(pred, ifTrue, ifFalse));
153     }
154 
identifier(String name)155     public IdentifierExpr identifier(String name) {
156         return register(new IdentifierExpr(name));
157     }
158 
staticIdentifier(String name)159     public StaticIdentifierExpr staticIdentifier(String name) {
160         return register(new StaticIdentifierExpr(name));
161     }
162 
builtInVariable(String name, String type, String accessCode)163     public BuiltInVariableExpr builtInVariable(String name, String type, String accessCode) {
164         return register(new BuiltInVariableExpr(name, type, accessCode));
165     }
166 
viewFieldExpr(BindingTarget bindingTarget)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      */
staticIdentifierFor(final ModelClass modelClass)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