1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.model.interpolation.reflection;
20
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Hashtable;
24 import java.util.Iterator;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28
29 /**
30 * @deprecated use {@link org.apache.maven.api.services.ModelBuilder} instead
31 */
32 @Deprecated(since = "4.0.0")
33 class MethodMap {
34 private static final int MORE_SPECIFIC = 0;
35
36 private static final int LESS_SPECIFIC = 1;
37
38 private static final int INCOMPARABLE = 2;
39
40 /**
41 * Keep track of all methods with the same name.
42 */
43 private final Map<String, List<Method>> methodByNameMap = new Hashtable<>();
44
45 /**
46 * Add a method to a list of methods by name.
47 * For a particular class we are keeping track
48 * of all the methods with the same name.
49 *
50 * @param method The method
51 */
52 void add(Method method) {
53 String methodName = method.getName();
54
55 List<Method> l = get(methodName);
56
57 if (l == null) {
58 l = new ArrayList<>();
59 methodByNameMap.put(methodName, l);
60 }
61
62 l.add(method);
63 }
64
65 /**
66 * Return a list of methods with the same name.
67 *
68 * @param key The name of the method.
69 * @return List list of methods
70 */
71 List<Method> get(String key) {
72 return methodByNameMap.get(key);
73 }
74
75 /**
76 * Find a method. Attempts to find the
77 * most specific applicable method using the
78 * algorithm described in the JLS section
79 * 15.12.2 (with the exception that it can't
80 * distinguish a primitive type argument from
81 * an object type argument, since in reflection
82 * primitive type arguments are represented by
83 * their object counterparts, so for an argument of
84 * type (say) java.lang.Integer, it will not be able
85 * to decide between a method that takes int and a
86 * method that takes java.lang.Integer as a parameter.
87 * <p>
88 * This turns out to be a relatively rare case
89 * where this is needed - however, functionality
90 * like this is needed.
91 *
92 * @param methodName name of method
93 * @param args the actual arguments with which the method is called
94 * @return the most specific applicable method, or null if no
95 * method is applicable.
96 * @throws AmbiguousException if there is more than one maximally
97 * specific applicable method
98 */
99 Method find(String methodName, Object... args) throws AmbiguousException {
100 List<Method> methodList = get(methodName);
101
102 if (methodList == null) {
103 return null;
104 }
105
106 int l = args.length;
107 Class<?>[] classes = new Class[l];
108
109 for (int i = 0; i < l; ++i) {
110 Object arg = args[i];
111 // if we are careful down below, a null argument goes in there
112 // so we can know that the null was passed to the method
113 classes[i] = arg == null ? null : arg.getClass();
114 }
115
116 return getMostSpecific(methodList, classes);
117 }
118
119 /**
120 * simple distinguishable exception, used when
121 * we run across ambiguous overloading
122 */
123 static class AmbiguousException extends Exception {
124
125 private static final long serialVersionUID = 751688436639650618L;
126 }
127
128 private static Method getMostSpecific(List<Method> methods, Class<?>... classes) throws AmbiguousException {
129 LinkedList<Method> applicables = getApplicables(methods, classes);
130
131 if (applicables.isEmpty()) {
132 return null;
133 }
134
135 if (applicables.size() == 1) {
136 return applicables.getFirst();
137 }
138
139 // This list will contain the maximally specific methods. Hopefully at
140 // the end of the below loop, the list will contain exactly one method,
141 // (the most specific method) otherwise we have ambiguity.
142 LinkedList<Method> maximals = new LinkedList<>();
143
144 for (Method app : applicables) {
145 Class<?>[] appArgs = app.getParameterTypes();
146 boolean lessSpecific = false;
147
148 for (Iterator<Method> maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); ) {
149 Method max = maximal.next();
150
151 switch (moreSpecific(appArgs, max.getParameterTypes())) {
152 case MORE_SPECIFIC:
153 // This method is more specific than the previously
154 // known maximally specific, so remove the old maximum.
155 maximal.remove();
156 break;
157
158 case LESS_SPECIFIC:
159 // This method is less specific than some of the
160 // currently known maximally specific methods, so we
161 // won't add it into the set of maximally specific
162 // methods
163 lessSpecific = true;
164 break;
165
166 default:
167 }
168 }
169
170 if (!lessSpecific) {
171 maximals.addLast(app);
172 }
173 }
174
175 if (maximals.size() > 1) {
176 // We have more than one maximally specific method
177 throw new AmbiguousException();
178 }
179
180 return maximals.getFirst();
181 }
182
183 /**
184 * Determines which method signature (represented by a class array) is more
185 * specific. This defines a partial ordering on the method signatures.
186 *
187 * @param c1 first signature to compare
188 * @param c2 second signature to compare
189 * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
190 * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
191 */
192 private static int moreSpecific(Class<?>[] c1, Class<?>[] c2) {
193 boolean c1MoreSpecific = false;
194 boolean c2MoreSpecific = false;
195
196 for (int i = 0; i < c1.length; ++i) {
197 if (c1[i] != c2[i]) {
198 c1MoreSpecific = c1MoreSpecific || isStrictMethodInvocationConvertible(c2[i], c1[i]);
199 c2MoreSpecific = c2MoreSpecific || isStrictMethodInvocationConvertible(c1[i], c2[i]);
200 }
201 }
202
203 if (c1MoreSpecific) {
204 if (c2MoreSpecific) {
205 // Incomparable due to cross-assignable arguments (i.e.
206 // foo(String, Object) vs. foo(Object, String))
207 return INCOMPARABLE;
208 }
209
210 return MORE_SPECIFIC;
211 }
212
213 if (c2MoreSpecific) {
214 return LESS_SPECIFIC;
215 }
216
217 // Incomparable due to non-related arguments (i.e.
218 // foo(Runnable) vs. foo(Serializable))
219 return INCOMPARABLE;
220 }
221
222 /**
223 * Returns all methods that are applicable to actual argument types.
224 *
225 * @param methods list of all candidate methods
226 * @param classes the actual types of the arguments
227 * @return a list that contains only applicable methods (number of
228 * formal and actual arguments matches, and argument types are assignable
229 * to formal types through a method invocation conversion).
230 */
231 private static LinkedList<Method> getApplicables(List<Method> methods, Class<?>... classes) {
232 LinkedList<Method> list = new LinkedList<>();
233
234 for (Method method : methods) {
235 if (isApplicable(method, classes)) {
236 list.add(method);
237 }
238 }
239 return list;
240 }
241
242 /**
243 * Returns true if the supplied method is applicable to actual
244 * argument types.
245 *
246 * @param method The method to check for applicability
247 * @param classes The arguments
248 * @return true if the method applies to the parameter types
249 */
250 private static boolean isApplicable(Method method, Class<?>... classes) {
251 Class<?>[] methodArgs = method.getParameterTypes();
252
253 if (methodArgs.length != classes.length) {
254 return false;
255 }
256
257 for (int i = 0; i < classes.length; ++i) {
258 if (!isMethodInvocationConvertible(methodArgs[i], classes[i])) {
259 return false;
260 }
261 }
262
263 return true;
264 }
265
266 /**
267 * Determines whether a type represented by a class object is
268 * convertible to another type represented by a class object using a
269 * method invocation conversion, treating object types of primitive
270 * types as if they were primitive types (that is, a Boolean actual
271 * parameter type matches boolean primitive formal type). This behavior
272 * is because this method is used to determine applicable methods for
273 * an actual parameter list, and primitive types are represented by
274 * their object duals in reflective method calls.
275 *
276 * @param formal the formal parameter type to which the actual
277 * parameter type should be convertible
278 * @param actual the actual parameter type.
279 * @return true if either formal type is assignable from actual type,
280 * or formal is a primitive type and actual is its corresponding object
281 * type or an object type of a primitive type that can be converted to
282 * the formal type.
283 */
284 private static boolean isMethodInvocationConvertible(Class<?> formal, Class<?> actual) {
285 // if it's a null, it means the arg was null
286 if (actual == null && !formal.isPrimitive()) {
287 return true;
288 }
289
290 // Check for identity or widening reference conversion
291 if (actual != null && formal.isAssignableFrom(actual)) {
292 return true;
293 }
294
295 // Check for boxing with widening primitive conversion. Note that
296 // actual parameters are never primitives.
297 if (formal.isPrimitive()) {
298 if (formal == Boolean.TYPE && actual == Boolean.class) {
299 return true;
300 }
301 if (formal == Character.TYPE && actual == Character.class) {
302 return true;
303 }
304 if (formal == Byte.TYPE && actual == Byte.class) {
305 return true;
306 }
307 if (formal == Short.TYPE && (actual == Short.class || actual == Byte.class)) {
308 return true;
309 }
310 if (formal == Integer.TYPE && (actual == Integer.class || actual == Short.class || actual == Byte.class)) {
311 return true;
312 }
313 if (formal == Long.TYPE
314 && (actual == Long.class
315 || actual == Integer.class
316 || actual == Short.class
317 || actual == Byte.class)) {
318 return true;
319 }
320 if (formal == Float.TYPE
321 && (actual == Float.class
322 || actual == Long.class
323 || actual == Integer.class
324 || actual == Short.class
325 || actual == Byte.class)) {
326 return true;
327 }
328 if (formal == Double.TYPE
329 && (actual == Double.class
330 || actual == Float.class
331 || actual == Long.class
332 || actual == Integer.class
333 || actual == Short.class
334 || actual == Byte.class)) {
335 return true;
336 }
337 }
338
339 return false;
340 }
341
342 /**
343 * Determines whether a type represented by a class object is
344 * convertible to another type represented by a class object using a
345 * method invocation conversion, without matching object and primitive
346 * types. This method is used to determine the more specific type when
347 * comparing signatures of methods.
348 *
349 * @param formal the formal parameter type to which the actual
350 * parameter type should be convertible
351 * @param actual the actual parameter type.
352 * @return true if either formal type is assignable from actual type,
353 * or formal and actual are both primitive types and actual can be
354 * subject to widening conversion to formal.
355 */
356 private static boolean isStrictMethodInvocationConvertible(Class<?> formal, Class<?> actual) {
357 // we shouldn't get a null into, but if so
358 if (actual == null && !formal.isPrimitive()) {
359 return true;
360 }
361
362 // Check for identity or widening reference conversion
363 if (formal.isAssignableFrom(actual)) {
364 return true;
365 }
366
367 // Check for widening primitive conversion.
368 if (formal.isPrimitive()) {
369 if (formal == Short.TYPE && (actual == Byte.TYPE)) {
370 return true;
371 }
372 if (formal == Integer.TYPE && (actual == Short.TYPE || actual == Byte.TYPE)) {
373 return true;
374 }
375 if (formal == Long.TYPE && (actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
376 return true;
377 }
378 if (formal == Float.TYPE
379 && (actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
380 return true;
381 }
382 if (formal == Double.TYPE
383 && (actual == Float.TYPE
384 || actual == Long.TYPE
385 || actual == Integer.TYPE
386 || actual == Short.TYPE
387 || actual == Byte.TYPE)) {
388 return true;
389 }
390 }
391 return false;
392 }
393 }