1c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount/*
2c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * Copyright (C) 2016 The Android Open Source Project
3c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount *
4c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * Licensed under the Apache License, Version 2.0 (the "License");
5c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * you may not use this file except in compliance with the License.
6c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * You may obtain a copy of the License at
7c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount *
8c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount *      http://www.apache.org/licenses/LICENSE-2.0
9c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount *
10c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * Unless required by applicable law or agreed to in writing, software
11c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * distributed under the License is distributed on an "AS IS" BASIS,
12c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * See the License for the specific language governing permissions and
14c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount * limitations under the License.
15c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount */
16c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
17c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountpackage android.databinding.tool.expr;
18c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
19c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport android.databinding.tool.processing.Scope;
20c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport android.databinding.tool.reflection.ModelAnalyzer;
21c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport android.databinding.tool.reflection.ModelClass;
22c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport android.databinding.tool.reflection.ModelMethod;
23c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport android.databinding.tool.solver.ExecutionPath;
24c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
25c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport java.util.ArrayList;
26c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountimport java.util.List;
27c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
28c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mountpublic abstract class MethodBaseExpr extends Expr {
29c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    String mName;
30c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
31c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    MethodBaseExpr(Expr parent, String name) {
32c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        super(parent);
33c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        mName = name;
34c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
35c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
36c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    public Expr getTarget() {
37c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        return getChildren().get(0);
38c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
39c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
40c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    @Override
41c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    public List<ExecutionPath> toExecutionPath(List<ExecutionPath> paths) {
42c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        final List<ExecutionPath> targetPaths = getTarget().toExecutionPath(paths);
43c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        // after this, we need a null check.
44c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        List<ExecutionPath> result = new ArrayList<ExecutionPath>();
45c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        if (getTarget() instanceof StaticIdentifierExpr) {
46c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            result.addAll(toExecutionPathInOrder(paths, getTarget()));
47c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        } else {
48c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            for (ExecutionPath path : targetPaths) {
49c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                final ComparisonExpr cmp = getModel()
50c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                        .comparison("!=", getTarget(), getModel().symbol("null", Object.class));
51c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                path.addPath(cmp);
52c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                final ExecutionPath subPath = path.addBranch(cmp, true);
53c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                if (subPath != null) {
54c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    subPath.addPath(this);
55c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    result.add(subPath);
56c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                }
57c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            }
58c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
59c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        return result;
60c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
61c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
62c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    protected Expr resolveListenersAsMethodReference(ModelClass listener, Expr parent) {
63c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        final Expr target = getTarget();
64c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        final ModelClass childType = target.getResolvedType();
65c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        if (listener == null) {
66c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            throw new IllegalStateException(
67c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    String.format("Could not resolve %s as a listener.", this));
68c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
69c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
70c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        List<ModelMethod> abstractMethods = listener.getAbstractMethods();
71c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        int numberOfAbstractMethods = abstractMethods == null ? 0 : abstractMethods.size();
72c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        if (numberOfAbstractMethods != 1) {
73c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            throw new IllegalStateException(String.format(
74c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    "Could not find accessor %s.%s and %s has %d abstract methods, so is" +
75c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                            " not resolved as a listener",
76c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    childType.getCanonicalName(), mName,
77c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    listener.getCanonicalName(), numberOfAbstractMethods));
78c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
79c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
80c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        // Look for a signature matching the abstract method
81c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        final ModelMethod listenerMethod = abstractMethods.get(0);
82c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        final ModelClass[] listenerParameters = listenerMethod.getParameterTypes();
83c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        boolean isStatic = getTarget() instanceof StaticIdentifierExpr;
84c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        List<ModelMethod> methods = childType.findMethods(mName, isStatic);
85c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        for (ModelMethod method : methods) {
86c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            if (acceptsParameters(method, listenerParameters) &&
87c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    method.getReturnType(null).equals(listenerMethod.getReturnType(null))) {
88c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                target.getParents().remove(this);
89c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                resetResolvedType();
90c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                // replace this with ListenerExpr in parent
91c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                Expr listenerExpr = getModel().listenerExpr(getTarget(), mName, listener,
92c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                        listenerMethod);
93c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                if (parent != null) {
94c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    int index;
95c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    while ((index = parent.getChildren().indexOf(this)) != -1) {
96c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                        parent.getChildren().set(index, listenerExpr);
97c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    }
98c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                }
99c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                if (getModel().mBindingExpressions.contains(this)) {
100c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    getModel().bindingExpr(listenerExpr);
101c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                }
102c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                getParents().remove(parent);
103c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                if (getParents().isEmpty()) {
104c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                    getModel().removeExpr(this);
105c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                }
106c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                return listenerExpr;
107c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            }
108c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
109c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
110c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        throw new IllegalStateException(String.format(
111c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                "Listener class %s with method %s did not match signature of any method %s",
112c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                listener.getCanonicalName(), listenerMethod.getName(), this));
113c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
114c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
115c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    private boolean acceptsParameters(ModelMethod method, ModelClass[] listenerParameters) {
116c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        ModelClass[] parameters = method.getParameterTypes();
117c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        if (parameters.length != listenerParameters.length) {
118c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            return false;
119c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
120c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        for (int i = 0; i < parameters.length; i++) {
121c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            if (!parameters[i].isAssignableFrom(listenerParameters[i])) {
122c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                return false;
123c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            }
124c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
125c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        return true;
126c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
127c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
128c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    @Override
129c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    protected List<Dependency> constructDependencies() {
130c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        final List<Dependency> dependencies = constructDynamicChildrenDependencies();
131c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        for (Dependency dependency : dependencies) {
132c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            if (dependency.getOther() == getTarget()) {
133c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount                dependency.setMandatory(true);
134c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            }
135c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
136c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        return dependencies;
137c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
138c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
139c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    public String getName() {
140c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        return mName;
141c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
142c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount
143c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    @Override
144c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    public void updateExpr(ModelAnalyzer modelAnalyzer) {
145c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        try {
146c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            Scope.enter(this);
147c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            resolveType(modelAnalyzer);
148c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            super.updateExpr(modelAnalyzer);
149c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        } finally {
150c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount            Scope.exit();
151c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount        }
152c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount    }
153c0c1dab0b6254e4d27f18c37a72a9e7952e958a0George Mount}
154