summaryrefslogtreecommitdiff
path: root/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
blob: 87ae28d45ebf5b2d35002204b68f7dba56aba4ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.databinding.tool.reflection;

import android.databinding.Bindable;

import java.util.List;

public abstract class ModelMethod {
    public abstract ModelClass getDeclaringClass();

    public abstract ModelClass[] getParameterTypes();

    public abstract String getName();

    public abstract ModelClass getReturnType(List<ModelClass> args);

    public abstract boolean isVoid();

    public abstract boolean isPublic();

    public abstract boolean isStatic();

    public abstract boolean isAbstract();

    /**
     * @return whether or not this method has been given the {@link Bindable} annotation.
     */
    public abstract boolean isBindable();

    /**
     * Since when this method is available. Important for Binding expressions so that we don't
     * call non-existing APIs when setting UI.
     *
     * @return The SDK_INT where this method was added. If it is not a framework method, should
     * return 1.
     */
    public abstract int getMinApi();

    /**
     * Returns the JNI description of the method which can be used to lookup it in SDK.
     * @see TypeUtil
     */
    public abstract String getJniDescription();

    /**
     * @return true if the final parameter is a varargs parameter.
     */
    public abstract boolean isVarArgs();

    /**
     * @param args The arguments to the method
     * @return Whether the arguments would be accepted as parameters to this method.
     */
    public boolean acceptsArguments(List<ModelClass> args) {
        boolean isVarArgs = isVarArgs();
        ModelClass[] parameterTypes = getParameterTypes();
        if ((!isVarArgs && args.size() != parameterTypes.length) ||
                (isVarArgs && args.size() < parameterTypes.length - 1)) {
            return false; // The wrong number of parameters
        }
        boolean parametersMatch = true;
        for (int i = 0; i < args.size(); i++) {
            ModelClass parameterType = getParameter(i, parameterTypes);
            ModelClass arg = args.get(i);
            if (!parameterType.isAssignableFrom(arg) && !isImplicitConversion(arg, parameterType)) {
                parametersMatch = false;
                break;
            }
        }
        return parametersMatch;
    }

    public boolean isBetterArgMatchThan(ModelMethod other, List<ModelClass> args) {
        final ModelClass[] parameterTypes = getParameterTypes();
        final ModelClass[] otherParameterTypes = other.getParameterTypes();
        for (int i = 0; i < args.size(); i++) {
            final ModelClass arg = args.get(i);
            final ModelClass thisParameter = getParameter(i, parameterTypes);
            final ModelClass thatParameter = other.getParameter(i, otherParameterTypes);
            final int diff = compareParameter(arg, thisParameter, thatParameter);
            if (diff != 0) {
                return diff < 0;
            }
        }
        return false;
    }

    public ModelClass getReturnType() {
        return getReturnType(null);
    }

    private ModelClass getParameter(int index, ModelClass[] parameterTypes) {
        int normalParamCount = isVarArgs() ? parameterTypes.length - 1 : parameterTypes.length;
        if (index < normalParamCount) {
            return parameterTypes[index];
        } else {
            return parameterTypes[parameterTypes.length - 1].getComponentType();
        }
    }

    private static int compareParameter(ModelClass arg, ModelClass thisParameter,
            ModelClass thatParameter) {
        if (thatParameter.equals(arg)) {
            return 1;
        } else if (thisParameter.equals(arg)) {
            return -1;
        } else if (isBoxingConversion(thatParameter, arg)) {
            return 1;
        } else if (isBoxingConversion(thisParameter, arg)) {
            // Boxing/unboxing is second best
            return -1;
        } else {
            int argConversionLevel = getImplicitConversionLevel(arg);
            if (argConversionLevel != -1) {
                int oldConversionLevel = getImplicitConversionLevel(thatParameter);
                int newConversionLevel = getImplicitConversionLevel(thisParameter);
                if (newConversionLevel != -1 &&
                        (oldConversionLevel == -1 || newConversionLevel < oldConversionLevel)) {
                    return -1;
                } else if (oldConversionLevel != -1) {
                    return 1;
                }
            }
            // Look for more exact match
            if (thatParameter.isAssignableFrom(thisParameter)) {
                return -1;
            }
        }
        return 0; // no difference
    }

    public static boolean isBoxingConversion(ModelClass class1, ModelClass class2) {
        if (class1.isPrimitive() != class2.isPrimitive()) {
            return (class1.box().equals(class2.box()));
        } else {
            return false;
        }
    }

    public static int getImplicitConversionLevel(ModelClass primitive) {
        if (primitive == null) {
            return -1;
        } else if (primitive.isByte()) {
            return 0;
        } else if (primitive.isChar()) {
            return 1;
        } else if (primitive.isShort()) {
            return 2;
        } else if (primitive.isInt()) {
            return 3;
        } else if (primitive.isLong()) {
            return 4;
        } else if (primitive.isFloat()) {
            return 5;
        } else if (primitive.isDouble()) {
            return 6;
        } else {
            return -1;
        }
    }

    public static boolean isImplicitConversion(ModelClass from, ModelClass to) {
        if (from != null && to != null && from.isPrimitive() && to.isPrimitive()) {
            if (from.isBoolean() || to.isBoolean() || to.isChar()) {
                return false;
            }
            int fromConversionLevel = getImplicitConversionLevel(from);
            int toConversionLevel = getImplicitConversionLevel(to);
            return fromConversionLevel < toConversionLevel;
        } else {
            return false;
        }
    }
}