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