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