FieldAccessExpr.java revision 731b74f7f44e67312a1fc4161c4e0aae221b2417
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.expr;
18
19import android.databinding.tool.processing.Scope;
20import android.databinding.tool.reflection.Callable;
21import android.databinding.tool.reflection.Callable.Type;
22import android.databinding.tool.reflection.ModelAnalyzer;
23import android.databinding.tool.reflection.ModelClass;
24import android.databinding.tool.reflection.ModelMethod;
25import android.databinding.tool.util.L;
26
27import java.util.ArrayList;
28import java.util.List;
29
30public class FieldAccessExpr extends Expr {
31    String mName;
32    Callable mGetter;
33    final boolean mIsObservableField;
34    private List<ModelMethod> mListenerMethods;
35    private List<ModelMethod> mCalledMethods;
36    private List<ModelClass> mListenerTypes;
37    private List<ModelMethod> mPotentialListeners;
38
39    FieldAccessExpr(Expr parent, String name) {
40        super(parent);
41        mName = name;
42        mIsObservableField = false;
43    }
44
45    FieldAccessExpr(Expr parent, String name, boolean isObservableField) {
46        super(parent);
47        mName = name;
48        mIsObservableField = isObservableField;
49    }
50
51    public Expr getChild() {
52        return getChildren().get(0);
53    }
54
55    public Callable getGetter() {
56        if (mGetter == null) {
57            getResolvedType();
58        }
59        return mGetter;
60    }
61
62    public List<ModelMethod> getListenerMethods() {
63        return mListenerMethods;
64    }
65
66    public List<ModelMethod> getCalledMethods() {
67        return mCalledMethods;
68    }
69
70    public List<ModelClass> getListenerTypes() { return mListenerTypes; }
71
72    public boolean isListener() {
73        return mListenerMethods != null && !mListenerMethods.isEmpty();
74    }
75
76    public int getMinApi() {
77        if (isListener()) {
78            int minApi = 1;
79            for (ModelClass listener : mListenerTypes) {
80                int listenerApi = listener.getMinApi();
81                minApi = Math.max(minApi, listenerApi);
82            }
83            return minApi;
84        }
85        return mGetter.getMinApi();
86    }
87
88    @Override
89    public boolean isDynamic() {
90        if (mGetter == null) {
91            getResolvedType();
92        }
93        if (mGetter == null || mGetter.type == Type.METHOD) {
94            return !isListener();
95        }
96        // if it is static final, gone
97        if (getChild().isDynamic()) {
98            // if owner is dynamic, then we can be dynamic unless we are static final
99            return !mGetter.isStatic() || mGetter.isDynamic();
100        }
101
102        // if owner is NOT dynamic, we can be dynamic if an only if getter is dynamic
103        return mGetter.isDynamic();
104    }
105
106    public boolean hasBindableAnnotations() {
107        return mGetter.canBeInvalidated();
108    }
109
110    @Override
111    public boolean resolveListeners(ModelClass listener) {
112        if (mPotentialListeners == null) {
113            return false;
114        }
115
116        List<ModelMethod> abstractMethods = listener.getAbstractMethods();
117        int numberOfAbstractMethods = abstractMethods == null ? 0 : abstractMethods.size();
118        if (numberOfAbstractMethods != 1) {
119            if (mGetter == null) {
120                L.e("Could not find accessor %s.%s and %s has %d abstract methods, so is" +
121                                " not resolved as a listener",
122                        getChild().getResolvedType().getCanonicalName(), mName,
123                        listener.getCanonicalName(), numberOfAbstractMethods);
124            }
125            return false;
126        }
127
128        // See if we've already resolved this listener type
129        if (mListenerMethods == null) {
130            mListenerMethods = new ArrayList<ModelMethod>();
131            mCalledMethods = new ArrayList<ModelMethod>();
132            mListenerTypes = new ArrayList<ModelClass>();
133        } else {
134            for (ModelClass previousListeners : mListenerTypes) {
135                if (previousListeners.equals(listener)) {
136                    return false;
137                }
138            }
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        for (ModelMethod method : mPotentialListeners) {
145            if (acceptsParameters(method, listenerParameters)) {
146                mListenerTypes.add(listener);
147                mListenerMethods.add(listenerMethod);
148                mCalledMethods.add(method);
149                resetResolvedType();
150                return true;
151            }
152        }
153
154        if (mGetter == null) {
155            L.e("Listener class %s with method %s did not match signature of any method %s.%s",
156                    listener.getCanonicalName(), listenerMethod.getName(),
157                    getChild().getResolvedType().getCanonicalName(), mName);
158        }
159        return false;
160    }
161
162    private boolean acceptsParameters(ModelMethod method, ModelClass[] listenerParameters) {
163        ModelClass[] parameters = method.getParameterTypes();
164        if (parameters.length != listenerParameters.length) {
165            return false;
166        }
167        for (int i = 0; i < parameters.length; i++) {
168            if (!parameters[i].isAssignableFrom(listenerParameters[i])) {
169                return false;
170            }
171        }
172        return true;
173    }
174
175    @Override
176    protected List<Dependency> constructDependencies() {
177        final List<Dependency> dependencies = constructDynamicChildrenDependencies();
178        for (Dependency dependency : dependencies) {
179            if (dependency.getOther() == getChild()) {
180                dependency.setMandatory(true);
181            }
182        }
183        return dependencies;
184    }
185
186    @Override
187    protected String computeUniqueKey() {
188        if (mIsObservableField) {
189            return join(mName, "..", super.computeUniqueKey());
190        }
191        return join(mName, ".", super.computeUniqueKey());
192    }
193
194    public String getName() {
195        return mName;
196    }
197
198    @Override
199    public void updateExpr(ModelAnalyzer modelAnalyzer) {
200        try {
201            Scope.enter(this);
202            resolveType(modelAnalyzer);
203            super.updateExpr(modelAnalyzer);
204        } finally {
205            Scope.exit();
206        }
207    }
208
209    @Override
210    protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
211        if (mGetter == null) {
212            if (mPotentialListeners != null) {
213                return modelAnalyzer.findClass(Object.class);
214            }
215            Expr child = getChild();
216            child.getResolvedType();
217            boolean isStatic = child instanceof StaticIdentifierExpr;
218            ModelClass resolvedType = child.getResolvedType();
219            L.d("resolving %s. Resolved class type: %s", this, resolvedType);
220
221            mGetter = resolvedType.findGetterOrField(mName, isStatic);
222            mPotentialListeners = resolvedType.findMethods(mName, isStatic);
223
224            if (mGetter == null) {
225                if (mPotentialListeners == null) {
226                    L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName);
227                }
228                return modelAnalyzer.findClass(Object.class);
229            }
230
231            if (mGetter.isStatic() && !isStatic) {
232                // found a static method on an instance. register a new one
233                child.getParents().remove(this);
234                getChildren().remove(child);
235                StaticIdentifierExpr staticId = getModel().staticIdentifierFor(resolvedType);
236                getChildren().add(staticId);
237                staticId.getParents().add(this);
238                child = getChild(); // replace the child for the next if stmt
239            }
240
241            if (mGetter.resolvedType.isObservableField()) {
242                // Make this the ".get()" and add an extra field access for the observable field
243                child.getParents().remove(this);
244                getChildren().remove(child);
245
246                FieldAccessExpr observableField = getModel().observableField(child, mName);
247                observableField.mGetter = mGetter;
248
249                getChildren().add(observableField);
250                observableField.getParents().add(this);
251                mGetter = mGetter.resolvedType.findGetterOrField("get", false);
252                mName = "";
253            }
254        }
255        if (isListener()) {
256            return modelAnalyzer.findClass(Object.class);
257        }
258        return mGetter.resolvedType;
259    }
260
261    @Override
262    protected String asPackage() {
263        String parentPackage = getChild().asPackage();
264        return parentPackage == null ? null : parentPackage + "." + mName;
265    }
266}
267