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<String> field value is assignable to a Collection<CharSequence> 277 * field, but List<Number> is not assignable to List<Integer>. 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<java.lang.Number<} 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<?>} 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<java.lang.Number, java.lang.String<} 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<?, ?>} 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<java.lang.String, java.lang.Number<} 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<?, ?>} 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<String, String> could be converted to a Map<Id, EmailAddress> 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}