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