• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FieldAccessExpr(Expr parent, String name)47     FieldAccessExpr(Expr parent, String name) {
48         super(parent);
49         mName = name;
50         mIsObservableField = false;
51     }
52 
FieldAccessExpr(Expr parent, String name, boolean isObservableField)53     FieldAccessExpr(Expr parent, String name, boolean isObservableField) {
54         super(parent);
55         mName = name;
56         mIsObservableField = isObservableField;
57     }
58 
getChild()59     public Expr getChild() {
60         return getChildren().get(0);
61     }
62 
getGetter()63     public Callable getGetter() {
64         if (mGetter == null) {
65             getResolvedType();
66         }
67         return mGetter;
68     }
69 
70     @Override
getInvertibleError()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 
getMinApi()83     public int getMinApi() {
84         return mGetter.getMinApi();
85     }
86 
87     @Override
isDynamic()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 
hasBindableAnnotations()109     public boolean hasBindableAnnotations() {
110         return mGetter.canBeInvalidated();
111     }
112 
113     @Override
resolveListeners(ModelClass listener, Expr parent)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 
acceptsParameters(ModelMethod method, ModelClass[] listenerParameters)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
constructDependencies()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
computeUniqueKey()206     protected String computeUniqueKey() {
207         if (mIsObservableField) {
208             return addTwoWay(join(mName, "..", super.computeUniqueKey()));
209         }
210         return addTwoWay(join(mName, ".", super.computeUniqueKey()));
211     }
212 
getName()213     public String getName() {
214         return mName;
215     }
216 
getBrName()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
updateExpr(ModelAnalyzer modelAnalyzer)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
resolveType(ModelAnalyzer modelAnalyzer)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
resolveTwoWayExpressions(Expr parent)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 
attributeMatchesName(String attribute, String field)344     private static boolean attributeMatchesName(String attribute, String field) {
345         int colonIndex = attribute.indexOf(':');
346         return attribute.substring(colonIndex + 1).equals(field);
347     }
348 
replaceExpression(Expr parent, Expr replacement)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
asPackage()367     protected String asPackage() {
368         String parentPackage = getChild().asPackage();
369         return parentPackage == null ? null : parentPackage + "." + mName;
370     }
371 
372     @Override
generateCode(boolean expand)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
toInverseCode(KCode value)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