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

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

/*
 * 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.Binding;
import android.databinding.tool.BindingTarget;
import android.databinding.tool.InverseBinding;
import android.databinding.tool.ext.ExtKt;
import android.databinding.tool.processing.ErrorMessages;
import android.databinding.tool.processing.Scope;
import android.databinding.tool.reflection.Callable;
import android.databinding.tool.reflection.Callable.Type;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.store.SetterStore;
import android.databinding.tool.store.SetterStore.BindingGetterCall;
import android.databinding.tool.util.BrNameUtil;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.writer.KCode;

import com.google.common.collect.Lists;

import java.util.List;

public class FieldAccessExpr extends MethodBaseExpr {
    // notification name for the field. Important when we map this to a method w/ different name
    String mBrName;
    Callable mGetter;
    boolean mIsListener;
    boolean mIsViewAttributeAccess;

    FieldAccessExpr(Expr parent, String name) {
        super(parent, name);
        mName = name;
    }

    public Callable getGetter() {
        if (mGetter == null) {
            getResolvedType();
        }
        return mGetter;
    }

    @Override
    public String getInvertibleError() {
        if (getGetter() == null) {
            return "Listeners do not support two-way binding";
        }
        if (mGetter.setterName == null) {
            return "Two-way binding cannot resolve a setter for " + getResolvedType().toJavaCode() +
                    " property '" + mName + "'";
        }
        if (!mGetter.isDynamic()) {
            return "Cannot change a final field in " + getResolvedType().toJavaCode() +
                    " property " + mName;
        }
        return null;
    }

    public int getMinApi() {
        return mGetter == null ? 0 : mGetter.getMinApi();
    }

    @Override
    public boolean isDynamic() {
        if (mGetter == null) {
            getResolvedType();
        }
        if (mGetter == null || mGetter.type == Type.METHOD) {
            return true;
        }
        // if it is static final, gone
        if (getTarget().isDynamic()) {
            // if owner is dynamic, then we can be dynamic unless we are static final
            return !mGetter.isStatic() || mGetter.isDynamic();
        }

        if (mIsViewAttributeAccess) {
            return true; // must be able to invalidate this
        }

        // if owner is NOT dynamic, we can be dynamic if an only if getter is dynamic
        return mGetter.isDynamic();
    }

    public boolean hasBindableAnnotations() {
        return mGetter != null && mGetter.canBeInvalidated();
    }

    @Override
    public Expr resolveListeners(ModelClass listener, Expr parent) {
        final ModelClass targetType = getTarget().getResolvedType();
        if (getGetter() == null && (listener == null || !mIsListener)) {
            L.e("Could not resolve %s.%s as an accessor or listener on the attribute.",
                    targetType.getCanonicalName(), mName);
            return this;
        }
        try {
            Expr listenerExpr = resolveListenersAsMethodReference(listener, parent);
            L.w("Method references using '.' is deprecated. Instead of '%s', use '%s::%s'",
                    toString(), getTarget(), getName());
            return listenerExpr;
        } catch (IllegalStateException e) {
            if (getGetter() == null) {
                L.e("%s", e.getMessage());
            }
            return this;
        }
    }

    @Override
    protected String computeUniqueKey() {
        return join(mName, ".", getTarget().getUniqueKey());
    }

    public String getBrName() {
        if (mIsListener) {
            return null;
        }
        try {
            Scope.enter(this);
            Preconditions.checkNotNull(mGetter, "cannot get br name before resolving the getter");
            return mBrName;
        } finally {
            Scope.exit();
        }
    }

    @Override
    protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
        if (mIsListener) {
            return modelAnalyzer.findClass(Object.class);
        }
        if (mGetter == null) {
            Expr target = getTarget();
            target.getResolvedType();
            boolean isStatic = target instanceof StaticIdentifierExpr;
            ModelClass resolvedType = target.getResolvedType();
            L.d("resolving %s. Resolved class type: %s", this, resolvedType);

            mGetter = resolvedType.findGetterOrField(mName, isStatic);

            if (mGetter == null) {
                mIsListener = !resolvedType.findMethods(mName, isStatic).isEmpty();
                if (!mIsListener) {
                    L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName);
                }
                return modelAnalyzer.findClass(Object.class);
            }

            if (mGetter.isStatic() && !isStatic) {
                // found a static method on an instance. register a new one
                replaceStaticIdentifier(resolvedType);
                target = getTarget();
            }

            if (mGetter.resolvedType.isObservableField()) {
                // Make this the ".get()" and add an extra field access for the observable field
                target.getParents().remove(this);
                getChildren().remove(target);

                FieldAccessExpr observableField = getModel().observableField(target, mName);
                getChildren().add(observableField);
                observableField.getParents().add(this);
                mGetter = mGetter.resolvedType.findGetterOrField("", false);
                mName = "";
                mBrName = ExtKt.br(mName);
            } else if (hasBindableAnnotations()) {
                mBrName = ExtKt.br(BrNameUtil.brKey(mGetter));
            }
        }
        return mGetter.resolvedType;
    }

    protected void replaceStaticIdentifier(ModelClass staticIdentifierType) {
        getTarget().getParents().remove(this);
        getChildren().remove(getTarget());
        StaticIdentifierExpr staticId = getModel().staticIdentifierFor(staticIdentifierType);
        getChildren().add(staticId);
        staticId.getParents().add(this);
    }

    @Override
    public Expr resolveTwoWayExpressions(Expr parent) {
        final Expr child = getTarget();
        if (!(child instanceof ViewFieldExpr)) {
            return this;
        }
        final ViewFieldExpr expr = (ViewFieldExpr) child;
        final BindingTarget bindingTarget = expr.getBindingTarget();

        // This is a binding to a View's attribute, so look for matching attribute
        // on that View's BindingTarget. If there is an expression, we simply replace
        // the binding with that binding expression.
        for (Binding binding : bindingTarget.getBindings()) {
            if (attributeMatchesName(binding.getName(), mName)) {
                final Expr replacement = binding.getExpr();
                replaceExpression(parent, replacement);
                return replacement;
            }
        }

        // There was no binding expression to bind to. This should be a two-way binding.
        // This is a synthesized two-way binding because we must capture the events from
        // the View and change the value when the target View's attribute changes.
        final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
        final ModelClass targetClass = expr.getResolvedType();
        BindingGetterCall getter = setterStore.getGetterCall(mName, targetClass, null, null);
        if (getter == null) {
            getter = setterStore.getGetterCall("android:" + mName, targetClass, null, null);
            if (getter == null) {
                L.e("Could not resolve the two-way binding attribute '%s' on type '%s'",
                        mName, targetClass);
            }
        }
        InverseBinding inverseBinding = null;
        for (Binding binding : bindingTarget.getBindings()) {
            final Expr testExpr = binding.getExpr();
            if (testExpr instanceof TwoWayListenerExpr &&
                    getter.getEventAttribute().equals(binding.getName())) {
                inverseBinding = ((TwoWayListenerExpr) testExpr).mInverseBinding;
                break;
            }
        }
        if (inverseBinding == null) {
            inverseBinding = bindingTarget.addInverseBinding(mName, getter);
        }
        inverseBinding.addChainedExpression(this);
        mIsViewAttributeAccess = true;
        enableDirectInvalidation();
        return this;
    }

    private static boolean attributeMatchesName(String attribute, String field) {
        int colonIndex = attribute.indexOf(':');
        return attribute.substring(colonIndex + 1).equals(field);
    }

    private void replaceExpression(Expr parent, Expr replacement) {
        if (parent != null) {
            List children = parent.getChildren();
            int index;
            while ((index = children.indexOf(this)) >= 0) {
                children.set(index, replacement);
                replacement.getParents().add(parent);
            }
            while (getParents().remove(parent)) {
                // just remove all copies of parent.
            }
        }
        if (getParents().isEmpty()) {
            getModel().removeExpr(this);
        }
    }

    @Override
    protected String asPackage() {
        String parentPackage = getTarget().asPackage();
        return parentPackage == null ? null : parentPackage + "." + mName;
    }

    @Override
    protected KCode generateCode() {
        // once we can deprecate using Field.access for callbacks, we can get rid of this since
        // it will be detected when resolve type is run.
        Preconditions.checkNotNull(getGetter(), ErrorMessages.CANNOT_RESOLVE_TYPE, this);
        KCode code = new KCode()
                .app("", getTarget().toCode()).app(".");
        if (getGetter().type == Callable.Type.FIELD) {
            return code.app(getGetter().name);
        } else {
            return code.app(getGetter().name).app("()");
        }
    }

    @Override
    public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) {
        Expr castExpr = model.castExpr(getResolvedType().toJavaCode(), value);
        Expr target = getTarget().cloneToModel(model);
        Expr result;
        if (getGetter().type == Callable.Type.FIELD) {
            result = model.assignment(target, mName, castExpr);
        } else {
            result = model.methodCall(target, mGetter.setterName, Lists.newArrayList(castExpr));
        }
        return result;
    }

    @Override
    public Expr cloneToModel(ExprModel model) {
        final Expr clonedTarget = getTarget().cloneToModel(model);
        return model.field(clonedTarget, mName);
    }

    @Override
    public String toString() {
        String name = mName.isEmpty() ? "get()" : mName;
        return getTarget().toString() + '.' + name;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy