Home | History | Annotate | Download | only in expr
      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      */
     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 
    115     public void setCurrentParserContext(ParserRuleContext currentParserContext) {
    116         mCurrentParserContext = currentParserContext;
    117     }
    118 
    119     public void unregister(Expr expr) {
    120         mExprMap.remove(expr.getUniqueKey());
    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     /**
    164      * Creates a static identifier for the given class or returns the existing one.
    165      */
    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