Efficient type plus annotation comparisons during converter retrieval
Issue: SPR-14926
Issue: SPR-12926
(cherry picked from commit f6b8b84)
This commit is contained in:
@@ -231,6 +231,7 @@ public class FormattingConversionService extends GenericConversionService
|
|||||||
|
|
||||||
public AnnotationPrinterConverter(Class<? extends Annotation> annotationType,
|
public AnnotationPrinterConverter(Class<? extends Annotation> annotationType,
|
||||||
AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
|
AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
|
||||||
|
|
||||||
this.annotationType = annotationType;
|
this.annotationType = annotationType;
|
||||||
this.annotationFormatterFactory = annotationFormatterFactory;
|
this.annotationFormatterFactory = annotationFormatterFactory;
|
||||||
this.fieldType = fieldType;
|
this.fieldType = fieldType;
|
||||||
@@ -284,6 +285,7 @@ public class FormattingConversionService extends GenericConversionService
|
|||||||
|
|
||||||
public AnnotationParserConverter(Class<? extends Annotation> annotationType,
|
public AnnotationParserConverter(Class<? extends Annotation> annotationType,
|
||||||
AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
|
AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
|
||||||
|
|
||||||
this.annotationType = annotationType;
|
this.annotationType = annotationType;
|
||||||
this.annotationFormatterFactory = annotationFormatterFactory;
|
this.annotationFormatterFactory = annotationFormatterFactory;
|
||||||
this.fieldType = fieldType;
|
this.fieldType = fieldType;
|
||||||
@@ -350,16 +352,13 @@ public class FormattingConversionService extends GenericConversionService
|
|||||||
if (this == other) {
|
if (this == other) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(other instanceof AnnotationConverterKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
AnnotationConverterKey otherKey = (AnnotationConverterKey) other;
|
AnnotationConverterKey otherKey = (AnnotationConverterKey) other;
|
||||||
return (this.annotation.equals(otherKey.annotation) && this.fieldType.equals(otherKey.fieldType));
|
return (this.fieldType == otherKey.fieldType && this.annotation.equals(otherKey.annotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return (this.annotation.hashCode() + 29 * this.fieldType.hashCode());
|
return (this.fieldType.hashCode() * 29 + this.annotation.hashCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class TypeDescriptor implements Serializable {
|
|||||||
|
|
||||||
private final ResolvableType resolvableType;
|
private final ResolvableType resolvableType;
|
||||||
|
|
||||||
private final AnnotatedElement annotatedElement;
|
private final AnnotatedElementAdapter annotatedElement;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,7 +82,6 @@ public class TypeDescriptor implements Serializable {
|
|||||||
* @param methodParameter the method parameter
|
* @param methodParameter the method parameter
|
||||||
*/
|
*/
|
||||||
public TypeDescriptor(MethodParameter methodParameter) {
|
public TypeDescriptor(MethodParameter methodParameter) {
|
||||||
Assert.notNull(methodParameter, "MethodParameter must not be null");
|
|
||||||
this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
|
this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
|
||||||
this.type = this.resolvableType.resolve(methodParameter.getParameterType());
|
this.type = this.resolvableType.resolve(methodParameter.getParameterType());
|
||||||
this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ?
|
this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ?
|
||||||
@@ -95,7 +94,6 @@ public class TypeDescriptor implements Serializable {
|
|||||||
* @param field the field
|
* @param field the field
|
||||||
*/
|
*/
|
||||||
public TypeDescriptor(Field field) {
|
public TypeDescriptor(Field field) {
|
||||||
Assert.notNull(field, "Field must not be null");
|
|
||||||
this.resolvableType = ResolvableType.forField(field);
|
this.resolvableType = ResolvableType.forField(field);
|
||||||
this.type = this.resolvableType.resolve(field.getType());
|
this.type = this.resolvableType.resolve(field.getType());
|
||||||
this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations());
|
this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations());
|
||||||
@@ -121,6 +119,7 @@ public class TypeDescriptor implements Serializable {
|
|||||||
* @param resolvableType the resolvable type
|
* @param resolvableType the resolvable type
|
||||||
* @param type the backing type (or {@code null} if it should get resolved)
|
* @param type the backing type (or {@code null} if it should get resolved)
|
||||||
* @param annotations the type annotations
|
* @param annotations the type annotations
|
||||||
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) {
|
protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) {
|
||||||
this.resolvableType = resolvableType;
|
this.resolvableType = resolvableType;
|
||||||
@@ -211,7 +210,7 @@ public class TypeDescriptor implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of this type: the fully qualified class name.
|
* Return the name of this type: the fully qualified class name.
|
||||||
*/
|
*/
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return ClassUtils.getQualifiedName(getType());
|
return ClassUtils.getQualifiedName(getType());
|
||||||
@@ -225,7 +224,7 @@ public class TypeDescriptor implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The annotations associated with this type descriptor, if any.
|
* Return the annotations associated with this type descriptor, if any.
|
||||||
* @return the annotations, or an empty array if none
|
* @return the annotations, or an empty array if none
|
||||||
*/
|
*/
|
||||||
public Annotation[] getAnnotations() {
|
public Annotation[] getAnnotations() {
|
||||||
@@ -240,6 +239,11 @@ public class TypeDescriptor implements Serializable {
|
|||||||
* @return <tt>true</tt> if the annotation is present
|
* @return <tt>true</tt> if the annotation is present
|
||||||
*/
|
*/
|
||||||
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
||||||
|
if (this.annotatedElement.isEmpty()) {
|
||||||
|
// Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations()
|
||||||
|
// to return a copy of the array, whereas we can do it more efficiently here.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType);
|
return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +255,11 @@ public class TypeDescriptor implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
|
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
|
||||||
|
if (this.annotatedElement.isEmpty()) {
|
||||||
|
// Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations()
|
||||||
|
// to return a copy of the array, whereas we can do it more efficiently here.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType);
|
return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,37 +443,51 @@ public class TypeDescriptor implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object other) {
|
||||||
if (this == obj) {
|
if (this == other) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(obj instanceof TypeDescriptor)) {
|
if (!(other instanceof TypeDescriptor)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TypeDescriptor other = (TypeDescriptor) obj;
|
TypeDescriptor otherDesc = (TypeDescriptor) other;
|
||||||
if (!ObjectUtils.nullSafeEquals(this.type, other.type)) {
|
if (getType() != otherDesc.getType()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (getAnnotations().length != other.getAnnotations().length) {
|
if (!annotationsMatch(otherDesc)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (Annotation ann : getAnnotations()) {
|
|
||||||
if (!ann.equals(other.getAnnotation(ann.annotationType()))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isCollection() || isArray()) {
|
if (isCollection() || isArray()) {
|
||||||
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor());
|
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor());
|
||||||
}
|
}
|
||||||
else if (isMap()) {
|
else if (isMap()) {
|
||||||
return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) &&
|
return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) &&
|
||||||
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor());
|
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean annotationsMatch(TypeDescriptor otherDesc) {
|
||||||
|
Annotation[] anns = getAnnotations();
|
||||||
|
Annotation[] otherAnns = otherDesc.getAnnotations();
|
||||||
|
if (anns == otherAnns) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (anns.length != otherAnns.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (anns.length > 0) {
|
||||||
|
for (int i = 0; i < anns.length; i++) {
|
||||||
|
if (anns[i] != otherAnns[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getType().hashCode();
|
return getType().hashCode();
|
||||||
@@ -480,6 +503,20 @@ public class TypeDescriptor implements Serializable {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new type descriptor for an object.
|
||||||
|
* <p>Use this factory method to introspect a source object before asking the
|
||||||
|
* conversion system to convert it to some another type.
|
||||||
|
* <p>If the provided object is {@code null}, returns {@code null}, else calls
|
||||||
|
* {@link #valueOf(Class)} to build a TypeDescriptor from the object's class.
|
||||||
|
* @param source the source object
|
||||||
|
* @return the type descriptor
|
||||||
|
*/
|
||||||
|
public static TypeDescriptor forObject(Object source) {
|
||||||
|
return (source != null ? valueOf(source.getClass()) : null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new type descriptor from the given type.
|
* Create a new type descriptor from the given type.
|
||||||
* <p>Use this to instruct the conversion system to convert an object to a
|
* <p>Use this to instruct the conversion system to convert an object to a
|
||||||
@@ -640,19 +677,6 @@ public class TypeDescriptor implements Serializable {
|
|||||||
return nested(new TypeDescriptor(property), nestingLevel);
|
return nested(new TypeDescriptor(property), nestingLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new type descriptor for an object.
|
|
||||||
* <p>Use this factory method to introspect a source object before asking the
|
|
||||||
* conversion system to convert it to some another type.
|
|
||||||
* <p>If the provided object is {@code null}, returns {@code null}, else calls
|
|
||||||
* {@link #valueOf(Class)} to build a TypeDescriptor from the object's class.
|
|
||||||
* @param source the source object
|
|
||||||
* @return the type descriptor
|
|
||||||
*/
|
|
||||||
public static TypeDescriptor forObject(Object source) {
|
|
||||||
return (source != null ? valueOf(source.getClass()) : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
|
private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
|
||||||
ResolvableType nested = typeDescriptor.resolvableType;
|
ResolvableType nested = typeDescriptor.resolvableType;
|
||||||
for (int i = 0; i < nestingLevel; i++) {
|
for (int i = 0; i < nestingLevel; i++) {
|
||||||
@@ -723,6 +747,10 @@ public class TypeDescriptor implements Serializable {
|
|||||||
return getAnnotations();
|
return getAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return ObjectUtils.isEmpty(this.annotations);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return (this == other || (other instanceof AnnotatedElementAdapter &&
|
return (this == other || (other instanceof AnnotatedElementAdapter &&
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -533,7 +533,7 @@ public class TypeDescriptorTests {
|
|||||||
public void elementTypePreserveContext() throws Exception {
|
public void elementTypePreserveContext() throws Exception {
|
||||||
TypeDescriptor desc = new TypeDescriptor(getClass().getField("listPreserveContext"));
|
TypeDescriptor desc = new TypeDescriptor(getClass().getField("listPreserveContext"));
|
||||||
assertEquals(Integer.class, desc.getElementTypeDescriptor().getElementTypeDescriptor().getType());
|
assertEquals(Integer.class, desc.getElementTypeDescriptor().getElementTypeDescriptor().getType());
|
||||||
List<Integer> value = new ArrayList<Integer>(3);
|
List<Integer> value = new ArrayList<>(3);
|
||||||
desc = desc.elementTypeDescriptor(value);
|
desc = desc.elementTypeDescriptor(value);
|
||||||
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
|
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
|
||||||
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
|
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
|
||||||
@@ -551,7 +551,7 @@ public class TypeDescriptorTests {
|
|||||||
public void mapKeyTypePreserveContext() throws Exception {
|
public void mapKeyTypePreserveContext() throws Exception {
|
||||||
TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext"));
|
TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext"));
|
||||||
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementTypeDescriptor().getType());
|
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementTypeDescriptor().getType());
|
||||||
List<Integer> value = new ArrayList<Integer>(3);
|
List<Integer> value = new ArrayList<>(3);
|
||||||
desc = desc.getMapKeyTypeDescriptor(value);
|
desc = desc.getMapKeyTypeDescriptor(value);
|
||||||
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
|
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
|
||||||
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
|
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
|
||||||
@@ -569,7 +569,7 @@ public class TypeDescriptorTests {
|
|||||||
public void mapValueTypePreserveContext() throws Exception {
|
public void mapValueTypePreserveContext() throws Exception {
|
||||||
TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext"));
|
TypeDescriptor desc = new TypeDescriptor(getClass().getField("mapPreserveContext"));
|
||||||
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
|
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType());
|
||||||
List<Integer> value = new ArrayList<Integer>(3);
|
List<Integer> value = new ArrayList<>(3);
|
||||||
desc = desc.getMapValueTypeDescriptor(value);
|
desc = desc.getMapValueTypeDescriptor(value);
|
||||||
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
|
assertEquals(Integer.class, desc.getElementTypeDescriptor().getType());
|
||||||
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
|
assertNotNull(desc.getAnnotation(FieldAnnotation.class));
|
||||||
@@ -598,15 +598,16 @@ public class TypeDescriptorTests {
|
|||||||
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
|
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
|
||||||
assertEquals(t11, t12);
|
assertEquals(t11, t12);
|
||||||
|
|
||||||
TypeDescriptor t13 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
|
MethodParameter testAnnotatedMethod = new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0);
|
||||||
TypeDescriptor t14 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
|
TypeDescriptor t13 = new TypeDescriptor(testAnnotatedMethod);
|
||||||
|
TypeDescriptor t14 = new TypeDescriptor(testAnnotatedMethod);
|
||||||
assertEquals(t13, t14);
|
assertEquals(t13, t14);
|
||||||
|
|
||||||
TypeDescriptor t15 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
|
TypeDescriptor t15 = new TypeDescriptor(testAnnotatedMethod);
|
||||||
TypeDescriptor t16 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethodDifferentAnnotationValue", String.class), 0));
|
TypeDescriptor t16 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethodDifferentAnnotationValue", String.class), 0));
|
||||||
assertNotEquals(t15, t16);
|
assertNotEquals(t15, t16);
|
||||||
|
|
||||||
TypeDescriptor t17 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
|
TypeDescriptor t17 = new TypeDescriptor(testAnnotatedMethod);
|
||||||
TypeDescriptor t18 = new TypeDescriptor(new MethodParameter(getClass().getMethod("test5", String.class), 0));
|
TypeDescriptor t18 = new TypeDescriptor(new MethodParameter(getClass().getMethod("test5", String.class), 0));
|
||||||
assertNotEquals(t17, t18);
|
assertNotEquals(t17, t18);
|
||||||
}
|
}
|
||||||
@@ -841,19 +842,19 @@ public class TypeDescriptorTests {
|
|||||||
|
|
||||||
public List<String> listOfString;
|
public List<String> listOfString;
|
||||||
|
|
||||||
public List<List<String>> listOfListOfString = new ArrayList<List<String>>();
|
public List<List<String>> listOfListOfString = new ArrayList<>();
|
||||||
|
|
||||||
public List<List> listOfListOfUnknown = new ArrayList<List>();
|
public List<List> listOfListOfUnknown = new ArrayList<>();
|
||||||
|
|
||||||
public int[] intArray;
|
public int[] intArray;
|
||||||
|
|
||||||
public List<String>[] arrayOfListOfString;
|
public List<String>[] arrayOfListOfString;
|
||||||
|
|
||||||
public List<Integer> listField = new ArrayList<Integer>();
|
public List<Integer> listField = new ArrayList<>();
|
||||||
|
|
||||||
public Map<String, Integer> mapField = new HashMap<String, Integer>();
|
public Map<String, Integer> mapField = new HashMap<>();
|
||||||
|
|
||||||
public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>();
|
public Map<String, List<Integer>> nestedMapField = new HashMap<>();
|
||||||
|
|
||||||
public Map<List<Integer>, List<Long>> fieldMap;
|
public Map<List<Integer>, List<Long>> fieldMap;
|
||||||
|
|
||||||
@@ -879,9 +880,9 @@ public class TypeDescriptorTests {
|
|||||||
|
|
||||||
public Map<CharSequence, Number> isAssignableMapKeyValueTypes;
|
public Map<CharSequence, Number> isAssignableMapKeyValueTypes;
|
||||||
|
|
||||||
public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<String, Integer>();
|
public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<Integer>();
|
public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<>();
|
||||||
|
|
||||||
|
|
||||||
// Classes designed for test introspection
|
// Classes designed for test introspection
|
||||||
|
|||||||
Reference in New Issue
Block a user