All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.databinding.tool.expr.ExprModel Maven / Gradle / Ivy

Go to download

The annotation processor for Data Binding. Generates binding classes for runtime.

The newest version!
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.databinding.tool.expr;

import android.databinding.tool.BindingTarget;
import android.databinding.tool.CallbackWrapper;
import android.databinding.tool.InverseBinding;
import android.databinding.tool.processing.ErrorMessages;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.reflection.ModelMethod;
import android.databinding.tool.store.Location;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.writer.ExprModelExt;
import android.databinding.tool.writer.FlagSet;

import org.antlr.v4.runtime.ParserRuleContext;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class ExprModel {
    static final String DYNAMIC_UTIL = "android.databinding.DynamicUtil";
    public static final String SAFE_UNBOX_METHOD_NAME = "safeUnbox";

    Map mExprMap = new HashMap();

    List mBindingExpressions = new ArrayList();

    private int mInvalidateableFieldLimit = 0;

    private int mRequirementIdCount = 0;

    // each arg list receives a unique id even if it is the same arguments and method.
    private int mArgListIdCounter = 0;

    private static final String TRUE_KEY_SUFFIX = "== true";
    private static final String FALSE_KEY_SUFFIX = "== false";

    /**
     * Any expression can be invalidated by invalidating this flag.
     */
    private BitSet mInvalidateAnyFlags;
    private int mInvalidateAnyFlagIndex;

    /**
     * Used by code generation. Keeps the list of expressions that are waiting to be evaluated.
     */
    private List mPendingExpressions;

    /**
     * Used for converting flags into identifiers while debugging.
     */
    private String[] mFlagMapping;

    private int mFlagBucketCount;// how many buckets we use to identify flags

    private List mObservables;

    private boolean mSealed = false;

    private Map mImports = new HashMap();

    private ParserRuleContext mCurrentParserContext;
    private Location mCurrentLocationInFile;

    private Map mCallbackWrappers = new HashMap();

    private AtomicInteger mCallbackIdCounter = new AtomicInteger();

    private ExprModelExt mExt = new ExprModelExt();

    /**
     * Adds the expression to the list of expressions and returns it.
     * If it already exists, returns existing one.
     *
     * @param expr The new parsed expression
     * @return The expression itself or another one if the same thing was parsed before
     */
    public  T register(T expr) {
        Preconditions.check(!mSealed, "Cannot add expressions to a model after it is sealed");
        Location location = null;
        if (mCurrentParserContext != null) {
            location = new Location(mCurrentParserContext);
            location.setParentLocation(mCurrentLocationInFile);
        }
        //noinspection unchecked
        T existing = (T) mExprMap.get(expr.getUniqueKey());
        if (existing != null) {
            Preconditions.check(expr.getParents().isEmpty(),
                    "If an expression already exists, it should've never been added to a parent,"
                            + "if thats the case, somewhere we are creating an expression w/o"
                            + "calling expression model");
            // tell the expr that it is being swapped so that if it was added to some other expr
            // as a parent, those can swap their references
            expr.onSwappedWith(existing);
            if (location != null) {
                existing.addLocation(location);
            }
            return existing;
        }
        mExprMap.put(expr.getUniqueKey(), expr);
        expr.setModel(this);
        if (location != null) {
            expr.addLocation(location);
        }
        return expr;
    }

    protected void markSealed() {
        mSealed = true;
    }

    public ExprModelExt getExt() {
        return mExt;
    }

    public int obtainCallbackId() {
        return mCallbackIdCounter.incrementAndGet();
    }

    public void setCurrentParserContext(ParserRuleContext currentParserContext) {
        mCurrentParserContext = currentParserContext;
    }

    public ParserRuleContext getCurrentParserContext() {
        return mCurrentParserContext;
    }

    public Location getCurrentLocationInFile() {
        return mCurrentLocationInFile;
    }

    public Map getExprMap() {
        return mExprMap;
    }

    public int size() {
        return mExprMap.size();
    }

    public ComparisonExpr comparison(String op, Expr left, Expr right) {
        return register(new ComparisonExpr(op, left, right));
    }

    public InstanceOfExpr instanceOfOp(Expr expr, String type) {
        return register(new InstanceOfExpr(expr, type));
    }

    public FieldAccessExpr field(Expr parent, String name) {
        return register(new FieldAccessExpr(parent, name));
    }

    public MethodReferenceExpr methodReference(Expr parent, String name) {
        return register(new MethodReferenceExpr(parent, name));
    }

    public SymbolExpr symbol(String text, Class type) {
        return register(new SymbolExpr(text, type));
    }

    public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) {
        return register(new TernaryExpr(pred, ifTrue, ifFalse));
    }

    public IdentifierExpr identifier(String name) {
        return register(new IdentifierExpr(name));
    }

    public StaticIdentifierExpr staticIdentifier(String name) {
        return register(new StaticIdentifierExpr(name));
    }

    public BuiltInVariableExpr builtInVariable(String name, String type, String accessCode) {
        return register(new BuiltInVariableExpr(name, type, accessCode));
    }

    public ViewFieldExpr viewFieldExpr(BindingTarget bindingTarget) {
        return register(new ViewFieldExpr(bindingTarget));
    }

    public IdentifierExpr dynamicUtil() {
        IdentifierExpr dynamicUtil = staticIdentifier(DYNAMIC_UTIL);
        dynamicUtil.setUserDefinedType(DYNAMIC_UTIL);
        return dynamicUtil;
    }

    public IdentifierExpr viewDataBinding() {
        IdentifierExpr viewDataBinding = staticIdentifier(ModelAnalyzer.VIEW_DATA_BINDING);
        viewDataBinding.setUserDefinedType(ModelAnalyzer.VIEW_DATA_BINDING);
        return viewDataBinding;
    }

    public MethodCallExpr safeUnbox(Expr expr) {
        ModelClass resolvedType = expr.getResolvedType();
        Preconditions.check(resolvedType.unbox() != resolvedType, ErrorMessages.CANNOT_UNBOX_TYPE,
                resolvedType);
        MethodCallExpr methodCallExpr = methodCall(dynamicUtil(), SAFE_UNBOX_METHOD_NAME,
                Collections.singletonList(expr));
        methodCallExpr.setAllowProtected();
        for (Location location : expr.getLocations()) {
            methodCallExpr.addLocation(location);
        }
        return methodCallExpr;
    }

    /**
     * These are global methods in the expressions.
     * 

* To keep this list under control, we validate the method name instead of just resolving to * DynamicUtil or parent. */ public Expr globalMethodCall(String methodName, List args) { Preconditions.check(SAFE_UNBOX_METHOD_NAME.equals(methodName), ErrorMessages.CANNOT_FIND_METHOD_ON_OWNER, methodName, "ViewDataBinding"); Preconditions.check(args.size() == 1, ErrorMessages.ARGUMENT_COUNT_MISMATCH, 1, args.size()); MethodCallExpr expr = methodCall(dynamicUtil(), SAFE_UNBOX_METHOD_NAME, args); expr.setAllowProtected(); return expr; } /** * Creates a static identifier for the given class or returns the existing one. */ public StaticIdentifierExpr staticIdentifierFor(final ModelClass modelClass) { final String type = modelClass.getCanonicalName(); // check for existing StaticIdentifierExpr id = findStaticIdentifierExpr(type); if (id != null) { return id; } // does not exist. Find a name for it. int cnt = 0; int dotIndex = type.lastIndexOf("."); String baseName; Preconditions.check(dotIndex < type.length() - 1, "Invalid type %s", type); if (dotIndex == -1) { baseName = type; } else { baseName = type.substring(dotIndex + 1); } while (true) { String candidate = cnt == 0 ? baseName : baseName + cnt; if (!mImports.containsKey(candidate)) { return addImport(candidate, type, null); } cnt ++; Preconditions.check(cnt < 100, "Failed to create an import for " + type); } } @Nullable private StaticIdentifierExpr findStaticIdentifierExpr(String type) { for (Expr expr : mExprMap.values()) { if (expr instanceof StaticIdentifierExpr) { StaticIdentifierExpr id = (StaticIdentifierExpr) expr; if (id.getUserDefinedType().equals(type)) { return id; } } } return null; } public MethodCallExpr methodCall(Expr target, String name, List args) { return register(new MethodCallExpr(target, name, args)); } public MathExpr math(Expr left, String op, Expr right) { return register(new MathExpr(left, op, right)); } public TernaryExpr logical(Expr left, String op, Expr right) { if ("&&".equals(op)) { // left && right // left ? right : false return register(new TernaryExpr(left, right, symbol("false", boolean.class))); } else { // left || right // left ? true : right return register(new TernaryExpr(left, symbol("true", boolean.class), right)); } } public BitShiftExpr bitshift(Expr left, String op, Expr right) { return register(new BitShiftExpr(left, op, right)); } public UnaryExpr unary(String op, Expr expr) { return register(new UnaryExpr(op, expr)); } public Expr resourceExpr(BindingTarget target, String packageName, String resourceType, String resourceName, List args) { return register(new ResourceExpr(target, packageName, resourceType, resourceName, args)); } public Expr bracketExpr(Expr variableExpr, Expr argExpr) { return register(new BracketExpr(variableExpr, argExpr)); } public Expr castExpr(String type, Expr expr) { return register(new CastExpr(type, expr)); } public TwoWayListenerExpr twoWayListenerExpr(InverseBinding inverseBinding) { return register(new TwoWayListenerExpr(inverseBinding)); } public List getBindingExpressions() { return mBindingExpressions; } public StaticIdentifierExpr addImport(String alias, String type, Location location) { String existing = mImports.get(alias); if (existing != null) { if (existing.equals(type)) { final StaticIdentifierExpr id = findStaticIdentifierExpr(type); Preconditions.checkNotNull(id, "Missing import expression although it is" + " registered"); return id; } else { L.e("%s has already been defined as %s but trying to re-define as %s", alias, existing, type); } } final StaticIdentifierExpr id = staticIdentifier(alias); L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName()); id.setUserDefinedType(type); if (location != null) { id.addLocation(location); } mImports.put(alias, type); return id; } public Map getImports() { return mImports; } /** * The actual thingy that is set on the binding target. * * Input must be already registered */ public Expr bindingExpr(Expr bindingExpr) { Preconditions.check(mExprMap.containsKey(bindingExpr.getUniqueKey()), "Main expression should already be registered"); if (!mBindingExpressions.contains(bindingExpr)) { mBindingExpressions.add(bindingExpr); } bindingExpr.markAsBindingExpression(); return bindingExpr; } public void removeExpr(Expr expr) { Preconditions.check(!mSealed, "Can't modify the expression list after sealing the model."); mBindingExpressions.remove(expr); mExprMap.remove(expr.getUniqueKey()); } public List getObservables() { return mObservables; } /** * Give id to each expression. Will be useful if we serialize. */ public void seal() { L.d("sealing model"); resolveTypes(); List notifiableExpressions = new ArrayList(); //ensure class analyzer. We need to know observables at this point final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); updateExpressions(modelAnalyzer); int counter = 0; final Iterable observables = filterObservables(); List flagMapping = new ArrayList(); mObservables = new ArrayList(); for (Expr expr : observables) { // observables gets initial ids flagMapping.add(expr.getUniqueKey()); expr.setId(counter++); mObservables.add(expr); notifiableExpressions.add(expr); L.d("observable %s", expr.toString()); } // non-observable identifiers gets next ids final Iterable nonObservableIds = filterNonObservableIds(modelAnalyzer); for (Expr expr : nonObservableIds) { flagMapping.add(expr.getUniqueKey()); expr.setId(counter++); notifiableExpressions.add(expr); L.d("non-observable %s", expr.toString()); } // descendants of observables gets following ids for (Expr expr : observables) { for (Expr parent : expr.getParents()) { if (parent.hasId()) { continue;// already has some id, means observable } // only fields earn an id if (parent instanceof FieldAccessExpr) { FieldAccessExpr fae = (FieldAccessExpr) parent; L.d("checking field access expr %s. getter: %s", fae,fae.getGetter()); // FAE#getter might be null if it is used only in a callback. if (fae.getGetter() != null && fae.isDynamic() && fae.getGetter().canBeInvalidated()) { flagMapping.add(parent.getUniqueKey()); parent.setId(counter++); notifiableExpressions.add(parent); L.d("notifiable field %s : %s for %s : %s", parent.toString(), Integer.toHexString(System.identityHashCode(parent)), expr.getUniqueKey(), Integer.toHexString(System.identityHashCode(expr))); } } } } // now all 2-way bound view fields for (Expr expr : mExprMap.values()) { if (expr instanceof FieldAccessExpr) { FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) expr; if (fieldAccessExpr.getTarget() instanceof ViewFieldExpr) { flagMapping.add(fieldAccessExpr.getUniqueKey()); fieldAccessExpr.setId(counter++); } } } // non-dynamic binding expressions receive some ids so that they can be invalidated L.d("list of binding expressions"); for (int i = 0; i < mBindingExpressions.size(); i++) { L.d("[%d] %s", i, mBindingExpressions.get(i)); } // we don't assign ids to constant binding expressions because now invalidateAll has its own // flag. for (Expr expr : notifiableExpressions) { expr.enableDirectInvalidation(); } // make sure all dependencies are resolved to avoid future race conditions for (Expr expr : mExprMap.values()) { expr.getDependencies(); } mInvalidateAnyFlagIndex = counter ++; flagMapping.add("INVALIDATE ANY"); mInvalidateableFieldLimit = counter; BitSet invalidateableFlags = new BitSet(); for (int i = 0; i < mInvalidateableFieldLimit; i++) { invalidateableFlags.set(i, true); } // make sure all dependencies are resolved to avoid future race conditions for (Expr expr : mExprMap.values()) { if (expr.isConditional()) { L.d("requirement id for %s is %d", expr, counter); expr.setRequirementId(counter); flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX); flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX); counter += 2; } } BitSet conditionalFlags = new BitSet(); for (int i = mInvalidateableFieldLimit; i < counter; i++) { conditionalFlags.set(i, true); } mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2; // everybody gets an id for (Map.Entry entry : mExprMap.entrySet()) { final Expr value = entry.getValue(); if (!value.hasId()) { value.setId(counter++); } } mFlagMapping = new String[flagMapping.size()]; flagMapping.toArray(mFlagMapping); mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize); mInvalidateAnyFlags = new BitSet(); mInvalidateAnyFlags.set(mInvalidateAnyFlagIndex, true); for (Expr expr : mExprMap.values()) { expr.getShouldReadFlagsWithConditionals(); } for (Expr expr : mExprMap.values()) { // ensure all types are calculated expr.getResolvedType(); } mSealed = true; } /** * Run updateExpr on each binding expression until no new expressions are added. *

* Some expressions (e.g. field access) may replace themselves and add/remove new dependencies * so we need to make sure each expression's update is called at least once. */ private void updateExpressions(ModelAnalyzer modelAnalyzer) { int startSize = -1; while (startSize != mExprMap.size()) { startSize = mExprMap.size(); ArrayList exprs = new ArrayList(mBindingExpressions); for (Expr expr : exprs) { expr.updateExpr(modelAnalyzer); } } injectSafeUnboxing(modelAnalyzer); } /** * This traverses each expression to inject a null-safe auto-unboxing th each expression * that needs it. */ private void injectSafeUnboxing(ModelAnalyzer modelAnalyzer) { ArrayList expressions = new ArrayList(mBindingExpressions); for (Expr expr : expressions) { expr.recursivelyInjectSafeUnboxing(modelAnalyzer, this); } } public int getFlagBucketCount() { return mFlagBucketCount; } public int getTotalFlagCount() { return mRequirementIdCount * 2 + mInvalidateableFieldLimit; } public int getInvalidateableFieldLimit() { return mInvalidateableFieldLimit; } public String[] getFlagMapping() { return mFlagMapping; } public String getFlag(int id) { return mFlagMapping[id]; } private List filterNonObservableIds(final ModelAnalyzer modelAnalyzer) { List result = new ArrayList(); for (Expr input : mExprMap.values()) { if (input instanceof IdentifierExpr && !input.hasId() && !input.isObservable() && input.isDynamic()) { result.add(input); } } return result; } private Iterable filterObservables() { return mExprMap.values().stream() .filter(expr -> expr.isObservable()) .collect(Collectors.toList()); } /** * Calls getResolvedType() on all expressions until no more changes are made. */ private void resolveTypes() { int numExpressions; do { numExpressions = mExprMap.size(); mExprMap.values().stream() .collect(Collectors.toList()) .forEach(expr -> expr.getResolvedType()); } while (mExprMap.size() != numExpressions); } public List getPendingExpressions() { if (mPendingExpressions == null) { mPendingExpressions = new ArrayList(); for (Expr expr : mExprMap.values()) { // if an expression is NOT dynanic but has conditional dependants, still return it // so that conditional flags can be set if (!expr.isRead() && (expr.isDynamic() || expr.hasConditionalDependant())) { mPendingExpressions.add(expr); } } } return mPendingExpressions; } public boolean markBitsRead() { // each has should read flags, we set them back on them List markedSomeFlagsRead = new ArrayList(); for (Expr expr : filterShouldRead(getPendingExpressions())) { expr.markFlagsAsRead(expr.getShouldReadFlags()); markedSomeFlagsRead.add(expr); } return pruneDone(markedSomeFlagsRead); } private boolean pruneDone(List markedSomeFlagsAsRead) { boolean marked = true; List markedAsReadList = new ArrayList(); while (marked) { marked = false; for (Expr expr : mExprMap.values()) { if (expr.isRead()) { continue; } if (expr.markAsReadIfDone()) { L.d("marked %s as read ", expr.toString()); marked = true; markedAsReadList.add(expr); markedSomeFlagsAsRead.remove(expr); } } } boolean elevated = false; for (Expr markedAsRead : markedAsReadList) { for (Dependency dependency : markedAsRead.getDependants()) { if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) { elevated = true; } } } if (!elevated) { for (Expr partialRead : markedSomeFlagsAsRead) { // even if all paths are not satisfied, we can elevate certain conditional // dependencies if all of their paths are satisfied. for (Dependency dependency : partialRead.getDependants()) { Expr dependant = dependency.getDependant(); if (dependant.isConditional() && dependant.getAllCalculationPaths() .areAllPathsSatisfied(partialRead.mReadSoFar)) { if (dependant.considerElevatingConditionals(partialRead)) { elevated = true; } } } } } if (elevated) { // some conditionals are elevated. We should re-calculate flags for (Expr expr : getPendingExpressions()) { if (!expr.isRead()) { expr.invalidateReadFlags(); } } mPendingExpressions = null; } return elevated; } private static boolean hasConditionalOrNestedCannotReadDependency(Expr expr) { for (Dependency dependency : expr.getDependencies()) { if (dependency.isConditional() || dependency.getOther().hasNestedCannotRead()) { return true; } } return false; } public static ArrayList filterShouldRead(Iterable exprs) { ArrayList result = new ArrayList(); for (Expr expr : exprs) { if (!expr.getShouldReadFlags().isEmpty() && !hasConditionalOrNestedCannotReadDependency(expr)) { result.add(expr); } } return result; } /** * May return null if flag is equal to invalidate any flag. */ public Expr findFlagExpression(int flag) { if (mInvalidateAnyFlags.get(flag)) { return null; } final String key = mFlagMapping[flag]; if (mExprMap.containsKey(key)) { return mExprMap.get(key); } int falseIndex = key.indexOf(FALSE_KEY_SUFFIX); if (falseIndex > -1) { final String trimmed = key.substring(0, falseIndex); return mExprMap.get(trimmed); } int trueIndex = key.indexOf(TRUE_KEY_SUFFIX); if (trueIndex > -1) { final String trimmed = key.substring(0, trueIndex); return mExprMap.get(trimmed); } // log everything we call StringBuilder error = new StringBuilder(); error.append("cannot find flag:").append(flag).append("\n"); error.append("invalidate any flag:").append(mInvalidateAnyFlags).append("\n"); error.append("key:").append(key).append("\n"); error.append("flag mapping:").append(Arrays.toString(mFlagMapping)); L.e(error.toString()); return null; } public BitSet getInvalidateAnyBitSet() { return mInvalidateAnyFlags; } public int getInvalidateAnyFlagIndex() { return mInvalidateAnyFlagIndex; } public Expr argListExpr(Iterable expressions) { return register(new ArgListExpr(mArgListIdCounter ++, expressions)); } public void setCurrentLocationInFile(Location location) { mCurrentLocationInFile = location; } public Expr listenerExpr(Expr expression, String name, ModelClass listenerType, ModelMethod listenerMethod) { return register(new ListenerExpr(expression, name, listenerType, listenerMethod)); } public FieldAssignmentExpr assignment(Expr target, String name, Expr value) { return register(new FieldAssignmentExpr(target, name, value)); } public Map getCallbackWrappers() { return mCallbackWrappers; } public CallbackWrapper callbackWrapper(ModelClass klass, ModelMethod method) { final String key = CallbackWrapper.uniqueKey(klass, method); CallbackWrapper wrapper = mCallbackWrappers.get(key); if (wrapper == null) { wrapper = new CallbackWrapper(klass, method); mCallbackWrappers.put(key, wrapper); } return wrapper; } public LambdaExpr lambdaExpr(Expr expr, CallbackExprModel callbackExprModel) { return register(new LambdaExpr(expr, callbackExprModel)); } public IdentifierExpr findIdentifier(String name) { for (Expr expr : mExprMap.values()) { if (expr instanceof IdentifierExpr && name.equals(((IdentifierExpr) expr).getName())) { return (IdentifierExpr) expr; } } return null; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy