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