Home | History | Annotate | Download | only in tool
      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;
     18 
     19 import android.databinding.tool.expr.Expr;
     20 import android.databinding.tool.expr.ExprModel;
     21 import android.databinding.tool.expr.LambdaExpr;
     22 import android.databinding.tool.processing.ErrorMessages;
     23 import android.databinding.tool.processing.Scope;
     24 import android.databinding.tool.processing.scopes.LocationScopeProvider;
     25 import android.databinding.tool.reflection.ModelAnalyzer;
     26 import android.databinding.tool.reflection.ModelClass;
     27 import android.databinding.tool.reflection.ModelMethod;
     28 import android.databinding.tool.store.Location;
     29 import android.databinding.tool.store.SetterStore;
     30 import android.databinding.tool.store.SetterStore.BindingSetterCall;
     31 import android.databinding.tool.store.SetterStore.SetterCall;
     32 import android.databinding.tool.util.L;
     33 import android.databinding.tool.util.Preconditions;
     34 import android.databinding.tool.writer.LayoutBinderWriterKt;
     35 
     36 import java.util.List;
     37 import java.util.Map;
     38 
     39 public class Binding implements LocationScopeProvider {
     40 
     41     private final String mName;
     42     private Expr mExpr;
     43     private final BindingTarget mTarget;
     44     private BindingSetterCall mSetterCall;
     45 
     46     public Binding(BindingTarget target, String name, Expr expr) {
     47         this(target, name, expr, null);
     48     }
     49 
     50     public Binding(BindingTarget target, String name, Expr expr, BindingSetterCall setterCall) {
     51         mTarget = target;
     52         mName = name;
     53         mExpr = expr;
     54         mSetterCall = setterCall;
     55     }
     56 
     57     @Override
     58     public List<Location> provideScopeLocation() {
     59         return mExpr.getLocations();
     60     }
     61 
     62     public void resolveListeners() {
     63         final ModelClass listenerParameter = getListenerParameter(mTarget, mName, mExpr.getModel());
     64         Expr listenerExpr = mExpr.resolveListeners(listenerParameter, null);
     65         if (listenerExpr != mExpr) {
     66             listenerExpr.setBindingExpression(true);
     67             mExpr = listenerExpr;
     68         }
     69     }
     70 
     71     public void resolveCallbackParams() {
     72         if (!(mExpr instanceof LambdaExpr)) {
     73             return;
     74         }
     75         LambdaExpr lambdaExpr = (LambdaExpr) mExpr;
     76         final ModelClass listener = getListenerParameter(mTarget, mName, mExpr.getModel());
     77         Preconditions.checkNotNull(listener, ErrorMessages.CANNOT_FIND_SETTER_CALL, mName,
     78                 "lambda", getTarget().getInterfaceType());
     79         //noinspection ConstantConditions
     80         List<ModelMethod> abstractMethods = listener.getAbstractMethods();
     81         int numberOfAbstractMethods = abstractMethods.size();
     82         if (numberOfAbstractMethods != 1) {
     83             L.e(ErrorMessages.CANNOT_FIND_ABSTRACT_METHOD, mName, listener.getCanonicalName(),
     84                     numberOfAbstractMethods, 1);
     85         }
     86         final ModelMethod method = abstractMethods.get(0);
     87         final int argCount = lambdaExpr.getCallbackExprModel().getArgCount();
     88         if (argCount != 0 && argCount != method.getParameterTypes().length) {
     89             L.e(ErrorMessages.CALLBACK_ARGUMENT_COUNT_MISMATCH, listener.getCanonicalName(),
     90                     method.getName(), method.getParameterTypes().length, argCount);
     91         }
     92         lambdaExpr.setup(listener, method, mExpr.getModel().obtainCallbackId());
     93     }
     94 
     95     public void resolveTwoWayExpressions() {
     96         Expr expr = mExpr.resolveTwoWayExpressions(null);
     97         if (expr != mExpr) {
     98             mExpr = expr;
     99         }
    100     }
    101 
    102     private SetterStore.BindingSetterCall getSetterCall() {
    103         if (mSetterCall == null) {
    104             try {
    105                 Scope.enter(getTarget());
    106                 Scope.enter(this);
    107                 resolveSetterCall();
    108                 if (mSetterCall == null) {
    109                     L.e(ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, mExpr.getResolvedType(),
    110                             getTarget().getInterfaceType());
    111                 }
    112             } finally {
    113                 Scope.exit();
    114                 Scope.exit();
    115             }
    116         }
    117         return mSetterCall;
    118     }
    119 
    120     private void resolveSetterCall() {
    121         ModelClass viewType = mTarget.getResolvedType();
    122         if (viewType != null && viewType.extendsViewStub()) {
    123             if (isListenerAttribute(mName)) {
    124                 ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
    125                 ModelClass viewStubProxy = modelAnalyzer.
    126                         findClass("android.databinding.ViewStubProxy", null);
    127                 mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName,
    128                         viewStubProxy, mExpr.getResolvedType(), mExpr.getModel().getImports());
    129             } else if (isViewStubAttribute(mName)) {
    130                 mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr.getResolvedType(),
    131                         mExpr.getModel().getImports());
    132             } else {
    133                 mSetterCall = new ViewStubSetterCall(mName);
    134             }
    135         } else {
    136             final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
    137             mSetterCall = setterStore.getSetterCall(mName,
    138                     viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
    139         }
    140     }
    141 
    142     /**
    143      * Similar to getSetterCall, but assumes an Object parameter to find the best matching listener.
    144      */
    145     private static ModelClass getListenerParameter(BindingTarget target, String name,
    146             ExprModel model) {
    147         ModelClass viewType = target.getResolvedType();
    148         SetterCall setterCall;
    149         ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
    150         ModelClass objectParameter = modelAnalyzer.findClass(Object.class);
    151         SetterStore setterStore = SetterStore.get(modelAnalyzer);
    152         if (viewType != null && viewType.extendsViewStub()) {
    153             if (isListenerAttribute(name)) {
    154                 ModelClass viewStubProxy = modelAnalyzer.
    155                         findClass("android.databinding.ViewStubProxy", null);
    156                 setterCall = SetterStore.get(modelAnalyzer).getSetterCall(name,
    157                         viewStubProxy, objectParameter, model.getImports());
    158             } else if (isViewStubAttribute(name)) {
    159                 setterCall = null; // view stub attrs are not callbacks
    160             } else {
    161                 setterCall = new ViewStubSetterCall(name);
    162             }
    163         } else {
    164             setterCall = setterStore.getSetterCall(name, viewType, objectParameter,
    165                     model.getImports());
    166         }
    167         if (setterCall != null) {
    168             return setterCall.getParameterTypes()[0];
    169         }
    170         List<SetterStore.MultiAttributeSetter> setters =
    171                 setterStore.getMultiAttributeSetterCalls(new String[]{name}, viewType,
    172                 new ModelClass[] {modelAnalyzer.findClass(Object.class)});
    173         if (setters.isEmpty()) {
    174             return null;
    175         } else {
    176             return setters.get(0).getParameterTypes()[0];
    177         }
    178     }
    179 
    180     public BindingTarget getTarget() {
    181         return mTarget;
    182     }
    183 
    184     public String toJavaCode(String targetViewName, String bindingComponent) {
    185         final String currentValue = requiresOldValue()
    186                 ? "this." + LayoutBinderWriterKt.getOldValueName(mExpr) : null;
    187         final String argCode = getExpr().toCode().generate();
    188         return getSetterCall().toJava(bindingComponent, targetViewName, currentValue, argCode);
    189     }
    190 
    191     public String getBindingAdapterInstanceClass() {
    192         return getSetterCall().getBindingAdapterInstanceClass();
    193     }
    194 
    195     public Expr[] getComponentExpressions() {
    196         return new Expr[] { mExpr };
    197     }
    198 
    199     public boolean requiresOldValue() {
    200         return getSetterCall().requiresOldValue();
    201     }
    202 
    203     /**
    204      * The min api level in which this binding should be executed.
    205      * <p>
    206      * This should be the minimum value among the dependencies of this binding. For now, we only
    207      * check the setter.
    208      */
    209     public int getMinApi() {
    210         return getSetterCall().getMinApi();
    211     }
    212 
    213     public String getName() {
    214         return mName;
    215     }
    216 
    217     public Expr getExpr() {
    218         return mExpr;
    219     }
    220 
    221     private static boolean isViewStubAttribute(String name) {
    222         return ("android:inflatedId".equals(name) ||
    223                 "android:layout".equals(name) ||
    224                 "android:visibility".equals(name) ||
    225                 "android:layoutInflater".equals(name));
    226     }
    227 
    228     private static boolean isListenerAttribute(String name) {
    229         return ("android:onInflate".equals(name) ||
    230                 "android:onInflateListener".equals(name));
    231     }
    232 
    233     private static class ViewStubSetterCall extends SetterCall {
    234         private final String mName;
    235 
    236         public ViewStubSetterCall(String name) {
    237             mName = name.substring(name.lastIndexOf(':') + 1);
    238         }
    239 
    240         @Override
    241         protected String toJavaInternal(String componentExpression, String viewExpression,
    242                 String converted) {
    243             return "if (" + viewExpression + ".isInflated()) " + viewExpression +
    244                     ".getBinding().setVariable(BR." + mName + ", " + converted + ")";
    245         }
    246 
    247         @Override
    248         protected String toJavaInternal(String componentExpression, String viewExpression,
    249                 String oldValue, String converted) {
    250             return null;
    251         }
    252 
    253         @Override
    254         public int getMinApi() {
    255             return 0;
    256         }
    257 
    258         @Override
    259         public boolean requiresOldValue() {
    260             return false;
    261         }
    262 
    263         @Override
    264         public ModelClass[] getParameterTypes() {
    265             return new ModelClass[] {
    266                     ModelAnalyzer.getInstance().findClass(Object.class)
    267             };
    268         }
    269 
    270         @Override
    271         public String getBindingAdapterInstanceClass() {
    272             return null;
    273         }
    274     }
    275 
    276     private static class ViewStubDirectCall extends SetterCall {
    277         private final SetterCall mWrappedCall;
    278 
    279         public ViewStubDirectCall(String name, ModelClass viewType, ModelClass resolvedType,
    280                 Map<String, String> imports) {
    281             mWrappedCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(name,
    282                     viewType, resolvedType, imports);
    283             if (mWrappedCall == null) {
    284                 L.e("Cannot find the setter for attribute '%s' on %s with parameter type %s.",
    285                         name, viewType, resolvedType);
    286             }
    287         }
    288 
    289         @Override
    290         protected String toJavaInternal(String componentExpression, String viewExpression,
    291                 String converted) {
    292             return "if (!" + viewExpression + ".isInflated()) " +
    293                     mWrappedCall.toJava(componentExpression, viewExpression + ".getViewStub()",
    294                             null, converted);
    295         }
    296 
    297         @Override
    298         protected String toJavaInternal(String componentExpression, String viewExpression,
    299                 String oldValue, String converted) {
    300             return null;
    301         }
    302 
    303         @Override
    304         public int getMinApi() {
    305             return 0;
    306         }
    307 
    308         @Override
    309         public boolean requiresOldValue() {
    310             return false;
    311         }
    312 
    313         @Override
    314         public ModelClass[] getParameterTypes() {
    315             return new ModelClass[] {
    316                     ModelAnalyzer.getInstance().findClass(Object.class)
    317             };
    318         }
    319 
    320         @Override
    321         public String getBindingAdapterInstanceClass() {
    322             return mWrappedCall.getBindingAdapterInstanceClass();
    323         }
    324     }
    325 }
    326