1 /*
2  * Copyright (C) 2016 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.processing.Scope;
20 import android.databinding.tool.reflection.ModelAnalyzer;
21 import android.databinding.tool.reflection.ModelClass;
22 import android.databinding.tool.reflection.ModelMethod;
23 import android.databinding.tool.solver.ExecutionPath;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 public abstract class MethodBaseExpr extends Expr {
29     String mName;
30 
MethodBaseExpr(Expr parent, String name)31     MethodBaseExpr(Expr parent, String name) {
32         super(parent);
33         mName = name;
34     }
35 
getTarget()36     public Expr getTarget() {
37         return getChildren().get(0);
38     }
39 
40     @Override
toExecutionPath(List<ExecutionPath> paths)41     public List<ExecutionPath> toExecutionPath(List<ExecutionPath> paths) {
42         final List<ExecutionPath> targetPaths = getTarget().toExecutionPath(paths);
43         // after this, we need a null check.
44         List<ExecutionPath> result = new ArrayList<ExecutionPath>();
45         if (getTarget() instanceof StaticIdentifierExpr) {
46             result.addAll(toExecutionPathInOrder(paths, getTarget()));
47         } else {
48             for (ExecutionPath path : targetPaths) {
49                 final ComparisonExpr cmp = getModel()
50                         .comparison("!=", getTarget(), getModel().symbol("null", Object.class));
51                 path.addPath(cmp);
52                 final ExecutionPath subPath = path.addBranch(cmp, true);
53                 if (subPath != null) {
54                     subPath.addPath(this);
55                     result.add(subPath);
56                 }
57             }
58         }
59         return result;
60     }
61 
resolveListenersAsMethodReference(ModelClass listener, Expr parent)62     protected Expr resolveListenersAsMethodReference(ModelClass listener, Expr parent) {
63         final Expr target = getTarget();
64         final ModelClass childType = target.getResolvedType();
65         if (listener == null) {
66             throw new IllegalStateException(
67                     String.format("Could not resolve %s as a listener.", this));
68         }
69 
70         List<ModelMethod> abstractMethods = listener.getAbstractMethods();
71         int numberOfAbstractMethods = abstractMethods == null ? 0 : abstractMethods.size();
72         if (numberOfAbstractMethods != 1) {
73             throw new IllegalStateException(String.format(
74                     "Could not find accessor %s.%s and %s has %d abstract methods, so is" +
75                             " not resolved as a listener",
76                     childType.getCanonicalName(), mName,
77                     listener.getCanonicalName(), numberOfAbstractMethods));
78         }
79 
80         // Look for a signature matching the abstract method
81         final ModelMethod listenerMethod = abstractMethods.get(0);
82         final ModelClass[] listenerParameters = listenerMethod.getParameterTypes();
83         boolean isStatic = getTarget() instanceof StaticIdentifierExpr;
84         List<ModelMethod> methods = childType.findMethods(mName, isStatic);
85         for (ModelMethod method : methods) {
86             if (acceptsParameters(method, listenerParameters) &&
87                     method.getReturnType(null).equals(listenerMethod.getReturnType(null))) {
88                 target.getParents().remove(this);
89                 resetResolvedType();
90                 // replace this with ListenerExpr in parent
91                 Expr listenerExpr = getModel().listenerExpr(getTarget(), mName, listener,
92                         listenerMethod);
93                 if (parent != null) {
94                     int index;
95                     while ((index = parent.getChildren().indexOf(this)) != -1) {
96                         parent.getChildren().set(index, listenerExpr);
97                     }
98                 }
99                 if (getModel().mBindingExpressions.contains(this)) {
100                     getModel().bindingExpr(listenerExpr);
101                 }
102                 getParents().remove(parent);
103                 if (getParents().isEmpty()) {
104                     getModel().removeExpr(this);
105                 }
106                 return listenerExpr;
107             }
108         }
109 
110         throw new IllegalStateException(String.format(
111                 "Listener class %s with method %s did not match signature of any method %s",
112                 listener.getCanonicalName(), listenerMethod.getName(), this));
113     }
114 
acceptsParameters(ModelMethod method, ModelClass[] listenerParameters)115     private boolean acceptsParameters(ModelMethod method, ModelClass[] listenerParameters) {
116         ModelClass[] parameters = method.getParameterTypes();
117         if (parameters.length != listenerParameters.length) {
118             return false;
119         }
120         for (int i = 0; i < parameters.length; i++) {
121             if (!parameters[i].isAssignableFrom(listenerParameters[i])) {
122                 return false;
123             }
124         }
125         return true;
126     }
127 
128     @Override
constructDependencies()129     protected List<Dependency> constructDependencies() {
130         final List<Dependency> dependencies = constructDynamicChildrenDependencies();
131         for (Dependency dependency : dependencies) {
132             if (dependency.getOther() == getTarget()) {
133                 dependency.setMandatory(true);
134             }
135         }
136         return dependencies;
137     }
138 
getName()139     public String getName() {
140         return mName;
141     }
142 
143     @Override
updateExpr(ModelAnalyzer modelAnalyzer)144     public void updateExpr(ModelAnalyzer modelAnalyzer) {
145         try {
146             Scope.enter(this);
147             resolveType(modelAnalyzer);
148             super.updateExpr(modelAnalyzer);
149         } finally {
150             Scope.exit();
151         }
152     }
153 }
154