Fallback to StringUtils.uncapitalize(…) when looking up property paths.

Naming restrictions for property paths used in query method names requite capitalization of the first letter regardless whether the property name uses a second-letter uppercase form (zIndex -> ZIndex, qCode -> QCode). In such cases, Introspector.decapitalize(…) shortcuts to non-decapitalization as it checks the second letter casing.

This leads to the case that the property name cannot be resolved, assuming proper property naming (getzIndex(), zIndex()).

Falling back to StringUtils.uncapitalize() allows catching such properties.

Closes: #1851
Original Pull Request: #2940
This commit is contained in:
Mark Paluch
2023-09-22 09:34:06 +02:00
committed by Christoph Strobl
parent aac6b9dd7d
commit e04f0b0deb
4 changed files with 58 additions and 37 deletions

View File

@@ -50,7 +50,7 @@ public class PropertyPath implements Streamable<PropertyPath> {
private static final Pattern SPLITTER = Pattern.compile("(?:[%s]?([%s]*?[^%s]+))".replaceAll("%s", DELIMITERS));
private static final Pattern SPLITTER_FOR_QUOTED = Pattern.compile("(?:[%s]?([%s]*?[^%s]+))".replaceAll("%s", "\\."));
private static final Pattern NESTED_PROPERTY_PATTERN = Pattern.compile("\\p{Lu}[\\p{Ll}\\p{Nd}]*$");
private static final Map<Key, PropertyPath> cache = new ConcurrentReferenceHashMap<>();
private static final Map<Property, PropertyPath> cache = new ConcurrentReferenceHashMap<>();
private final TypeInformation<?> owningType;
private final String name;
@@ -83,19 +83,31 @@ public class PropertyPath implements Streamable<PropertyPath> {
Assert.notNull(owningType, "Owning type must not be null");
Assert.notNull(base, "Previously found properties must not be null");
String propertyName = Introspector.decapitalize(name);
TypeInformation<?> propertyType = owningType.getProperty(propertyName);
String decapitalized = Introspector.decapitalize(name);
Property property = lookupProperty(owningType, decapitalized);
if (propertyType == null) {
throw new PropertyReferenceException(propertyName, owningType, base);
if (property == null) {
property = lookupProperty(owningType, StringUtils.uncapitalize(name));
}
if (property == null) {
throw new PropertyReferenceException(decapitalized, owningType, base);
}
this.owningType = owningType;
this.typeInformation = propertyType;
this.isCollection = propertyType.isCollectionLike();
this.name = propertyName;
this.actualTypeInformation = propertyType.getActualType() == null ? propertyType
: propertyType.getRequiredActualType();
this.name = property.path();
this.typeInformation = property.type();
this.isCollection = this.typeInformation.isCollectionLike();
this.actualTypeInformation = this.typeInformation.getActualType() == null ? this.typeInformation
: this.typeInformation.getRequiredActualType();
}
@Nullable
private static Property lookupProperty(TypeInformation<?> owningType, String name) {
TypeInformation<?> propertyType = owningType.getProperty(name);
return propertyType != null ? new Property(propertyType, name) : null;
}
/**
@@ -351,7 +363,7 @@ public class PropertyPath implements Streamable<PropertyPath> {
Assert.hasText(source, "Source must not be null or empty");
Assert.notNull(type, "TypeInformation must not be null or empty");
return cache.computeIfAbsent(new Key(type, source), it -> {
return cache.computeIfAbsent(new Property(type, source), it -> {
List<String> iteratorSource = new ArrayList<>();
@@ -487,5 +499,6 @@ public class PropertyPath implements Streamable<PropertyPath> {
return String.format("%s.%s", owningType.getType().getSimpleName(), toDotPath());
}
private record Key(TypeInformation<?> type, String path) {};
private record Property(TypeInformation<?> type, String path) {
};
}

View File

@@ -370,8 +370,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
var field = ReflectionUtils.findField(rawType, fieldname);
return field != null ? Optional.of(TypeInformation.of(ResolvableType.forField(field, resolvableType)))
: Optional.ofNullable(BeanUtils.getPropertyDescriptor(rawType, fieldname)).map(it -> from(it, rawType))
.map(TypeInformation::of);
: Optional.ofNullable(BeanUtils.getPropertyDescriptor(rawType, fieldname))
.filter(it -> it.getName().equals(fieldname)).map(it -> from(it, rawType)).map(TypeInformation::of);
}
private ResolvableType from(PropertyDescriptor descriptor, Class<?> rawType) {