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.processing.Scope; 20 import android.databinding.tool.reflection.Callable; 21 import android.databinding.tool.reflection.Callable.Type; 22 import android.databinding.tool.reflection.ModelAnalyzer; 23 import android.databinding.tool.reflection.ModelClass; 24 import android.databinding.tool.reflection.ModelMethod; 25 import android.databinding.tool.solver.ExecutionPath; 26 import android.databinding.tool.util.L; 27 import android.databinding.tool.writer.KCode; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.List; 32 33 import static android.databinding.tool.reflection.Callable.DYNAMIC; 34 import static android.databinding.tool.reflection.Callable.STATIC; 35 36 37 public class MethodCallExpr extends Expr { 38 final String mName; 39 Callable mGetter; 40 // Allow protected calls -- only used for ViewDataBinding methods. 41 private boolean mAllowProtected; 42 43 static List<Expr> concat(Expr e, List<Expr> list) { 44 List<Expr> merged = new ArrayList<Expr>(); 45 merged.add(e); 46 merged.addAll(list); 47 return merged; 48 } 49 50 MethodCallExpr(Expr target, String name, List<Expr> args) { 51 super(concat(target, args)); 52 mName = name; 53 } 54 55 @SuppressWarnings("Duplicates") 56 @Override 57 public void updateExpr(ModelAnalyzer modelAnalyzer) { 58 try { 59 Scope.enter(this); 60 resolveType(modelAnalyzer); 61 super.updateExpr(modelAnalyzer); 62 } finally { 63 Scope.exit(); 64 } 65 } 66 67 @Override 68 protected KCode generateCode() { 69 KCode code = new KCode() 70 .app("", getTarget().toCode()) 71 .app(".") 72 .app(getGetter().name) 73 .app("("); 74 appendArgs(code); 75 code.app(")"); 76 return code; 77 } 78 79 @Override 80 public Expr cloneToModel(ExprModel model) { 81 return model.methodCall(getTarget().cloneToModel(model), mName, 82 cloneToModel(model, getArgs())); 83 } 84 85 private void appendArgs(KCode code) { 86 boolean first = true; 87 for (Expr arg : getArgs()) { 88 if (first) { 89 first = false; 90 } else { 91 code.app(", "); 92 } 93 code.app("", arg.toCode()); 94 } 95 } 96 97 @Override 98 public List<ExecutionPath> toExecutionPath(List<ExecutionPath> paths) { 99 final List<ExecutionPath> targetPaths = getTarget().toExecutionPath(paths); 100 // after this, we need a null check. 101 List<ExecutionPath> result = new ArrayList<ExecutionPath>(); 102 if (getTarget() instanceof StaticIdentifierExpr) { 103 result.addAll(toExecutionPathInOrder(paths, getArgs())); 104 } else { 105 for (ExecutionPath path : targetPaths) { 106 Expr cmp = getModel() 107 .comparison("!=", getTarget(), getModel().symbol("null", Object.class)); 108 path.addPath(cmp); 109 final ExecutionPath subPath = path.addBranch(cmp, true); 110 if (subPath != null) { 111 result.addAll(toExecutionPathInOrder(subPath, getArgs())); 112 } 113 } 114 } 115 return result; 116 } 117 118 private List<ExecutionPath> toExecutionPathInOrder(ExecutionPath path, List<Expr> args) { 119 return toExecutionPathInOrder(Arrays.asList(path), args); 120 } 121 122 @Override 123 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 124 if (mGetter == null) { 125 List<ModelClass> args = new ArrayList<ModelClass>(); 126 for (Expr expr : getArgs()) { 127 args.add(expr.getResolvedType()); 128 } 129 130 Expr target = getTarget(); 131 boolean isStatic = target instanceof StaticIdentifierExpr; 132 ModelMethod method = target.getResolvedType().getMethod(mName, args, isStatic, 133 mAllowProtected); 134 if (method == null) { 135 StringBuilder argTypes = new StringBuilder(); 136 for (ModelClass arg : args) { 137 if (argTypes.length() != 0) { 138 argTypes.append(", "); 139 } 140 argTypes.append(arg.toJavaCode()); 141 } 142 String message = "cannot find method '" + mName + "(" + argTypes + ")' in class " + 143 target.getResolvedType().toJavaCode(); 144 IllegalArgumentException e = new IllegalArgumentException(message); 145 L.e(e, "cannot find method %s(%s) in class %s", mName, argTypes, 146 target.getResolvedType().toJavaCode()); 147 throw e; 148 } 149 if (!isStatic && method.isStatic()) { 150 // found a static method on an instance. Use class instead 151 target.getParents().remove(this); 152 getChildren().remove(target); 153 StaticIdentifierExpr staticId = getModel() 154 .staticIdentifierFor(target.getResolvedType()); 155 getChildren().add(staticId); 156 staticId.getParents().add(this); 157 // make sure we update this in case we access it below 158 target = getTarget(); 159 } 160 int flags = DYNAMIC; 161 if (method.isStatic()) { 162 flags |= STATIC; 163 } 164 mGetter = new Callable(Type.METHOD, method.getName(), null, method.getReturnType(args), 165 method.getParameterTypes().length, flags, method); 166 } 167 return mGetter.resolvedType; 168 } 169 170 @Override 171 protected List<Dependency> constructDependencies() { 172 final List<Dependency> dependencies = constructDynamicChildrenDependencies(); 173 for (Dependency dependency : dependencies) { 174 if (dependency.getOther() == getTarget()) { 175 dependency.setMandatory(true); 176 } 177 } 178 return dependencies; 179 } 180 181 @Override 182 protected String computeUniqueKey() { 183 return join(getTarget().computeUniqueKey(), mName, 184 super.computeUniqueKey()); 185 } 186 187 public Expr getTarget() { 188 return getChildren().get(0); 189 } 190 191 public String getName() { 192 return mName; 193 } 194 195 public List<Expr> getArgs() { 196 return getChildren().subList(1, getChildren().size()); 197 } 198 199 public Callable getGetter() { 200 return mGetter; 201 } 202 203 public void setAllowProtected() { 204 mAllowProtected = true; 205 } 206 207 @Override 208 public String getInvertibleError() { 209 return "Method calls may not be used in two-way expressions"; 210 } 211 212 @Override 213 public String toString() { 214 StringBuilder buf = new StringBuilder(); 215 buf.append(getTarget()) 216 .append('.') 217 .append(mName) 218 .append('('); 219 final List<Expr> args = getArgs(); 220 for (int i = 0; i < args.size(); i++) { 221 Expr arg = args.get(i); 222 if (i != 0) { 223 buf.append(", "); 224 } 225 buf.append(arg); 226 } 227 buf.append(')'); 228 return buf.toString(); 229 } 230 } 231