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.convert;
018
019import java.io.Serializable;
020import java.lang.annotation.Annotation;
021import java.lang.reflect.AnnotatedElement;
022import java.lang.reflect.Field;
023import java.lang.reflect.Type;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.stream.Stream;
029
030import org.springframework.core.MethodParameter;
031import org.springframework.core.ResolvableType;
032import org.springframework.core.annotation.AnnotatedElementUtils;
033import org.springframework.lang.Nullable;
034import org.springframework.util.Assert;
035import org.springframework.util.ClassUtils;
036import org.springframework.util.ObjectUtils;
037
038/**
039 * Contextual descriptor about a type to convert from or to.
040 * Capable of representing arrays and generic collection types.
041 *
042 * @author Keith Donald
043 * @author Andy Clement
044 * @author Juergen Hoeller
045 * @author Phillip Webb
046 * @author Sam Brannen
047 * @author Stephane Nicoll
048 * @since 3.0
049 * @see ConversionService#canConvert(TypeDescriptor, TypeDescriptor)
050 * @see ConversionService#convert(Object, TypeDescriptor, TypeDescriptor)
051 */
052@SuppressWarnings("serial")
053public class TypeDescriptor implements Serializable {
054
055        private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
056
057        private static final Map<Class<?>, TypeDescriptor> commonTypesCache = new HashMap<>(32);
058
059        private static final Class<?>[] CACHED_COMMON_TYPES = {
060                        boolean.class, Boolean.class, byte.class, Byte.class, char.class, Character.class,
061                        double.class, Double.class, float.class, Float.class, int.class, Integer.class,
062                        long.class, Long.class, short.class, Short.class, String.class, Object.class};
063
064        static {
065                for (Class<?> preCachedClass : CACHED_COMMON_TYPES) {
066                        commonTypesCache.put(preCachedClass, valueOf(preCachedClass));
067                }
068        }
069
070
071        private final Class<?> type;
072
073        private final ResolvableType resolvableType;
074
075        private final AnnotatedElementAdapter annotatedElement;
076
077
078        /**
079         * Create a new type descriptor from a {@link MethodParameter}.
080         * <p>Use this constructor when a source or target conversion point is a
081         * constructor parameter, method parameter, or method return value.
082         * @param methodParameter the method parameter
083         */
084        public TypeDescriptor(MethodParameter methodParameter) {
085                this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
086                this.type = this.resolvableType.resolve(methodParameter.getNestedParameterType());
087                this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ?
088                                methodParameter.getMethodAnnotations() : methodParameter.getParameterAnnotations());
089        }
090
091        /**
092         * Create a new type descriptor from a {@link Field}.
093         * <p>Use this constructor when a source or target conversion point is a field.
094         * @param field the field
095         */
096        public TypeDescriptor(Field field) {
097                this.resolvableType = ResolvableType.forField(field);
098                this.type = this.resolvableType.resolve(field.getType());
099                this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations());
100        }
101
102        /**
103         * Create a new type descriptor from a {@link Property}.
104         * <p>Use this constructor when a source or target conversion point is a
105         * property on a Java class.
106         * @param property the property
107         */
108        public TypeDescriptor(Property property) {
109                Assert.notNull(property, "Property must not be null");
110                this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter());
111                this.type = this.resolvableType.resolve(property.getType());
112                this.annotatedElement = new AnnotatedElementAdapter(property.getAnnotations());
113        }
114
115        /**
116         * Create a new type descriptor from a {@link ResolvableType}.
117         * <p>This constructor is used internally and may also be used by subclasses
118         * that support non-Java languages with extended type systems. It is public
119         * as of 5.1.4 whereas it was protected before.
120         * @param resolvableType the resolvable type
121         * @param type the backing type (or {@code null} if it should get resolved)
122         * @param annotations the type annotations
123         * @since 4.0
124         */
125        public TypeDescriptor(ResolvableType resolvableType, @Nullable Class<?> type, @Nullable Annotation[] annotations) {
126                this.resolvableType = resolvableType;
127                this.type = (type != null ? type : resolvableType.toClass());
128                this.annotatedElement = new AnnotatedElementAdapter(annotations);
129        }
130
131
132        /**
133         * Variation of {@link #getType()} that accounts for a primitive type by
134         * returning its object wrapper type.
135         * <p>This is useful for conversion service implementations that wish to
136         * normalize to object-based types and not work with primitive types directly.
137         */
138        public Class<?> getObjectType() {
139                return ClassUtils.resolvePrimitiveIfNecessary(getType());
140        }
141
142        /**
143         * The type of the backing class, method parameter, field, or property
144         * described by this TypeDescriptor.
145         * <p>Returns primitive types as-is. See {@link #getObjectType()} for a
146         * variation of this operation that resolves primitive types to their
147         * corresponding Object types if necessary.
148         * @see #getObjectType()
149         */
150        public Class<?> getType() {
151                return this.type;
152        }
153
154        /**
155         * Return the underlying {@link ResolvableType}.
156         * @since 4.0
157         */
158        public ResolvableType getResolvableType() {
159                return this.resolvableType;
160        }
161
162        /**
163         * Return the underlying source of the descriptor. Will return a {@link Field},
164         * {@link MethodParameter} or {@link Type} depending on how the {@link TypeDescriptor}
165         * was constructed. This method is primarily to provide access to additional
166         * type information or meta-data that alternative JVM languages may provide.
167         * @since 4.0
168         */
169        public Object getSource() {
170                return this.resolvableType.getSource();
171        }
172
173        /**
174         * Narrows this {@link TypeDescriptor} by setting its type to the class of the
175         * provided value.
176         * <p>If the value is {@code null}, no narrowing is performed and this TypeDescriptor
177         * is returned unchanged.
178         * <p>Designed to be called by binding frameworks when they read property, field,
179         * or method return values. Allows such frameworks to narrow a TypeDescriptor built
180         * from a declared property, field, or method return value type. For example, a field
181         * declared as {@code java.lang.Object} would be narrowed to {@code java.util.HashMap}
182         * if it was set to a {@code java.util.HashMap} value. The narrowed TypeDescriptor
183         * can then be used to convert the HashMap to some other type. Annotation and nested
184         * type context is preserved by the narrowed copy.
185         * @param value the value to use for narrowing this type descriptor
186         * @return this TypeDescriptor narrowed (returns a copy with its type updated to the
187         * class of the provided value)
188         */
189        public TypeDescriptor narrow(@Nullable Object value) {
190                if (value == null) {
191                        return this;
192                }
193                ResolvableType narrowed = ResolvableType.forType(value.getClass(), getResolvableType());
194                return new TypeDescriptor(narrowed, value.getClass(), getAnnotations());
195        }
196
197        /**
198         * Cast this {@link TypeDescriptor} to a superclass or implemented interface
199         * preserving annotations and nested type context.
200         * @param superType the super type to cast to (can be {@code null})
201         * @return a new TypeDescriptor for the up-cast type
202         * @throws IllegalArgumentException if this type is not assignable to the super-type
203         * @since 3.2
204         */
205        @Nullable
206        public TypeDescriptor upcast(@Nullable Class<?> superType) {
207                if (superType == null) {
208                        return null;
209                }
210                Assert.isAssignable(superType, getType());
211                return new TypeDescriptor(getResolvableType().as(superType), superType, getAnnotations());
212        }
213
214        /**
215         * Return the name of this type: the fully qualified class name.
216         */
217        public String getName() {
218                return ClassUtils.getQualifiedName(getType());
219        }
220
221        /**
222         * Is this type a primitive type?
223         */
224        public boolean isPrimitive() {
225                return getType().isPrimitive();
226        }
227
228        /**
229         * Return the annotations associated with this type descriptor, if any.
230         * @return the annotations, or an empty array if none
231         */
232        public Annotation[] getAnnotations() {
233                return this.annotatedElement.getAnnotations();
234        }
235
236        /**
237         * Determine if this type descriptor has the specified annotation.
238         * <p>As of Spring Framework 4.2, this method supports arbitrary levels
239         * of meta-annotations.
240         * @param annotationType the annotation type
241         * @return <tt>true</tt> if the annotation is present
242         */
243        public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
244                if (this.annotatedElement.isEmpty()) {
245                        // Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations()
246                        // to return a copy of the array, whereas we can do it more efficiently here.
247                        return false;
248                }
249                return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType);
250        }
251
252        /**
253         * Obtain the annotation of the specified {@code annotationType} that is on this type descriptor.
254         * <p>As of Spring Framework 4.2, this method supports arbitrary levels of meta-annotations.
255         * @param annotationType the annotation type
256         * @return the annotation, or {@code null} if no such annotation exists on this type descriptor
257         */
258        @Nullable
259        public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
260                if (this.annotatedElement.isEmpty()) {
261                        // Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations()
262                        // to return a copy of the array, whereas we can do it more efficiently here.
263                        return null;
264                }
265                return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType);
266        }
267
268        /**
269         * Returns true if an object of this type descriptor can be assigned to the location
270         * described by the given type descriptor.
271         * <p>For example, {@code valueOf(String.class).isAssignableTo(valueOf(CharSequence.class))}
272         * returns {@code true} because a String value can be assigned to a CharSequence variable.
273         * On the other hand, {@code valueOf(Number.class).isAssignableTo(valueOf(Integer.class))}
274         * returns {@code false} because, while all Integers are Numbers, not all Numbers are Integers.
275         * <p>For arrays, collections, and maps, element and key/value types are checked if declared.
276         * For example, a List&lt;String&gt; field value is assignable to a Collection&lt;CharSequence&gt;
277         * field, but List&lt;Number&gt; is not assignable to List&lt;Integer&gt;.
278         * @return {@code true} if this type is assignable to the type represented by the provided
279         * type descriptor
280         * @see #getObjectType()
281         */
282        public boolean isAssignableTo(TypeDescriptor typeDescriptor) {
283                boolean typesAssignable = typeDescriptor.getObjectType().isAssignableFrom(getObjectType());
284                if (!typesAssignable) {
285                        return false;
286                }
287                if (isArray() && typeDescriptor.isArray()) {
288                        return isNestedAssignable(getElementTypeDescriptor(), typeDescriptor.getElementTypeDescriptor());
289                }
290                else if (isCollection() && typeDescriptor.isCollection()) {
291                        return isNestedAssignable(getElementTypeDescriptor(), typeDescriptor.getElementTypeDescriptor());
292                }
293                else if (isMap() && typeDescriptor.isMap()) {
294                        return isNestedAssignable(getMapKeyTypeDescriptor(), typeDescriptor.getMapKeyTypeDescriptor()) &&
295                                isNestedAssignable(getMapValueTypeDescriptor(), typeDescriptor.getMapValueTypeDescriptor());
296                }
297                else {
298                        return true;
299                }
300        }
301
302        private boolean isNestedAssignable(@Nullable TypeDescriptor nestedTypeDescriptor,
303                        @Nullable TypeDescriptor otherNestedTypeDescriptor) {
304
305                return (nestedTypeDescriptor == null || otherNestedTypeDescriptor == null ||
306                                nestedTypeDescriptor.isAssignableTo(otherNestedTypeDescriptor));
307        }
308
309        /**
310         * Is this type a {@link Collection} type?
311         */
312        public boolean isCollection() {
313                return Collection.class.isAssignableFrom(getType());
314        }
315
316        /**
317         * Is this type an array type?
318         */
319        public boolean isArray() {
320                return getType().isArray();
321        }
322
323        /**
324         * If this type is an array, returns the array's component type.
325         * If this type is a {@code Stream}, returns the stream's component type.
326         * If this type is a {@link Collection} and it is parameterized, returns the Collection's element type.
327         * If the Collection is not parameterized, returns {@code null} indicating the element type is not declared.
328         * @return the array component type or Collection element type, or {@code null} if this type is not
329         * an array type or a {@code java.util.Collection} or if its element type is not parameterized
330         * @see #elementTypeDescriptor(Object)
331         */
332        @Nullable
333        public TypeDescriptor getElementTypeDescriptor() {
334                if (getResolvableType().isArray()) {
335                        return new TypeDescriptor(getResolvableType().getComponentType(), null, getAnnotations());
336                }
337                if (Stream.class.isAssignableFrom(getType())) {
338                        return getRelatedIfResolvable(this, getResolvableType().as(Stream.class).getGeneric(0));
339                }
340                return getRelatedIfResolvable(this, getResolvableType().asCollection().getGeneric(0));
341        }
342
343        /**
344         * If this type is a {@link Collection} or an array, creates a element TypeDescriptor
345         * from the provided collection or array element.
346         * <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class
347         * of the provided collection or array element. For example, if this describes a
348         * {@code java.util.List&lt;java.lang.Number&lt;} and the element argument is an
349         * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}.
350         * If this describes a {@code java.util.List&lt;?&gt;} and the element argument is an
351         * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}
352         * as well.
353         * <p>Annotation and nested type context will be preserved in the narrowed
354         * TypeDescriptor that is returned.
355         * @param element the collection or array element
356         * @return a element type descriptor, narrowed to the type of the provided element
357         * @see #getElementTypeDescriptor()
358         * @see #narrow(Object)
359         */
360        @Nullable
361        public TypeDescriptor elementTypeDescriptor(Object element) {
362                return narrow(element, getElementTypeDescriptor());
363        }
364
365        /**
366         * Is this type a {@link Map} type?
367         */
368        public boolean isMap() {
369                return Map.class.isAssignableFrom(getType());
370        }
371
372        /**
373         * If this type is a {@link Map} and its key type is parameterized,
374         * returns the map's key type. If the Map's key type is not parameterized,
375         * returns {@code null} indicating the key type is not declared.
376         * @return the Map key type, or {@code null} if this type is a Map
377         * but its key type is not parameterized
378         * @throws IllegalStateException if this type is not a {@code java.util.Map}
379         */
380        @Nullable
381        public TypeDescriptor getMapKeyTypeDescriptor() {
382                Assert.state(isMap(), "Not a [java.util.Map]");
383                return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(0));
384        }
385
386        /**
387         * If this type is a {@link Map}, creates a mapKey {@link TypeDescriptor}
388         * from the provided map key.
389         * <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property
390         * to the class of the provided map key. For example, if this describes a
391         * {@code java.util.Map&lt;java.lang.Number, java.lang.String&lt;} and the key
392         * argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be
393         * {@code java.lang.Integer}. If this describes a {@code java.util.Map&lt;?, ?&gt;}
394         * and the key argument is a {@code java.lang.Integer}, the returned
395         * TypeDescriptor will be {@code java.lang.Integer} as well.
396         * <p>Annotation and nested type context will be preserved in the narrowed
397         * TypeDescriptor that is returned.
398         * @param mapKey the map key
399         * @return the map key type descriptor
400         * @throws IllegalStateException if this type is not a {@code java.util.Map}
401         * @see #narrow(Object)
402         */
403        @Nullable
404        public TypeDescriptor getMapKeyTypeDescriptor(Object mapKey) {
405                return narrow(mapKey, getMapKeyTypeDescriptor());
406        }
407
408        /**
409         * If this type is a {@link Map} and its value type is parameterized,
410         * returns the map's value type.
411         * <p>If the Map's value type is not parameterized, returns {@code null}
412         * indicating the value type is not declared.
413         * @return the Map value type, or {@code null} if this type is a Map
414         * but its value type is not parameterized
415         * @throws IllegalStateException if this type is not a {@code java.util.Map}
416         */
417        @Nullable
418        public TypeDescriptor getMapValueTypeDescriptor() {
419                Assert.state(isMap(), "Not a [java.util.Map]");
420                return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(1));
421        }
422
423        /**
424         * If this type is a {@link Map}, creates a mapValue {@link TypeDescriptor}
425         * from the provided map value.
426         * <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property
427         * to the class of the provided map value. For example, if this describes a
428         * {@code java.util.Map&lt;java.lang.String, java.lang.Number&lt;} and the value
429         * argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be
430         * {@code java.lang.Integer}. If this describes a {@code java.util.Map&lt;?, ?&gt;}
431         * and the value argument is a {@code java.lang.Integer}, the returned
432         * TypeDescriptor will be {@code java.lang.Integer} as well.
433         * <p>Annotation and nested type context will be preserved in the narrowed
434         * TypeDescriptor that is returned.
435         * @param mapValue the map value
436         * @return the map value type descriptor
437         * @throws IllegalStateException if this type is not a {@code java.util.Map}
438         * @see #narrow(Object)
439         */
440        @Nullable
441        public TypeDescriptor getMapValueTypeDescriptor(Object mapValue) {
442                return narrow(mapValue, getMapValueTypeDescriptor());
443        }
444
445        @Nullable
446        private TypeDescriptor narrow(@Nullable Object value, @Nullable TypeDescriptor typeDescriptor) {
447                if (typeDescriptor != null) {
448                        return typeDescriptor.narrow(value);
449                }
450                if (value != null) {
451                        return narrow(value);
452                }
453                return null;
454        }
455
456        @Override
457        public boolean equals(@Nullable Object other) {
458                if (this == other) {
459                        return true;
460                }
461                if (!(other instanceof TypeDescriptor)) {
462                        return false;
463                }
464                TypeDescriptor otherDesc = (TypeDescriptor) other;
465                if (getType() != otherDesc.getType()) {
466                        return false;
467                }
468                if (!annotationsMatch(otherDesc)) {
469                        return false;
470                }
471                if (isCollection() || isArray()) {
472                        return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor());
473                }
474                else if (isMap()) {
475                        return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) &&
476                                        ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
477                }
478                else {
479                        return true;
480                }
481        }
482
483        private boolean annotationsMatch(TypeDescriptor otherDesc) {
484                Annotation[] anns = getAnnotations();
485                Annotation[] otherAnns = otherDesc.getAnnotations();
486                if (anns == otherAnns) {
487                        return true;
488                }
489                if (anns.length != otherAnns.length) {
490                        return false;
491                }
492                if (anns.length > 0) {
493                        for (int i = 0; i < anns.length; i++) {
494                                if (!annotationEquals(anns[i], otherAnns[i])) {
495                                        return false;
496                                }
497                        }
498                }
499                return true;
500        }
501
502        private boolean annotationEquals(Annotation ann, Annotation otherAnn) {
503                // Annotation.equals is reflective and pretty slow, so let's check identity and proxy type first.
504                return (ann == otherAnn || (ann.getClass() == otherAnn.getClass() && ann.equals(otherAnn)));
505        }
506
507        @Override
508        public int hashCode() {
509                return getType().hashCode();
510        }
511
512        @Override
513        public String toString() {
514                StringBuilder builder = new StringBuilder();
515                for (Annotation ann : getAnnotations()) {
516                        builder.append("@").append(ann.annotationType().getName()).append(' ');
517                }
518                builder.append(getResolvableType().toString());
519                return builder.toString();
520        }
521
522
523        /**
524         * Create a new type descriptor for an object.
525         * <p>Use this factory method to introspect a source object before asking the
526         * conversion system to convert it to some another type.
527         * <p>If the provided object is {@code null}, returns {@code null}, else calls
528         * {@link #valueOf(Class)} to build a TypeDescriptor from the object's class.
529         * @param source the source object
530         * @return the type descriptor
531         */
532        @Nullable
533        public static TypeDescriptor forObject(@Nullable Object source) {
534                return (source != null ? valueOf(source.getClass()) : null);
535        }
536
537        /**
538         * Create a new type descriptor from the given type.
539         * <p>Use this to instruct the conversion system to convert an object to a
540         * specific target type, when no type location such as a method parameter or
541         * field is available to provide additional conversion context.
542         * <p>Generally prefer use of {@link #forObject(Object)} for constructing type
543         * descriptors from source objects, as it handles the {@code null} object case.
544         * @param type the class (may be {@code null} to indicate {@code Object.class})
545         * @return the corresponding type descriptor
546         */
547        public static TypeDescriptor valueOf(@Nullable Class<?> type) {
548                if (type == null) {
549                        type = Object.class;
550                }
551                TypeDescriptor desc = commonTypesCache.get(type);
552                return (desc != null ? desc : new TypeDescriptor(ResolvableType.forClass(type), null, null));
553        }
554
555        /**
556         * Create a new type descriptor from a {@link java.util.Collection} type.
557         * <p>Useful for converting to typed Collections.
558         * <p>For example, a {@code List<String>} could be converted to a
559         * {@code List<EmailAddress>} by converting to a targetType built with this method.
560         * The method call to construct such a {@code TypeDescriptor} would look something
561         * like: {@code collection(List.class, TypeDescriptor.valueOf(EmailAddress.class));}
562         * @param collectionType the collection type, which must implement {@link Collection}.
563         * @param elementTypeDescriptor a descriptor for the collection's element type,
564         * used to convert collection elements
565         * @return the collection type descriptor
566         */
567        public static TypeDescriptor collection(Class<?> collectionType, @Nullable TypeDescriptor elementTypeDescriptor) {
568                Assert.notNull(collectionType, "Collection type must not be null");
569                if (!Collection.class.isAssignableFrom(collectionType)) {
570                        throw new IllegalArgumentException("Collection type must be a [java.util.Collection]");
571                }
572                ResolvableType element = (elementTypeDescriptor != null ? elementTypeDescriptor.resolvableType : null);
573                return new TypeDescriptor(ResolvableType.forClassWithGenerics(collectionType, element), null, null);
574        }
575
576        /**
577         * Create a new type descriptor from a {@link java.util.Map} type.
578         * <p>Useful for converting to typed Maps.
579         * <p>For example, a Map&lt;String, String&gt; could be converted to a Map&lt;Id, EmailAddress&gt;
580         * by converting to a targetType built with this method:
581         * The method call to construct such a TypeDescriptor would look something like:
582         * <pre class="code">
583         * map(Map.class, TypeDescriptor.valueOf(Id.class), TypeDescriptor.valueOf(EmailAddress.class));
584         * </pre>
585         * @param mapType the map type, which must implement {@link Map}
586         * @param keyTypeDescriptor a descriptor for the map's key type, used to convert map keys
587         * @param valueTypeDescriptor the map's value type, used to convert map values
588         * @return the map type descriptor
589         */
590        public static TypeDescriptor map(Class<?> mapType, @Nullable TypeDescriptor keyTypeDescriptor,
591                        @Nullable TypeDescriptor valueTypeDescriptor) {
592
593                Assert.notNull(mapType, "Map type must not be null");
594                if (!Map.class.isAssignableFrom(mapType)) {
595                        throw new IllegalArgumentException("Map type must be a [java.util.Map]");
596                }
597                ResolvableType key = (keyTypeDescriptor != null ? keyTypeDescriptor.resolvableType : null);
598                ResolvableType value = (valueTypeDescriptor != null ? valueTypeDescriptor.resolvableType : null);
599                return new TypeDescriptor(ResolvableType.forClassWithGenerics(mapType, key, value), null, null);
600        }
601
602        /**
603         * Create a new type descriptor as an array of the specified type.
604         * <p>For example to create a {@code Map<String,String>[]} use:
605         * <pre class="code">
606         * TypeDescriptor.array(TypeDescriptor.map(Map.class, TypeDescriptor.value(String.class), TypeDescriptor.value(String.class)));
607         * </pre>
608         * @param elementTypeDescriptor the {@link TypeDescriptor} of the array element or {@code null}
609         * @return an array {@link TypeDescriptor} or {@code null} if {@code elementTypeDescriptor} is {@code null}
610         * @since 3.2.1
611         */
612        @Nullable
613        public static TypeDescriptor array(@Nullable TypeDescriptor elementTypeDescriptor) {
614                if (elementTypeDescriptor == null) {
615                        return null;
616                }
617                return new TypeDescriptor(ResolvableType.forArrayComponent(elementTypeDescriptor.resolvableType),
618                                null, elementTypeDescriptor.getAnnotations());
619        }
620
621        /**
622         * Create a type descriptor for a nested type declared within the method parameter.
623         * <p>For example, if the methodParameter is a {@code List<String>} and the
624         * nesting level is 1, the nested type descriptor will be String.class.
625         * <p>If the methodParameter is a {@code List<List<String>>} and the nesting
626         * level is 2, the nested type descriptor will also be a String.class.
627         * <p>If the methodParameter is a {@code Map<Integer, String>} and the nesting
628         * level is 1, the nested type descriptor will be String, derived from the map value.
629         * <p>If the methodParameter is a {@code List<Map<Integer, String>>} and the
630         * nesting level is 2, the nested type descriptor will be String, derived from the map value.
631         * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared.
632         * For example, if the method parameter is a {@code List<?>}, the nested type
633         * descriptor returned will be {@code null}.
634         * @param methodParameter the method parameter with a nestingLevel of 1
635         * @param nestingLevel the nesting level of the collection/array element or
636         * map key/value declaration within the method parameter
637         * @return the nested type descriptor at the specified nesting level,
638         * or {@code null} if it could not be obtained
639         * @throws IllegalArgumentException if the nesting level of the input
640         * {@link MethodParameter} argument is not 1, or if the types up to the
641         * specified nesting level are not of collection, array, or map types
642         */
643        @Nullable
644        public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) {
645                if (methodParameter.getNestingLevel() != 1) {
646                        throw new IllegalArgumentException("MethodParameter nesting level must be 1: " +
647                                        "use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal");
648                }
649                return nested(new TypeDescriptor(methodParameter), nestingLevel);
650        }
651
652        /**
653         * Create a type descriptor for a nested type declared within the field.
654         * <p>For example, if the field is a {@code List<String>} and the nesting
655         * level is 1, the nested type descriptor will be {@code String.class}.
656         * <p>If the field is a {@code List<List<String>>} and the nesting level is
657         * 2, the nested type descriptor will also be a {@code String.class}.
658         * <p>If the field is a {@code Map<Integer, String>} and the nesting level
659         * is 1, the nested type descriptor will be String, derived from the map value.
660         * <p>If the field is a {@code List<Map<Integer, String>>} and the nesting
661         * level is 2, the nested type descriptor will be String, derived from the map value.
662         * <p>Returns {@code null} if a nested type cannot be obtained because it was not
663         * declared. For example, if the field is a {@code List<?>}, the nested type
664         * descriptor returned will be {@code null}.
665         * @param field the field
666         * @param nestingLevel the nesting level of the collection/array element or
667         * map key/value declaration within the field
668         * @return the nested type descriptor at the specified nesting level,
669         * or {@code null} if it could not be obtained
670         * @throws IllegalArgumentException if the types up to the specified nesting
671         * level are not of collection, array, or map types
672         */
673        @Nullable
674        public static TypeDescriptor nested(Field field, int nestingLevel) {
675                return nested(new TypeDescriptor(field), nestingLevel);
676        }
677
678        /**
679         * Create a type descriptor for a nested type declared within the property.
680         * <p>For example, if the property is a {@code List<String>} and the nesting
681         * level is 1, the nested type descriptor will be {@code String.class}.
682         * <p>If the property is a {@code List<List<String>>} and the nesting level
683         * is 2, the nested type descriptor will also be a {@code String.class}.
684         * <p>If the property is a {@code Map<Integer, String>} and the nesting level
685         * is 1, the nested type descriptor will be String, derived from the map value.
686         * <p>If the property is a {@code List<Map<Integer, String>>} and the nesting
687         * level is 2, the nested type descriptor will be String, derived from the map value.
688         * <p>Returns {@code null} if a nested type cannot be obtained because it was not
689         * declared. For example, if the property is a {@code List<?>}, the nested type
690         * descriptor returned will be {@code null}.
691         * @param property the property
692         * @param nestingLevel the nesting level of the collection/array element or
693         * map key/value declaration within the property
694         * @return the nested type descriptor at the specified nesting level, or
695         * {@code null} if it could not be obtained
696         * @throws IllegalArgumentException if the types up to the specified nesting
697         * level are not of collection, array, or map types
698         */
699        @Nullable
700        public static TypeDescriptor nested(Property property, int nestingLevel) {
701                return nested(new TypeDescriptor(property), nestingLevel);
702        }
703
704        @Nullable
705        private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
706                ResolvableType nested = typeDescriptor.resolvableType;
707                for (int i = 0; i < nestingLevel; i++) {
708                        if (Object.class == nested.getType()) {
709                                // Could be a collection type but we don't know about its element type,
710                                // so let's just assume there is an element type of type Object...
711                        }
712                        else {
713                                nested = nested.getNested(2);
714                        }
715                }
716                if (nested == ResolvableType.NONE) {
717                        return null;
718                }
719                return getRelatedIfResolvable(typeDescriptor, nested);
720        }
721
722        @Nullable
723        private static TypeDescriptor getRelatedIfResolvable(TypeDescriptor source, ResolvableType type) {
724                if (type.resolve() == null) {
725                        return null;
726                }
727                return new TypeDescriptor(type, null, source.getAnnotations());
728        }
729
730
731        /**
732         * Adapter class for exposing a {@code TypeDescriptor}'s annotations as an
733         * {@link AnnotatedElement}, in particular to {@link AnnotatedElementUtils}.
734         * @see AnnotatedElementUtils#isAnnotated(AnnotatedElement, Class)
735         * @see AnnotatedElementUtils#getMergedAnnotation(AnnotatedElement, Class)
736         */
737        private class AnnotatedElementAdapter implements AnnotatedElement, Serializable {
738
739                @Nullable
740                private final Annotation[] annotations;
741
742                public AnnotatedElementAdapter(@Nullable Annotation[] annotations) {
743                        this.annotations = annotations;
744                }
745
746                @Override
747                public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
748                        for (Annotation annotation : getAnnotations()) {
749                                if (annotation.annotationType() == annotationClass) {
750                                        return true;
751                                }
752                        }
753                        return false;
754                }
755
756                @Override
757                @Nullable
758                @SuppressWarnings("unchecked")
759                public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
760                        for (Annotation annotation : getAnnotations()) {
761                                if (annotation.annotationType() == annotationClass) {
762                                        return (T) annotation;
763                                }
764                        }
765                        return null;
766                }
767
768                @Override
769                public Annotation[] getAnnotations() {
770                        return (this.annotations != null ? this.annotations.clone() : EMPTY_ANNOTATION_ARRAY);
771                }
772
773                @Override
774                public Annotation[] getDeclaredAnnotations() {
775                        return getAnnotations();
776                }
777
778                public boolean isEmpty() {
779                        return ObjectUtils.isEmpty(this.annotations);
780                }
781
782                @Override
783                public boolean equals(@Nullable Object other) {
784                        return (this == other || (other instanceof AnnotatedElementAdapter &&
785                                        Arrays.equals(this.annotations, ((AnnotatedElementAdapter) other).annotations)));
786                }
787
788                @Override
789                public int hashCode() {
790                        return Arrays.hashCode(this.annotations);
791                }
792
793                @Override
794                public String toString() {
795                        return TypeDescriptor.this.toString();
796                }
797        }
798
799}