Skip to content

Commit 902938e

Browse files
committed
smarter guessing of the element type (SPR-7283)
1 parent 379bc5a commit 902938e

File tree

4 files changed

+204
-111
lines changed

4 files changed

+204
-111
lines changed

org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.core.MethodParameter;
2727
import org.springframework.util.Assert;
2828
import org.springframework.util.ClassUtils;
29+
import org.springframework.util.CollectionUtils;
2930
import org.springframework.util.ObjectUtils;
3031

3132
/**
@@ -34,7 +35,7 @@
3435
* @author Keith Donald
3536
* @author Andy Clement
3637
* @author Juergen Hoeller
37-
* @since 3.0
38+
* @since 3.0
3839
*/
3940
public class TypeDescriptor {
4041

@@ -44,7 +45,7 @@ public class TypeDescriptor {
4445
private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>();
4546

4647
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
47-
48+
4849
static {
4950
typeDescriptorCache.put(boolean.class, new TypeDescriptor(boolean.class));
5051
typeDescriptorCache.put(Boolean.class, new TypeDescriptor(Boolean.class));
@@ -75,13 +76,13 @@ public class TypeDescriptor {
7576
private Object value;
7677

7778
private TypeDescriptor elementType;
78-
79+
7980
private TypeDescriptor mapKeyType;
80-
81+
8182
private TypeDescriptor mapValueType;
8283

8384
private Annotation[] annotations;
84-
85+
8586

8687
/**
8788
* Create a new type descriptor from a method or constructor parameter.
@@ -248,15 +249,15 @@ public Class<?> getElementType() {
248249
* Return the element type as a type descriptor.
249250
*/
250251
public synchronized TypeDescriptor getElementTypeDescriptor() {
251-
if (elementType == null) {
252-
elementType = forElementType(resolveElementType());
252+
if (this.elementType == null) {
253+
this.elementType = forElementType(resolveElementType());
253254
}
254-
return elementType;
255+
return this.elementType;
255256
}
256257

257258
/**
258-
* Return the element type as a type descriptor; if the element type is null (cannot be determined),
259-
* the type descriptor is derived from the element argument.
259+
* Return the element type as a type descriptor. If the element type is null
260+
* (cannot be determined), the type descriptor is derived from the element argument.
260261
* @param element the element
261262
* @return the element type descriptor
262263
*/
@@ -273,7 +274,7 @@ public boolean isMap() {
273274
}
274275

275276
/**
276-
* Is this descriptor for a map where the key type and value type are known?
277+
* Is this descriptor for a map where the key type and value type are known?
277278
*/
278279
public boolean isMapEntryTypeKnown() {
279280
return (isMap() && getMapKeyType() != null && getMapValueType() != null);
@@ -291,42 +292,43 @@ public Class<?> getMapKeyType() {
291292
* Returns map key type as a type descriptor.
292293
*/
293294
public synchronized TypeDescriptor getMapKeyTypeDescriptor() {
294-
if (mapKeyType == null) {
295-
mapKeyType = forElementType(resolveMapKeyType());
295+
if (this.mapKeyType == null) {
296+
this.mapKeyType = forElementType(resolveMapKeyType());
296297
}
297-
return mapKeyType;
298+
return this.mapKeyType;
298299
}
299300

300301
/**
301-
* Return the map key type as a type descriptor; if the key type is null (cannot be determined), the type descriptor is derived from the key argument.
302+
* Return the map key type as a type descriptor. If the key type is null
303+
* (cannot be determined), the type descriptor is derived from the key argument.
302304
* @param key the key
303305
* @return the map key type descriptor
304306
*/
305307
public TypeDescriptor getMapKeyTypeDescriptor(Object key) {
306308
TypeDescriptor keyType = getMapKeyTypeDescriptor();
307309
return keyType != TypeDescriptor.NULL ? keyType : TypeDescriptor.forObject(key);
308310
}
309-
311+
310312
/**
311313
* Determine the generic value type of the wrapped Map parameter/field, if any.
312314
* @return the generic type, or <code>null</code> if none
313315
*/
314316
public Class<?> getMapValueType() {
315317
return getMapValueTypeDescriptor().getType();
316318
}
317-
319+
318320
/**
319321
* Returns map value type as a type descriptor.
320322
*/
321323
public synchronized TypeDescriptor getMapValueTypeDescriptor() {
322324
if (this.mapValueType == null) {
323-
mapValueType = forElementType(resolveMapValueType());
325+
this.mapValueType = forElementType(resolveMapValueType());
324326
}
325327
return this.mapValueType;
326328
}
327329

328330
/**
329-
* Return the map value type as a type descriptor; if the value type is null
331+
* Return the map value type as a type descriptor. If the value type is null
330332
* (cannot be determined), the type descriptor is derived from the value argument.
331333
* @param value the value
332334
* @return the map value type descriptor
@@ -355,7 +357,7 @@ public Annotation getAnnotation(Class<? extends Annotation> annotationType) {
355357
return annotation;
356358
}
357359
}
358-
return null;
360+
return null;
359361
}
360362

361363
/**
@@ -423,14 +425,14 @@ else if (isMap()) {
423425
ObjectUtils.nullSafeEquals(getMapValueType(), td.getMapValueType());
424426
}
425427
else {
426-
return annotatedTypeEquals;
428+
return annotatedTypeEquals;
427429
}
428430
}
429-
431+
430432
public int hashCode() {
431433
return getType().hashCode();
432434
}
433-
435+
434436
/**
435437
* A textual representation of the type descriptor (eg. Map<String,Foo>) for use in messages
436438
*/
@@ -459,7 +461,7 @@ public String toString() {
459461
}
460462
else if (isCollection()) {
461463
Class<?> elementType = getElementType();
462-
builder.append("<").append(elementType != null ? ClassUtils.getQualifiedName(elementType) : "?").append(">");
464+
builder.append("<").append(elementType != null ? ClassUtils.getQualifiedName(elementType) : "?").append(">");
463465
}
464466
builder.append("]");
465467
return builder.toString();
@@ -468,7 +470,7 @@ else if (isCollection()) {
468470

469471

470472
// internal helpers
471-
473+
472474
private Class<?> resolveElementType() {
473475
if (isArray()) {
474476
return getType().getComponentType();
@@ -478,9 +480,9 @@ else if (isCollection()) {
478480
}
479481
else {
480482
return null;
481-
}
483+
}
482484
}
483-
485+
484486
@SuppressWarnings("unchecked")
485487
private Class<?> resolveCollectionElementType() {
486488
if (this.field != null) {
@@ -490,17 +492,14 @@ else if (this.methodParameter != null) {
490492
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
491493
}
492494
else if (this.value instanceof Collection) {
493-
Collection coll = (Collection) this.value;
494-
if (!coll.isEmpty()) {
495-
Object elem = coll.iterator().next();
496-
if (elem != null) {
497-
return elem.getClass();
498-
}
495+
Class<?> elementType = CollectionUtils.findCommonElementType((Collection) this.value);
496+
if (elementType != null) {
497+
return elementType;
499498
}
500499
}
501-
return (this.type != null ? GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type) : null);
500+
return (this.type != null ? GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection>) this.type) : null);
502501
}
503-
502+
504503
@SuppressWarnings("unchecked")
505504
private Class<?> resolveMapKeyType() {
506505
if (this.field != null) {
@@ -510,12 +509,9 @@ else if (this.methodParameter != null) {
510509
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
511510
}
512511
else if (this.value instanceof Map<?, ?>) {
513-
Map<?, ?> map = (Map<?, ?>) this.value;
514-
if (!map.isEmpty()) {
515-
Object key = map.keySet().iterator().next();
516-
if (key != null) {
517-
return key.getClass();
518-
}
512+
Class<?> keyType = CollectionUtils.findCommonElementType(((Map<?, ?>) this.value).keySet());
513+
if (keyType != null) {
514+
return keyType;
519515
}
520516
}
521517
return (this.type != null && isMap() ? GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map>) this.type) : null);
@@ -530,17 +526,14 @@ else if (this.methodParameter != null) {
530526
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
531527
}
532528
else if (this.value instanceof Map<?, ?>) {
533-
Map<?, ?> map = (Map<?, ?>) this.value;
534-
if (!map.isEmpty()) {
535-
Object val = map.values().iterator().next();
536-
if (val != null) {
537-
return val.getClass();
538-
}
529+
Class<?> valueType = CollectionUtils.findCommonElementType(((Map<?, ?>) this.value).values());
530+
if (valueType != null) {
531+
return valueType;
539532
}
540533
}
541534
return (isMap() && this.type != null ? GenericCollectionTypeResolver.getMapValueType((Class<? extends Map>) this.type) : null);
542535
}
543-
536+
544537
private Annotation[] resolveAnnotations() {
545538
if (this.field != null) {
546539
return this.field.getAnnotations();

0 commit comments

Comments
 (0)