• 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.Binding;
20 import android.databinding.tool.BindingTarget;
21 import android.databinding.tool.InverseBinding;
22 import android.databinding.tool.ext.ExtKt;
23 import android.databinding.tool.processing.ErrorMessages;
24 import android.databinding.tool.processing.Scope;
25 import android.databinding.tool.reflection.Callable;
26 import android.databinding.tool.reflection.Callable.Type;
27 import android.databinding.tool.reflection.ModelAnalyzer;
28 import android.databinding.tool.reflection.ModelClass;
29 import android.databinding.tool.store.SetterStore;
30 import android.databinding.tool.store.SetterStore.BindingGetterCall;
31 import android.databinding.tool.util.BrNameUtil;
32 import android.databinding.tool.util.L;
33 import android.databinding.tool.util.Preconditions;
34 import android.databinding.tool.writer.KCode;
35 
36 import com.google.common.collect.Lists;
37 
38 import java.util.List;
39 
40 public class FieldAccessExpr extends MethodBaseExpr {
41     // notification name for the field. Important when we map this to a method w/ different name
42     String mBrName;
43     Callable mGetter;
44     boolean mIsListener;
45     boolean mIsViewAttributeAccess;
46 
FieldAccessExpr(Expr parent, String name)47     FieldAccessExpr(Expr parent, String name) {
48         super(parent, name);
49         mName = name;
50     }
51 
getGetter()52     public Callable getGetter() {
53         if (mGetter == null) {
54             getResolvedType();
55         }
56         return mGetter;
57     }
58 
59     @Override
getInvertibleError()60     public String getInvertibleError() {
61         if (getGetter() == null) {
62             return "Listeners do not support two-way binding";
63         }
64         if (mGetter.setterName == null) {
65             return "Two-way binding cannot resolve a setter for " + getResolvedType().toJavaCode() +
66                     " property '" + mName + "'";
67         }
68         if (!mGetter.isDynamic()) {
69             return "Cannot change a final field in " + getResolvedType().toJavaCode() +
70                     " property " + mName;
71         }
72         return null;
73     }
74 
getMinApi()75     public int getMinApi() {
76         return mGetter == null ? 0 : mGetter.getMinApi();
77     }
78 
79     @Override
isDynamic()80     public boolean isDynamic() {
81         if (mGetter == null) {
82             getResolvedType();
83         }
84         if (mGetter == null || mGetter.type == Type.METHOD) {
85             return true;
86         }
87         // if it is static final, gone
88         if (getTarget().isDynamic()) {
89             // if owner is dynamic, then we can be dynamic unless we are static final
90             return !mGetter.isStatic() || mGetter.isDynamic();
91         }
92 
93         if (mIsViewAttributeAccess) {
94             return true; // must be able to invalidate this
95         }
96 
97         // if owner is NOT dynamic, we can be dynamic if an only if getter is dynamic
98         return mGetter.isDynamic();
99     }
100 
hasBindableAnnotations()101     public boolean hasBindableAnnotations() {
102         return mGetter != null && mGetter.canBeInvalidated();
103     }
104 
105     @Override
resolveListeners(ModelClass listener, Expr parent)106     public Expr resolveListeners(ModelClass listener, Expr parent) {
107         final ModelClass targetType = getTarget().getResolvedType();
108         if (getGetter() == null && (listener == null || !mIsListener)) {
109             L.e("Could not resolve %s.%s as an accessor or listener on the attribute.",
110                     targetType.getCanonicalName(), mName);
111             return this;
112         }
113         try {
114             Expr listenerExpr = resolveListenersAsMethodReference(listener, parent);
115             L.w("Method references using '.' is deprecated. Instead of '%s', use '%s::%s'",
116                     toString(), getTarget(), getName());
117             return listenerExpr;
118         } catch (IllegalStateException e) {
119             if (getGetter() == null) {
120                 L.e("%s", e.getMessage());
121             }
122             return this;
123         }
124     }
125 
126     @Override
computeUniqueKey()127     protected String computeUniqueKey() {
128         return join(mName, ".", getTarget().getUniqueKey());
129     }
130 
getBrName()131     public String getBrName() {
132         if (mIsListener) {
133             return null;
134         }
135         try {
136             Scope.enter(this);
137             Preconditions.checkNotNull(mGetter, "cannot get br name before resolving the getter");
138             return mBrName;
139         } finally {
140             Scope.exit();
141         }
142     }
143 
144     @Override
resolveType(ModelAnalyzer modelAnalyzer)145     protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
146         if (mIsListener) {
147             return modelAnalyzer.findClass(Object.class);
148         }
149         if (mGetter == null) {
150             Expr target = getTarget();
151             target.getResolvedType();
152             boolean isStatic = target instanceof StaticIdentifierExpr;
153             ModelClass resolvedType = target.getResolvedType();
154             L.d("resolving %s. Resolved class type: %s", this, resolvedType);
155 
156             mGetter = resolvedType.findGetterOrField(mName, isStatic);
157 
158             if (mGetter == null) {
159                 mIsListener = !resolvedType.findMethods(mName, isStatic).isEmpty();
160                 if (!mIsListener) {
161                     L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName);
162                 }
163                 return modelAnalyzer.findClass(Object.class);
164             }
165 
166             if (mGetter.isStatic() && !isStatic) {
167                 // found a static method on an instance. register a new one
168                 replaceStaticIdentifier(resolvedType);
169                 target = getTarget();
170             }
171 
172             if (mGetter.resolvedType.isObservableField()) {
173                 // Make this the ".get()" and add an extra field access for the observable field
174                 target.getParents().remove(this);
175                 getChildren().remove(target);
176 
177                 FieldAccessExpr observableField = getModel().observableField(target, mName);
178                 getChildren().add(observableField);
179                 observableField.getParents().add(this);
180                 mGetter = mGetter.resolvedType.findGetterOrField("", false);
181                 mName = "";
182                 mBrName = ExtKt.br(mName);
183             } else if (hasBindableAnnotations()) {
184                 mBrName = ExtKt.br(BrNameUtil.brKey(mGetter));
185             }
186         }
187         return mGetter.resolvedType;
188     }
189 
replaceStaticIdentifier(ModelClass staticIdentifierType)190     protected void replaceStaticIdentifier(ModelClass staticIdentifierType) {
191         getTarget().getParents().remove(this);
192         getChildren().remove(getTarget());
193         StaticIdentifierExpr staticId = getModel().staticIdentifierFor(staticIdentifierType);
194         getChildren().add(staticId);
195         staticId.getParents().add(this);
196     }
197 
198     @Override
resolveTwoWayExpressions(Expr parent)199     public Expr resolveTwoWayExpressions(Expr parent) {
200         final Expr child = getTarget();
201         if (!(child instanceof ViewFieldExpr)) {
202             return this;
203         }
204         final ViewFieldExpr expr = (ViewFieldExpr) child;
205         final BindingTarget bindingTarget = expr.getBindingTarget();
206 
207         // This is a binding to a View's attribute, so look for matching attribute
208         // on that View's BindingTarget. If there is an expression, we simply replace
209         // the binding with that binding expression.
210         for (Binding binding : bindingTarget.getBindings()) {
211             if (attributeMatchesName(binding.getName(), mName)) {
212                 final Expr replacement = binding.getExpr();
213                 replaceExpression(parent, replacement);
214                 return replacement;
215             }
216         }
217 
218         // There was no binding expression to bind to. This should be a two-way binding.
219         // This is a synthesized two-way binding because we must capture the events from
220         // the View and change the value when the target View's attribute changes.
221         final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
222         final ModelClass targetClass = expr.getResolvedType();
223         BindingGetterCall getter = setterStore.getGetterCall(mName, targetClass, null, null);
224         if (getter == null) {
225             getter = setterStore.getGetterCall("android:" + mName, targetClass, null, null);
226             if (getter == null) {
227                 L.e("Could not resolve the two-way binding attribute '%s' on type '%s'",
228                         mName, targetClass);
229             }
230         }
231         InverseBinding inverseBinding = null;
232         for (Binding binding : bindingTarget.getBindings()) {
233             final Expr testExpr = binding.getExpr();
234             if (testExpr instanceof TwoWayListenerExpr &&
235                     getter.getEventAttribute().equals(binding.getName())) {
236                 inverseBinding = ((TwoWayListenerExpr) testExpr).mInverseBinding;
237                 break;
238             }
239         }
240         if (inverseBinding == null) {
241             inverseBinding = bindingTarget.addInverseBinding(mName, getter);
242         }
243         inverseBinding.addChainedExpression(this);
244         mIsViewAttributeAccess = true;
245         enableDirectInvalidation();
246         return this;
247     }
248 
attributeMatchesName(String attribute, String field)249     private static boolean attributeMatchesName(String attribute, String field) {
250         int colonIndex = attribute.indexOf(':');
251         return attribute.substring(colonIndex + 1).equals(field);
252     }
253 
replaceExpression(Expr parent, Expr replacement)254     private void replaceExpression(Expr parent, Expr replacement) {
255         if (parent != null) {
256             List<Expr> children = parent.getChildren();
257             int index;
258             while ((index = children.indexOf(this)) >= 0) {
259                 children.set(index, replacement);
260                 replacement.getParents().add(parent);
261             }
262             while (getParents().remove(parent)) {
263                 // just remove all copies of parent.
264             }
265         }
266         if (getParents().isEmpty()) {
267             getModel().removeExpr(this);
268         }
269     }
270 
271     @Override
asPackage()272     protected String asPackage() {
273         String parentPackage = getTarget().asPackage();
274         return parentPackage == null ? null : parentPackage + "." + mName;
275     }
276 
277     @Override
generateCode()278     protected KCode generateCode() {
279         // once we can deprecate using Field.access for callbacks, we can get rid of this since
280         // it will be detected when resolve type is run.
281         Preconditions.checkNotNull(getGetter(), ErrorMessages.CANNOT_RESOLVE_TYPE, this);
282         KCode code = new KCode()
283                 .app("", getTarget().toCode()).app(".");
284         if (getGetter().type == Callable.Type.FIELD) {
285             return code.app(getGetter().name);
286         } else {
287             return code.app(getGetter().name).app("()");
288         }
289     }
290 
291     @Override
generateInverse(ExprModel model, Expr value, String bindingClassName)292     public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) {
293         Expr castExpr = model.castExpr(getResolvedType().toJavaCode(), value);
294         Expr target = getTarget().cloneToModel(model);
295         Expr result;
296         if (getGetter().type == Callable.Type.FIELD) {
297             result = model.assignment(target, mName, castExpr);
298         } else {
299             result = model.methodCall(target, mGetter.setterName, Lists.newArrayList(castExpr));
300         }
301         return result;
302     }
303 
304     @Override
cloneToModel(ExprModel model)305     public Expr cloneToModel(ExprModel model) {
306         final Expr clonedTarget = getTarget().cloneToModel(model);
307         return model.field(clonedTarget, mName);
308     }
309 
310     @Override
toString()311     public String toString() {
312         String name = mName.isEmpty() ? "get()" : mName;
313         return getTarget().toString() + '.' + name;
314     }
315 }
316