Home | History | Annotate | Download | only in expr
      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