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 android.databinding.tool.ext.ExtKt;
     20 import android.databinding.tool.Binding;
     21 import android.databinding.tool.BindingTarget;
     22 import android.databinding.tool.InverseBinding;
     23 import android.databinding.tool.processing.Scope;
     24 import android.databinding.tool.reflection.Callable;
     25 import android.databinding.tool.reflection.Callable.Type;
     26 import android.databinding.tool.reflection.ModelAnalyzer;
     27 import android.databinding.tool.reflection.ModelClass;
     28 import android.databinding.tool.reflection.ModelMethod;
     29 import android.databinding.tool.util.BrNameUtil;
     30 import android.databinding.tool.store.SetterStore;
     31 import android.databinding.tool.store.SetterStore.BindingGetterCall;
     32 import android.databinding.tool.util.L;
     33 import android.databinding.tool.util.Preconditions;
     34 import android.databinding.tool.writer.KCode;
     35 
     36 import java.util.List;
     37 
     38 public class FieldAccessExpr extends Expr {
     39     String mName;
     40     // notification name for the field. Important when we map this to a method w/ different name
     41     String mBrName;
     42     Callable mGetter;
     43     final boolean mIsObservableField;
     44     boolean mIsListener;
     45     boolean mIsViewAttributeAccess;
     46 
     47     FieldAccessExpr(Expr parent, String name) {
     48         super(parent);
     49         mName = name;
     50         mIsObservableField = false;
     51     }
     52 
     53     FieldAccessExpr(Expr parent, String name, boolean isObservableField) {
     54         super(parent);
     55         mName = name;
     56         mIsObservableField = isObservableField;
     57     }
     58 
     59     public Expr getChild() {
     60         return getChildren().get(0);
     61     }
     62 
     63     public Callable getGetter() {
     64         if (mGetter == null) {
     65             getResolvedType();
     66         }
     67         return mGetter;
     68     }
     69 
     70     @Override
     71     public String getInvertibleError() {
     72         if (getGetter().setterName == null) {
     73             return "Two-way binding cannot resolve a setter for " + getResolvedType().toJavaCode() +
     74                     " property '" + mName + "'";
     75         }
     76         if (!mGetter.isDynamic()) {
     77             return "Cannot change a final field in " + getResolvedType().toJavaCode() +
     78                     " property " + mName;
     79         }
     80         return null;
     81     }
     82 
     83     public int getMinApi() {
     84         return mGetter.getMinApi();
     85     }
     86 
     87     @Override
     88     public boolean isDynamic() {
     89         if (mGetter == null) {
     90             getResolvedType();
     91         }
     92         if (mGetter == null || mGetter.type == Type.METHOD) {
     93             return true;
     94         }
     95         // if it is static final, gone
     96         if (getChild().isDynamic()) {
     97             // if owner is dynamic, then we can be dynamic unless we are static final
     98             return !mGetter.isStatic() || mGetter.isDynamic();
     99         }
    100 
    101         if (mIsViewAttributeAccess) {
    102             return true; // must be able to invalidate this
    103         }
    104 
    105         // if owner is NOT dynamic, we can be dynamic if an only if getter is dynamic
    106         return mGetter.isDynamic();
    107     }
    108 
    109     public boolean hasBindableAnnotations() {
    110         return mGetter.canBeInvalidated();
    111     }
    112 
    113     @Override
    114     public Expr resolveListeners(ModelClass listener, Expr parent) {
    115         if (mName == null || mName.isEmpty()) {
    116             return this; // ObservableFields aren't listeners
    117         }
    118         final ModelClass childType = getChild().getResolvedType();
    119         if (getGetter() == null) {
    120             if (listener == null || !mIsListener) {
    121                 L.e("Could not resolve %s.%s as an accessor or listener on the attribute.",
    122                         childType.getCanonicalName(), mName);
    123                 return this;
    124             }
    125             getChild().getParents().remove(this);
    126         } else if (listener == null) {
    127             return this; // Not a listener, but we have a getter.
    128         }
    129         List<ModelMethod> abstractMethods = listener.getAbstractMethods();
    130         int numberOfAbstractMethods = abstractMethods == null ? 0 : abstractMethods.size();
    131         if (numberOfAbstractMethods != 1) {
    132             if (mGetter == null) {
    133                 L.e("Could not find accessor %s.%s and %s has %d abstract methods, so is" +
    134                                 " not resolved as a listener",
    135                         childType.getCanonicalName(), mName,
    136                         listener.getCanonicalName(), numberOfAbstractMethods);
    137             }
    138             return this;
    139         }
    140 
    141         // Look for a signature matching the abstract method
    142         final ModelMethod listenerMethod = abstractMethods.get(0);
    143         final ModelClass[] listenerParameters = listenerMethod.getParameterTypes();
    144         boolean isStatic = getChild() instanceof StaticIdentifierExpr;
    145         List<ModelMethod> methods = childType.findMethods(mName, isStatic);
    146         if (methods == null) {
    147             return this;
    148         }
    149         for (ModelMethod method : methods) {
    150             if (acceptsParameters(method, listenerParameters) &&
    151                     method.getReturnType(null).equals(listenerMethod.getReturnType(null))) {
    152                 resetResolvedType();
    153                 // replace this with ListenerExpr in parent
    154                 Expr listenerExpr = getModel().listenerExpr(getChild(), mName, listener,
    155                         listenerMethod);
    156                 if (parent != null) {
    157                     int index;
    158                     while ((index = parent.getChildren().indexOf(this)) != -1) {
    159                         parent.getChildren().set(index, listenerExpr);
    160                     }
    161                 }
    162                 if (getModel().mBindingExpressions.contains(this)) {
    163                     getModel().bindingExpr(listenerExpr);
    164                 }
    165                 getParents().remove(parent);
    166                 if (getParents().isEmpty()) {
    167                     getModel().removeExpr(this);
    168                 }
    169                 return listenerExpr;
    170             }
    171         }
    172 
    173         if (mGetter == null) {
    174             L.e("Listener class %s with method %s did not match signature of any method %s.%s",
    175                     listener.getCanonicalName(), listenerMethod.getName(),
    176                     childType.getCanonicalName(), mName);
    177         }
    178         return this;
    179     }
    180 
    181     private boolean acceptsParameters(ModelMethod method, ModelClass[] listenerParameters) {
    182         ModelClass[] parameters = method.getParameterTypes();
    183         if (parameters.length != listenerParameters.length) {
    184             return false;
    185         }
    186         for (int i = 0; i < parameters.length; i++) {
    187             if (!parameters[i].isAssignableFrom(listenerParameters[i])) {
    188                 return false;
    189             }
    190         }
    191         return true;
    192     }
    193 
    194     @Override
    195     protected List<Dependency> constructDependencies() {
    196         final List<Dependency> dependencies = constructDynamicChildrenDependencies();
    197         for (Dependency dependency : dependencies) {
    198             if (dependency.getOther() == getChild()) {
    199                 dependency.setMandatory(true);
    200             }
    201         }
    202         return dependencies;
    203     }
    204 
    205     @Override
    206     protected String computeUniqueKey() {
    207         if (mIsObservableField) {
    208             return addTwoWay(join(mName, "..", super.computeUniqueKey()));
    209         }
    210         return addTwoWay(join(mName, ".", super.computeUniqueKey()));
    211     }
    212 
    213     public String getName() {
    214         return mName;
    215     }
    216 
    217     public String getBrName() {
    218         if (mIsListener) {
    219             return null;
    220         }
    221         try {
    222             Scope.enter(this);
    223             Preconditions.checkNotNull(mGetter, "cannot get br name before resolving the getter");
    224             return mBrName;
    225         } finally {
    226             Scope.exit();
    227         }
    228     }
    229 
    230     @Override
    231     public void updateExpr(ModelAnalyzer modelAnalyzer) {
    232         try {
    233             Scope.enter(this);
    234             resolveType(modelAnalyzer);
    235             super.updateExpr(modelAnalyzer);
    236         } finally {
    237             Scope.exit();
    238         }
    239     }
    240 
    241     @Override
    242     protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
    243         if (mIsListener) {
    244             return modelAnalyzer.findClass(Object.class);
    245         }
    246         if (mGetter == null) {
    247             Expr child = getChild();
    248             child.getResolvedType();
    249             boolean isStatic = child instanceof StaticIdentifierExpr;
    250             ModelClass resolvedType = child.getResolvedType();
    251             L.d("resolving %s. Resolved class type: %s", this, resolvedType);
    252 
    253             mGetter = resolvedType.findGetterOrField(mName, isStatic);
    254 
    255             if (mGetter == null) {
    256                 mIsListener = resolvedType.findMethods(mName, isStatic) != null;
    257                 if (!mIsListener) {
    258                     L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName);
    259                 }
    260                 return modelAnalyzer.findClass(Object.class);
    261             }
    262 
    263             if (mGetter.isStatic() && !isStatic) {
    264                 // found a static method on an instance. register a new one
    265                 child.getParents().remove(this);
    266                 getChildren().remove(child);
    267                 StaticIdentifierExpr staticId = getModel().staticIdentifierFor(resolvedType);
    268                 getChildren().add(staticId);
    269                 staticId.getParents().add(this);
    270                 child = getChild(); // replace the child for the next if stmt
    271             }
    272 
    273             if (mGetter.resolvedType.isObservableField()) {
    274                 // Make this the ".get()" and add an extra field access for the observable field
    275                 child.getParents().remove(this);
    276                 getChildren().remove(child);
    277 
    278                 FieldAccessExpr observableField = getModel().observableField(child, mName);
    279                 observableField.mGetter = mGetter;
    280 
    281                 getChildren().add(observableField);
    282                 observableField.getParents().add(this);
    283                 mGetter = mGetter.resolvedType.findGetterOrField("", false);
    284                 mName = "";
    285                 mBrName = ExtKt.br(mName);
    286             } else if (hasBindableAnnotations()) {
    287                 mBrName = ExtKt.br(BrNameUtil.brKey(mGetter));
    288             }
    289         }
    290         return mGetter.resolvedType;
    291     }
    292 
    293     @Override
    294     public Expr resolveTwoWayExpressions(Expr parent) {
    295         final Expr child = getChild();
    296         if (!(child instanceof ViewFieldExpr)) {
    297             return this;
    298         }
    299         final ViewFieldExpr expr = (ViewFieldExpr) child;
    300         final BindingTarget bindingTarget = expr.getBindingTarget();
    301 
    302         // This is a binding to a View's attribute, so look for matching attribute
    303         // on that View's BindingTarget. If there is an expression, we simply replace
    304         // the binding with that binding expression.
    305         for (Binding binding : bindingTarget.getBindings()) {
    306             if (attributeMatchesName(binding.getName(), mName)) {
    307                 final Expr replacement = binding.getExpr();
    308                 replaceExpression(parent, replacement);
    309                 return replacement;
    310             }
    311         }
    312 
    313         // There was no binding expression to bind to. This should be a two-way binding.
    314         // This is a synthesized two-way binding because we must capture the events from
    315         // the View and change the value when the target View's attribute changes.
    316         final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
    317         final ModelClass targetClass = expr.getResolvedType();
    318         BindingGetterCall getter = setterStore.getGetterCall(mName, targetClass, null, null);
    319         if (getter == null) {
    320             getter = setterStore.getGetterCall("android:" + mName, targetClass, null, null);
    321             if (getter == null) {
    322                 L.e("Could not resolve the two-way binding attribute '%s' on type '%s'",
    323                         mName, targetClass);
    324             }
    325         }
    326         InverseBinding inverseBinding = null;
    327         for (Binding binding : bindingTarget.getBindings()) {
    328             final Expr testExpr = binding.getExpr();
    329             if (testExpr instanceof TwoWayListenerExpr &&
    330                     getter.getEventAttribute().equals(binding.getName())) {
    331                 inverseBinding = ((TwoWayListenerExpr) testExpr).mInverseBinding;
    332                 break;
    333             }
    334         }
    335         if (inverseBinding == null) {
    336             inverseBinding = bindingTarget.addInverseBinding(mName, getter);
    337         }
    338         inverseBinding.addChainedExpression(this);
    339         mIsViewAttributeAccess = true;
    340         enableDirectInvalidation();
    341         return this;
    342     }
    343 
    344     private static boolean attributeMatchesName(String attribute, String field) {
    345         int colonIndex = attribute.indexOf(':');
    346         return attribute.substring(colonIndex + 1).equals(field);
    347     }
    348 
    349     private void replaceExpression(Expr parent, Expr replacement) {
    350         if (parent != null) {
    351             List<Expr> children = parent.getChildren();
    352             int index;
    353             while ((index = children.indexOf(this)) >= 0) {
    354                 children.set(index, replacement);
    355                 replacement.getParents().add(parent);
    356             }
    357             while (getParents().remove(parent)) {
    358                 // just remove all copies of parent.
    359             }
    360         }
    361         if (getParents().isEmpty()) {
    362             getModel().removeExpr(this);
    363         }
    364     }
    365 
    366     @Override
    367     protected String asPackage() {
    368         String parentPackage = getChild().asPackage();
    369         return parentPackage == null ? null : parentPackage + "." + mName;
    370     }
    371 
    372     @Override
    373     protected KCode generateCode(boolean expand) {
    374         KCode code = new KCode();
    375         if (expand) {
    376             String defaultValue = ModelAnalyzer.getInstance().getDefaultValue(
    377                     getResolvedType().toJavaCode());
    378             code.app("(", getChild().toCode(true))
    379                     .app(" == null) ? ")
    380                     .app(defaultValue)
    381                     .app(" : ");
    382         }
    383         code.app("", getChild().toCode(expand)).app(".");
    384         if (getGetter().type == Callable.Type.FIELD) {
    385             return code.app(getGetter().name);
    386         } else {
    387             return code.app(getGetter().name).app("()");
    388         }
    389     }
    390 
    391     @Override
    392     public KCode toInverseCode(KCode value) {
    393         if (mGetter.setterName == null) {
    394             throw new IllegalStateException("There is no inverse for " + toCode().generate());
    395         }
    396         KCode castValue = new KCode("(").app(getResolvedType().toJavaCode() + ")(", value).app(")");
    397         String type = getChild().getResolvedType().toJavaCode();
    398         KCode code = new KCode("targetObj_.");
    399         if (getGetter().type == Callable.Type.FIELD) {
    400             code.app(getGetter().setterName).app(" = ", castValue).app(";");
    401         } else {
    402             code.app(getGetter().setterName).app("(", castValue).app(")").app(";");
    403         }
    404         return new KCode()
    405                 .app("final ")
    406                 .app(type)
    407                 .app(" targetObj_ = ", getChild().toCode(true))
    408                 .app(";")
    409                 .nl(new KCode("if (targetObj_ != null) {"))
    410                 .tab(code)
    411                 .nl(new KCode("}"));
    412     }
    413 }
    414