diff --git a/src/main/java/org/springframework/data/mapping/PropertyPath.java b/src/main/java/org/springframework/data/mapping/PropertyPath.java index 81f34bff4..7c3432435 100644 --- a/src/main/java/org/springframework/data/mapping/PropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/PropertyPath.java @@ -50,6 +50,7 @@ public class PropertyPath implements Streamable { private static final String ALL_UPPERCASE = "[A-Z0-9._$]+"; 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 cache = new ConcurrentReferenceHashMap<>(); private final TypeInformation owningType; @@ -81,7 +82,7 @@ public class PropertyPath implements Streamable { Assert.hasText(name, "Name must not be null or empty!"); Assert.notNull(owningType, "Owning type must not be null!"); - Assert.notNull(base, "Perviously found properties must not be null!"); + Assert.notNull(base, "Previously found properties must not be null!"); String propertyName = Introspector.decapitalize(name); TypeInformation propertyType = owningType.getProperty(propertyName); @@ -458,8 +459,7 @@ public class PropertyPath implements Streamable { exception = e; } - Pattern pattern = Pattern.compile("\\p{Lu}\\p{Ll}*$"); - Matcher matcher = pattern.matcher(source); + Matcher matcher = NESTED_PROPERTY_PATTERN.matcher(source); if (matcher.find() && matcher.start() != 0) { diff --git a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java index 128ad222a..6bc790fc5 100755 --- a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java @@ -218,6 +218,20 @@ public class PropertyPathUnitTests { .isThrownBy(() -> from("PropertyThatWillFail4Sure", Foo.class)); } + @Test // GH-2472 + public void acceptsValidPathWithDigits() { + assertThat(from("bar1", Sample.class)).isNotNull(); + assertThat(from("bar1foo", Sample.class)).isNotNull(); + } + + @Test // GH-2472 + public void acceptsValidNestedPathWithDigits() { + assertThat(from("sample.bar1", SampleHolder.class)).isNotNull(); + assertThat(from("sample.bar1foo", SampleHolder.class)).isNotNull(); + assertThat(from("sampleBar1", SampleHolder.class)).isNotNull(); + assertThat(from("sampleBar1foo", SampleHolder.class)).isNotNull(); + } + @Test public void rejectsInvalidProperty() { @@ -353,7 +367,7 @@ public class PropertyPathUnitTests { } @Test // DATACMNS-1199 - public void rejectsNonExistantNestedPath() { + public void rejectsNonExistentNestedPath() { assertThatExceptionOfType(PropertyReferenceException.class) // .isThrownBy(() -> from("user", Bar.class).nested("nonexistant")) // @@ -419,6 +433,13 @@ public class PropertyPathUnitTests { private String userName; private FooBar user; private Bar bar; + private Bar bar1; + private Bar bar1foo; + } + + private class SampleHolder { + + private Sample sample; } private class Sample2 { diff --git a/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java b/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java index e9edf83b2..8b71fb1a9 100755 --- a/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java @@ -425,6 +425,11 @@ class PartTreeUnitTests { assertThat(tree.isDistinct()).isTrue(); } + @Test // GH-2472 + void resolvesPropertyPathsWithNumbers() { + assertThat(new PartTree("findByUserLastname2", UserHolder.class)).isNotNull(); + } + @Test // DATAJPA-324 void resolvesPropertyPathFromGettersOnInterfaces() { assertThat(new PartTree("findByCategoryId", Product.class)).isNotNull(); @@ -735,9 +740,14 @@ class PartTreeUnitTests { return result; } + class UserHolder { + User user; + } + class User { String firstname; String lastname; + String lastname2; double[] location; boolean active; Date birthday;