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