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