001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.core;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.AnnotatedElement;
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Member;
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.lang.reflect.ParameterizedType;
026import java.lang.reflect.Type;
027import java.util.HashMap;
028import java.util.Map;
029
030import org.springframework.util.Assert;
031import org.springframework.util.ClassUtils;
032import org.springframework.util.ObjectUtils;
033
034/**
035 * Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
036 * or {@link Constructor} plus a parameter index and a nested type index for a declared generic
037 * type. Useful as a specification object to pass along.
038 *
039 * <p>As of 4.2, there is a {@link org.springframework.core.annotation.SynthesizingMethodParameter}
040 * subclass available which synthesizes annotations with attribute aliases. That subclass is used
041 * for web and message endpoint processing, in particular.
042 *
043 * @author Juergen Hoeller
044 * @author Rob Harrop
045 * @author Andy Clement
046 * @author Sam Brannen
047 * @since 2.0
048 * @see org.springframework.core.annotation.SynthesizingMethodParameter
049 */
050public class MethodParameter {
051
052        private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
053
054        private static final Class<?> javaUtilOptionalClass;
055
056        static {
057                Class<?> clazz;
058                try {
059                        clazz = ClassUtils.forName("java.util.Optional", MethodParameter.class.getClassLoader());
060                }
061                catch (ClassNotFoundException ex) {
062                        // Java 8 not available - Optional references simply not supported then.
063                        clazz = null;
064                }
065                javaUtilOptionalClass = clazz;
066        }
067
068
069        private final Method method;
070
071        private final Constructor<?> constructor;
072
073        private final int parameterIndex;
074
075        private int nestingLevel;
076
077        /** Map from Integer level to Integer type index */
078        Map<Integer, Integer> typeIndexesPerLevel;
079
080        /** The containing class. Could also be supplied by overriding {@link #getContainingClass()} */
081        private volatile Class<?> containingClass;
082
083        private volatile Class<?> parameterType;
084
085        private volatile Type genericParameterType;
086
087        private volatile Annotation[] parameterAnnotations;
088
089        private volatile ParameterNameDiscoverer parameterNameDiscoverer;
090
091        private volatile String parameterName;
092
093        private volatile MethodParameter nestedMethodParameter;
094
095
096        /**
097         * Create a new {@code MethodParameter} for the given method, with nesting level 1.
098         * @param method the Method to specify a parameter for
099         * @param parameterIndex the index of the parameter: -1 for the method
100         * return type; 0 for the first method parameter; 1 for the second method
101         * parameter, etc.
102         */
103        public MethodParameter(Method method, int parameterIndex) {
104                this(method, parameterIndex, 1);
105        }
106
107        /**
108         * Create a new {@code MethodParameter} for the given method.
109         * @param method the Method to specify a parameter for
110         * @param parameterIndex the index of the parameter: -1 for the method
111         * return type; 0 for the first method parameter; 1 for the second method
112         * parameter, etc.
113         * @param nestingLevel the nesting level of the target type
114         * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
115         * nested List, whereas 2 would indicate the element of the nested List)
116         */
117        public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
118                Assert.notNull(method, "Method must not be null");
119                this.method = method;
120                this.parameterIndex = parameterIndex;
121                this.nestingLevel = nestingLevel;
122                this.constructor = null;
123        }
124
125        /**
126         * Create a new MethodParameter for the given constructor, with nesting level 1.
127         * @param constructor the Constructor to specify a parameter for
128         * @param parameterIndex the index of the parameter
129         */
130        public MethodParameter(Constructor<?> constructor, int parameterIndex) {
131                this(constructor, parameterIndex, 1);
132        }
133
134        /**
135         * Create a new MethodParameter for the given constructor.
136         * @param constructor the Constructor to specify a parameter for
137         * @param parameterIndex the index of the parameter
138         * @param nestingLevel the nesting level of the target type
139         * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
140         * nested List, whereas 2 would indicate the element of the nested List)
141         */
142        public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
143                Assert.notNull(constructor, "Constructor must not be null");
144                this.constructor = constructor;
145                this.parameterIndex = parameterIndex;
146                this.nestingLevel = nestingLevel;
147                this.method = null;
148        }
149
150        /**
151         * Copy constructor, resulting in an independent MethodParameter object
152         * based on the same metadata and cache state that the original object was in.
153         * @param original the original MethodParameter object to copy from
154         */
155        public MethodParameter(MethodParameter original) {
156                Assert.notNull(original, "Original must not be null");
157                this.method = original.method;
158                this.constructor = original.constructor;
159                this.parameterIndex = original.parameterIndex;
160                this.nestingLevel = original.nestingLevel;
161                this.typeIndexesPerLevel = original.typeIndexesPerLevel;
162                this.containingClass = original.containingClass;
163                this.parameterType = original.parameterType;
164                this.genericParameterType = original.genericParameterType;
165                this.parameterAnnotations = original.parameterAnnotations;
166                this.parameterNameDiscoverer = original.parameterNameDiscoverer;
167                this.parameterName = original.parameterName;
168        }
169
170
171        /**
172         * Return the wrapped Method, if any.
173         * <p>Note: Either Method or Constructor is available.
174         * @return the Method, or {@code null} if none
175         */
176        public Method getMethod() {
177                return this.method;
178        }
179
180        /**
181         * Return the wrapped Constructor, if any.
182         * <p>Note: Either Method or Constructor is available.
183         * @return the Constructor, or {@code null} if none
184         */
185        public Constructor<?> getConstructor() {
186                return this.constructor;
187        }
188
189        /**
190         * Return the class that declares the underlying Method or Constructor.
191         */
192        public Class<?> getDeclaringClass() {
193                return getMember().getDeclaringClass();
194        }
195
196        /**
197         * Return the wrapped member.
198         * @return the Method or Constructor as Member
199         */
200        public Member getMember() {
201                // NOTE: no ternary expression to retain JDK <8 compatibility even when using
202                // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
203                // as common type, with that new base class not available on older JDKs)
204                if (this.method != null) {
205                        return this.method;
206                }
207                else {
208                        return this.constructor;
209                }
210        }
211
212        /**
213         * Return the wrapped annotated element.
214         * <p>Note: This method exposes the annotations declared on the method/constructor
215         * itself (i.e. at the method/constructor level, not at the parameter level).
216         * @return the Method or Constructor as AnnotatedElement
217         */
218        public AnnotatedElement getAnnotatedElement() {
219                // NOTE: no ternary expression to retain JDK <8 compatibility even when using
220                // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
221                // as common type, with that new base class not available on older JDKs)
222                if (this.method != null) {
223                        return this.method;
224                }
225                else {
226                        return this.constructor;
227                }
228        }
229
230        /**
231         * Return the index of the method/constructor parameter.
232         * @return the parameter index (-1 in case of the return type)
233         */
234        public int getParameterIndex() {
235                return this.parameterIndex;
236        }
237
238        /**
239         * Increase this parameter's nesting level.
240         * @see #getNestingLevel()
241         */
242        public void increaseNestingLevel() {
243                this.nestingLevel++;
244        }
245
246        /**
247         * Decrease this parameter's nesting level.
248         * @see #getNestingLevel()
249         */
250        public void decreaseNestingLevel() {
251                getTypeIndexesPerLevel().remove(this.nestingLevel);
252                this.nestingLevel--;
253        }
254
255        /**
256         * Return the nesting level of the target type
257         * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
258         * nested List, whereas 2 would indicate the element of the nested List).
259         */
260        public int getNestingLevel() {
261                return this.nestingLevel;
262        }
263
264        /**
265         * Set the type index for the current nesting level.
266         * @param typeIndex the corresponding type index
267         * (or {@code null} for the default type index)
268         * @see #getNestingLevel()
269         */
270        public void setTypeIndexForCurrentLevel(int typeIndex) {
271                getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex);
272        }
273
274        /**
275         * Return the type index for the current nesting level.
276         * @return the corresponding type index, or {@code null}
277         * if none specified (indicating the default type index)
278         * @see #getNestingLevel()
279         */
280        public Integer getTypeIndexForCurrentLevel() {
281                return getTypeIndexForLevel(this.nestingLevel);
282        }
283
284        /**
285         * Return the type index for the specified nesting level.
286         * @param nestingLevel the nesting level to check
287         * @return the corresponding type index, or {@code null}
288         * if none specified (indicating the default type index)
289         */
290        public Integer getTypeIndexForLevel(int nestingLevel) {
291                return getTypeIndexesPerLevel().get(nestingLevel);
292        }
293
294        /**
295         * Obtain the (lazily constructed) type-indexes-per-level Map.
296         */
297        private Map<Integer, Integer> getTypeIndexesPerLevel() {
298                if (this.typeIndexesPerLevel == null) {
299                        this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
300                }
301                return this.typeIndexesPerLevel;
302        }
303
304        /**
305         * Return a variant of this {@code MethodParameter} which points to the
306         * same parameter but one nesting level deeper. This is effectively the
307         * same as {@link #increaseNestingLevel()}, just with an independent
308         * {@code MethodParameter} object (e.g. in case of the original being cached).
309         * @since 4.3
310         */
311        public MethodParameter nested() {
312                if (this.nestedMethodParameter != null) {
313                        return this.nestedMethodParameter;
314                }
315                MethodParameter nestedParam = clone();
316                nestedParam.nestingLevel = this.nestingLevel + 1;
317                this.nestedMethodParameter = nestedParam;
318                return nestedParam;
319        }
320
321        /**
322         * Return whether this method parameter is declared as optional
323         * in the form of Java 8's {@link java.util.Optional}.
324         * @since 4.3
325         */
326        public boolean isOptional() {
327                return (getParameterType() == javaUtilOptionalClass);
328        }
329
330        /**
331         * Return a variant of this {@code MethodParameter} which points to
332         * the same parameter but one nesting level deeper in case of a
333         * {@link java.util.Optional} declaration.
334         * @since 4.3
335         * @see #isOptional()
336         * @see #nested()
337         */
338        public MethodParameter nestedIfOptional() {
339                return (isOptional() ? nested() : this);
340        }
341
342        /**
343         * Set a containing class to resolve the parameter type against.
344         */
345        void setContainingClass(Class<?> containingClass) {
346                this.containingClass = containingClass;
347        }
348
349        /**
350         * Return the containing class for this method parameter.
351         * @return a specific containing class (potentially a subclass of the
352         * declaring class), or otherwise simply the declaring class itself
353         * @see #getDeclaringClass()
354         */
355        public Class<?> getContainingClass() {
356                return (this.containingClass != null ? this.containingClass : getDeclaringClass());
357        }
358
359        /**
360         * Set a resolved (generic) parameter type.
361         */
362        void setParameterType(Class<?> parameterType) {
363                this.parameterType = parameterType;
364        }
365
366        /**
367         * Return the type of the method/constructor parameter.
368         * @return the parameter type (never {@code null})
369         */
370        public Class<?> getParameterType() {
371                Class<?> paramType = this.parameterType;
372                if (paramType == null) {
373                        if (this.parameterIndex < 0) {
374                                Method method = getMethod();
375                                paramType = (method != null ? method.getReturnType() : void.class);
376                        }
377                        else {
378                                paramType = (this.method != null ?
379                                                this.method.getParameterTypes()[this.parameterIndex] :
380                                                this.constructor.getParameterTypes()[this.parameterIndex]);
381                        }
382                        this.parameterType = paramType;
383                }
384                return paramType;
385        }
386
387        /**
388         * Return the generic type of the method/constructor parameter.
389         * @return the parameter type (never {@code null})
390         * @since 3.0
391         */
392        public Type getGenericParameterType() {
393                Type paramType = this.genericParameterType;
394                if (paramType == null) {
395                        if (this.parameterIndex < 0) {
396                                Method method = getMethod();
397                                paramType = (method != null ? method.getGenericReturnType() : void.class);
398                        }
399                        else {
400                                Type[] genericParameterTypes = (this.method != null ?
401                                                this.method.getGenericParameterTypes() : this.constructor.getGenericParameterTypes());
402                                int index = this.parameterIndex;
403                                if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() &&
404                                                !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) &&
405                                                genericParameterTypes.length == this.constructor.getParameterTypes().length - 1) {
406                                        // Bug in javac: type array excludes enclosing instance parameter
407                                        // for inner classes with at least one generic constructor parameter,
408                                        // so access it with the actual parameter index lowered by 1
409                                        index = this.parameterIndex - 1;
410                                }
411                                paramType = (index >= 0 && index < genericParameterTypes.length ?
412                                                genericParameterTypes[index] : getParameterType());
413                        }
414                        this.genericParameterType = paramType;
415                }
416                return paramType;
417        }
418
419        /**
420         * Return the nested type of the method/constructor parameter.
421         * @return the parameter type (never {@code null})
422         * @since 3.1
423         * @see #getNestingLevel()
424         */
425        public Class<?> getNestedParameterType() {
426                if (this.nestingLevel > 1) {
427                        Type type = getGenericParameterType();
428                        for (int i = 2; i <= this.nestingLevel; i++) {
429                                if (type instanceof ParameterizedType) {
430                                        Type[] args = ((ParameterizedType) type).getActualTypeArguments();
431                                        Integer index = getTypeIndexForLevel(i);
432                                        type = args[index != null ? index : args.length - 1];
433                                }
434                                // TODO: Object.class if unresolvable
435                        }
436                        if (type instanceof Class) {
437                                return (Class<?>) type;
438                        }
439                        else if (type instanceof ParameterizedType) {
440                                Type arg = ((ParameterizedType) type).getRawType();
441                                if (arg instanceof Class) {
442                                        return (Class<?>) arg;
443                                }
444                        }
445                        return Object.class;
446                }
447                else {
448                        return getParameterType();
449                }
450        }
451
452        /**
453         * Return the nested generic type of the method/constructor parameter.
454         * @return the parameter type (never {@code null})
455         * @since 4.2
456         * @see #getNestingLevel()
457         */
458        public Type getNestedGenericParameterType() {
459                if (this.nestingLevel > 1) {
460                        Type type = getGenericParameterType();
461                        for (int i = 2; i <= this.nestingLevel; i++) {
462                                if (type instanceof ParameterizedType) {
463                                        Type[] args = ((ParameterizedType) type).getActualTypeArguments();
464                                        Integer index = getTypeIndexForLevel(i);
465                                        type = args[index != null ? index : args.length - 1];
466                                }
467                        }
468                        return type;
469                }
470                else {
471                        return getGenericParameterType();
472                }
473        }
474
475        /**
476         * Return the annotations associated with the target method/constructor itself.
477         */
478        public Annotation[] getMethodAnnotations() {
479                return adaptAnnotationArray(getAnnotatedElement().getAnnotations());
480        }
481
482        /**
483         * Return the method/constructor annotation of the given type, if available.
484         * @param annotationType the annotation type to look for
485         * @return the annotation object, or {@code null} if not found
486         */
487        public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
488                return adaptAnnotation(getAnnotatedElement().getAnnotation(annotationType));
489        }
490
491        /**
492         * Return whether the method/constructor is annotated with the given type.
493         * @param annotationType the annotation type to look for
494         * @since 4.3
495         * @see #getMethodAnnotation(Class)
496         */
497        public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
498                return getAnnotatedElement().isAnnotationPresent(annotationType);
499        }
500
501        /**
502         * Return the annotations associated with the specific method/constructor parameter.
503         */
504        public Annotation[] getParameterAnnotations() {
505                Annotation[] paramAnns = this.parameterAnnotations;
506                if (paramAnns == null) {
507                        Annotation[][] annotationArray = (this.method != null ?
508                                        this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
509                        int index = this.parameterIndex;
510                        if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() &&
511                                        !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) &&
512                                        annotationArray.length == this.constructor.getParameterTypes().length - 1) {
513                                // Bug in javac in JDK <9: annotation array excludes enclosing instance parameter
514                                // for inner classes, so access it with the actual parameter index lowered by 1
515                                index = this.parameterIndex - 1;
516                        }
517                        paramAnns = (index >= 0 && index < annotationArray.length ?
518                                        adaptAnnotationArray(annotationArray[index]) : EMPTY_ANNOTATION_ARRAY);
519                        this.parameterAnnotations = paramAnns;
520                }
521                return paramAnns;
522        }
523
524        /**
525         * Return {@code true} if the parameter has at least one annotation,
526         * {@code false} if it has none.
527         * @see #getParameterAnnotations()
528         */
529        public boolean hasParameterAnnotations() {
530                return (getParameterAnnotations().length != 0);
531        }
532
533        /**
534         * Return the parameter annotation of the given type, if available.
535         * @param annotationType the annotation type to look for
536         * @return the annotation object, or {@code null} if not found
537         */
538        @SuppressWarnings("unchecked")
539        public <A extends Annotation> A getParameterAnnotation(Class<A> annotationType) {
540                Annotation[] anns = getParameterAnnotations();
541                for (Annotation ann : anns) {
542                        if (annotationType.isInstance(ann)) {
543                                return (A) ann;
544                        }
545                }
546                return null;
547        }
548
549        /**
550         * Return whether the parameter is declared with the given annotation type.
551         * @param annotationType the annotation type to look for
552         * @see #getParameterAnnotation(Class)
553         */
554        public <A extends Annotation> boolean hasParameterAnnotation(Class<A> annotationType) {
555                return (getParameterAnnotation(annotationType) != null);
556        }
557
558        /**
559         * Initialize parameter name discovery for this method parameter.
560         * <p>This method does not actually try to retrieve the parameter name at
561         * this point; it just allows discovery to happen when the application calls
562         * {@link #getParameterName()} (if ever).
563         */
564        public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {
565                this.parameterNameDiscoverer = parameterNameDiscoverer;
566        }
567
568        /**
569         * Return the name of the method/constructor parameter.
570         * @return the parameter name (may be {@code null} if no
571         * parameter name metadata is contained in the class file or no
572         * {@link #initParameterNameDiscovery ParameterNameDiscoverer}
573         * has been set to begin with)
574         */
575        public String getParameterName() {
576                ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
577                if (discoverer != null) {
578                        String[] parameterNames = (this.method != null ?
579                                        discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
580                        if (parameterNames != null) {
581                                this.parameterName = parameterNames[this.parameterIndex];
582                        }
583                        this.parameterNameDiscoverer = null;
584                }
585                return this.parameterName;
586        }
587
588
589        /**
590         * A template method to post-process a given annotation instance before
591         * returning it to the caller.
592         * <p>The default implementation simply returns the given annotation as-is.
593         * @param annotation the annotation about to be returned
594         * @return the post-processed annotation (or simply the original one)
595         * @since 4.2
596         */
597        protected <A extends Annotation> A adaptAnnotation(A annotation) {
598                return annotation;
599        }
600
601        /**
602         * A template method to post-process a given annotation array before
603         * returning it to the caller.
604         * <p>The default implementation simply returns the given annotation array as-is.
605         * @param annotations the annotation array about to be returned
606         * @return the post-processed annotation array (or simply the original one)
607         * @since 4.2
608         */
609        protected Annotation[] adaptAnnotationArray(Annotation[] annotations) {
610                return annotations;
611        }
612
613
614        @Override
615        public boolean equals(Object other) {
616                if (this == other) {
617                        return true;
618                }
619                if (!(other instanceof MethodParameter)) {
620                        return false;
621                }
622                MethodParameter otherParam = (MethodParameter) other;
623                return (getContainingClass() == otherParam.getContainingClass() &&
624                                ObjectUtils.nullSafeEquals(this.typeIndexesPerLevel, otherParam.typeIndexesPerLevel) &&
625                                this.nestingLevel == otherParam.nestingLevel &&
626                                this.parameterIndex == otherParam.parameterIndex &&
627                                getMember().equals(otherParam.getMember()));
628        }
629
630        @Override
631        public int hashCode() {
632                return (getMember().hashCode() * 31 + this.parameterIndex);
633        }
634
635        @Override
636        public String toString() {
637                return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") +
638                                " parameter " + this.parameterIndex;
639        }
640
641        @Override
642        public MethodParameter clone() {
643                return new MethodParameter(this);
644        }
645
646
647        /**
648         * Create a new MethodParameter for the given method or constructor.
649         * <p>This is a convenience constructor for scenarios where a
650         * Method or Constructor reference is treated in a generic fashion.
651         * @param methodOrConstructor the Method or Constructor to specify a parameter for
652         * @param parameterIndex the index of the parameter
653         * @return the corresponding MethodParameter instance
654         */
655        public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
656                if (methodOrConstructor instanceof Method) {
657                        return new MethodParameter((Method) methodOrConstructor, parameterIndex);
658                }
659                else if (methodOrConstructor instanceof Constructor) {
660                        return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex);
661                }
662                else {
663                        throw new IllegalArgumentException(
664                                        "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
665                }
666        }
667
668}