1 /*
2 * Copyright 2014 - 2020 Rafael Winterhalter
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 package net.bytebuddy.implementation.bind;
17
18 import net.bytebuddy.description.method.MethodDescription;
19 import net.bytebuddy.description.method.ParameterList;
20 import net.bytebuddy.description.type.TypeDescription;
21
22 /**
23 * Implementation of an
24 * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
25 * that resolves two conflicting bindings by considering most-specific types of target method parameters in the same manner
26 * as the Java compiler resolves bindings of overloaded method.
27 * <p> </p>
28 * This ambiguity resolver:
29 * <ol>
30 * <li>Checks for each parameter of the source method if a one-to-one parameter binding to both of the target methods exist.</li>
31 * <li>If any of the source method parameters were bound one-to-one to both target methods, the method with the most specific
32 * type is considered as dominant.</li>
33 * <li>If this result is dominant for both the left and the right target method, this resolver will consider the binding as
34 * ambiguous.</li>
35 * <li>If none of the methods is dominant and if the comparison did not result in an ambiguous resolution, the method that
36 * consists of the most one-to-one parameter bindings is considered dominant.</li>
37 * </ol>
38 * Primitive types are considered dominant in the same manner as by the Java compiler.
39 * <p> </p>
40 * For example: If a source method only parameter was successfully bound one-to-one to the only parameters of the target
41 * methods {@code foo(Object)} and {@code bar(String)}, this ambiguity resolver will detect that the {@code String} type
42 * is more specific than the {@code Object} type and determine {@code bar(String)} as the dominant binding.
43 */
44 public enum ArgumentTypeResolver implements MethodDelegationBinder.AmbiguityResolver {
45
46 /**
47 * The singleton instance.
48 */
49 INSTANCE;
50
51 /**
52 * Resolves two bindings by comparing their binding of similar arguments and determining their most specific types.
53 *
54 * @param sourceParameterType The parameter type of the source method
55 * @param leftParameterIndex The index of the parameter of the left method.
56 * @param left The left method's parameter binding.
57 * @param rightParameterIndex The index of the parameter of the right method.
58 * @param right The right method's parameter binding.
59 * @return A resolution according to the given parameters.
60 */
61 private static Resolution resolveRivalBinding(TypeDescription sourceParameterType,
62 int leftParameterIndex,
63 MethodDelegationBinder.MethodBinding left,
64 int rightParameterIndex,
65 MethodDelegationBinder.MethodBinding right) {
66 TypeDescription leftParameterType = left.getTarget().getParameters().get(leftParameterIndex).getType().asErasure();
67 TypeDescription rightParameterType = right.getTarget().getParameters().get(rightParameterIndex).getType().asErasure();
68 if (!leftParameterType.equals(rightParameterType)) {
69 if (leftParameterType.isPrimitive() && rightParameterType.isPrimitive()) {
70 return PrimitiveTypePrecedence.forPrimitive(leftParameterType).resolve(PrimitiveTypePrecedence.forPrimitive(rightParameterType));
71 } else if (leftParameterType.isPrimitive() /* && !rightParameterType.isPrimitive() */) {
72 return sourceParameterType.isPrimitive() ? Resolution.LEFT : Resolution.RIGHT;
73 } else if (/* !leftParameterType.isPrimitive() && */ rightParameterType.isPrimitive()) {
74 return sourceParameterType.isPrimitive() ? Resolution.RIGHT : Resolution.LEFT;
75 } else {
76 // Note that leftParameterType != rightParameterType, thus both cannot be true.
77 if (leftParameterType.isAssignableFrom(rightParameterType)) {
78 return Resolution.RIGHT;
79 } else if (rightParameterType.isAssignableFrom(leftParameterType)) {
80 return Resolution.LEFT;
81 } else {
82 return Resolution.AMBIGUOUS;
83 }
84 }
85 } else {
86 return Resolution.UNKNOWN;
87 }
88 }
89
90 /**
91 * Resolves the most specific method by their score. A method's score is calculated by the absolute number of
92 * parameters that were bound by using an explicit {@link net.bytebuddy.implementation.bind.annotation.Argument}
93 * annotation.
94 *
95 * @param boundParameterScore The difference of the scores of the left and the right method.
96 * @return A resolution according to this score.
97 */
98 private static Resolution resolveByScore(int boundParameterScore) {
99 if (boundParameterScore == 0) {
100 return Resolution.AMBIGUOUS;
101 } else if (boundParameterScore > 0) {
102 return Resolution.LEFT;
103 } else /* difference < 0*/ {
104 return Resolution.RIGHT;
105 }
106 }
107
108 /**
109 * {@inheritDoc}
110 */
111 public Resolution resolve(MethodDescription source,
112 MethodDelegationBinder.MethodBinding left,
113 MethodDelegationBinder.MethodBinding right) {
114 Resolution resolution = Resolution.UNKNOWN;
115 ParameterList<?> sourceParameters = source.getParameters();
116 int leftExtra = 0, rightExtra = 0;
117 for (int sourceParameterIndex = 0; sourceParameterIndex < sourceParameters.size(); sourceParameterIndex++) {
118 ParameterIndexToken parameterIndexToken = new ParameterIndexToken(sourceParameterIndex);
119 Integer leftParameterIndex = left.getTargetParameterIndex(parameterIndexToken);
120 Integer rightParameterIndex = right.getTargetParameterIndex(parameterIndexToken);
121 if (leftParameterIndex != null && rightParameterIndex != null) {
122 resolution = resolution.merge(resolveRivalBinding(sourceParameters.get(sourceParameterIndex).getType().asErasure(),
123 leftParameterIndex,
124 left,
125 rightParameterIndex,
126 right));
127 } else if (leftParameterIndex != null /* && rightParameterIndex == null */) {
128 leftExtra++;
129 } else if (/*leftParameterIndex == null && */ rightParameterIndex != null) {
130 rightExtra++;
131 }
132 }
133 return resolution == Resolution.UNKNOWN
134 ? resolveByScore(leftExtra - rightExtra)
135 : resolution;
136 }
137
138 /**
139 * A representation of the precedence of a most specific primitive type in the Java programming language.
140 */
141 protected enum PrimitiveTypePrecedence {
142
143 /**
144 * The precedence of the {@code boolean} type.
145 */
146 BOOLEAN(0),
147
148 /**
149 * The precedence of the {@code byte} type.
150 */
151 BYTE(1),
152
153 /**
154 * The precedence of the {@code short} type.
155 */
156 SHORT(2),
157
158 /**
159 * The precedence of the {@code int} type.
160 */
161 INTEGER(3),
162
163 /**
164 * The precedence of the {@code char} type.
165 */
166 CHARACTER(4),
167
168 /**
169 * The precedence of the {@code long} type.
170 */
171 LONG(5),
172
173 /**
174 * The precedence of the {@code float} type.
175 */
176 FLOAT(6),
177
178 /**
179 * The precedence of the {@code double} type.
180 */
181 DOUBLE(7);
182
183 /**
184 * A score representing the precedence where a higher score represents a less specific type.
185 */
186 private final int score;
187
188 /**
189 * Creates a new primitive type precedence.
190 *
191 * @param score A score representing the precedence where a higher score represents a less specific type.
192 */
193 PrimitiveTypePrecedence(int score) {
194 this.score = score;
195 }
196
197 /**
198 * Locates the primitive type precedence for a given type.
199 *
200 * @param typeDescription The non-void, primitive type for which the precedence should be located.
201 * @return The corresponding primitive type precedence.
202 */
203 public static PrimitiveTypePrecedence forPrimitive(TypeDescription typeDescription) {
204 if (typeDescription.represents(boolean.class)) {
205 return BOOLEAN;
206 } else if (typeDescription.represents(byte.class)) {
207 return BYTE;
208 } else if (typeDescription.represents(short.class)) {
209 return SHORT;
210 } else if (typeDescription.represents(int.class)) {
211 return INTEGER;
212 } else if (typeDescription.represents(char.class)) {
213 return CHARACTER;
214 } else if (typeDescription.represents(long.class)) {
215 return LONG;
216 } else if (typeDescription.represents(float.class)) {
217 return FLOAT;
218 } else if (typeDescription.represents(double.class)) {
219 return DOUBLE;
220 } else {
221 throw new IllegalArgumentException("Not a non-void, primitive type " + typeDescription);
222 }
223 }
224
225 /**
226 * Resolves the least specific type of two primitive type precedence with this instance representing a
227 * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT}
228 * resolution and the argument type representing the
229 * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT}
230 * resolution.
231 *
232 * @param right Another primitive type precedence against which this precedence should be resolved.
233 * @return The resolution of
234 */
235 public Resolution resolve(PrimitiveTypePrecedence right) {
236 if (score - right.score == 0) {
237 return Resolution.UNKNOWN;
238 } else if (score - right.score > 0) {
239 return Resolution.RIGHT;
240 } else /* score - right.score < 0 */ {
241 return Resolution.LEFT;
242 }
243 }
244 }
245
246 /**
247 * This token is used to mark a one-to-one binding of a source method parameter to a target method parameter.
248 *
249 * @see net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding#getTargetParameterIndex(Object)
250 */
251 public static class ParameterIndexToken {
252
253 /**
254 * The parameter index that is represented by this token.
255 */
256 @SuppressWarnings("unused")
257 private final int parameterIndex;
258
259 /**
260 * Create a parameter index token for a given parameter of the source method.
261 *
262 * @param parameterIndex The parameter index of the source method which is mapped to a target method parameter.
263 */
264 public ParameterIndexToken(int parameterIndex) {
265 this.parameterIndex = parameterIndex;
266 }
267
268 @Override
269 public int hashCode() {
270 return parameterIndex;
271 }
272
273 @Override
274 public boolean equals(Object other) {
275 if (this == other) {
276 return true;
277 } else if (other == null || getClass() != other.getClass()) {
278 return false;
279 }
280 ParameterIndexToken parameterIndexToken = (ParameterIndexToken) other;
281 return parameterIndex == parameterIndexToken.parameterIndex;
282 }
283 }
284 }
285