From 2561c8e0aaeea84302430263a7229116baaf3104 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 27 Oct 2021 11:07:00 +0200 Subject: [PATCH] Clarify behavior of `PropertyPath`. Update tests and documentation. Closes #2491 --- .../data/mapping/PropertyPath.java | 69 ++++++++++++++++--- .../data/mapping/PropertyPathUnitTests.java | 12 ++++ 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/PropertyPath.java b/src/main/java/org/springframework/data/mapping/PropertyPath.java index f5ed23a28..02f502154 100644 --- a/src/main/java/org/springframework/data/mapping/PropertyPath.java +++ b/src/main/java/org/springframework/data/mapping/PropertyPath.java @@ -108,7 +108,15 @@ public class PropertyPath implements Streamable { } /** - * Returns the name of the {@link PropertyPath}. + * Returns the first part of the {@link PropertyPath}. + * + *
+	 * {@code
+	 * PropertyPath.from("a.b.c", Some.class).getSegment();
+	 * }
+	 * 
+ * + * will result in {@code "a"} * * @return the name will never be {@literal null}. */ @@ -156,7 +164,15 @@ public class PropertyPath implements Streamable { } /** - * Returns the next nested {@link PropertyPath}. + * Returns the {@link PropertyPath} path that results from removing the first element of the current one. + * + *
+	 * {@code
+	 * System.out.println(PropertyPath.from("a.b.c", Some.class).next().toDotPath());
+	 * }
+	 * 
+ * + * Will result in the output: {@code b.c} * * @return the next nested {@link PropertyPath} or {@literal null} if no nested {@link PropertyPath} available. * @see #hasNext() @@ -214,6 +230,29 @@ public class PropertyPath implements Streamable { return PropertyPath.from(lookup, owningType); } + /** + * Returns an {@link Iterator} that iterates over all the partial property paths with the same leaf type + * but decreasing length. + * + *
+	 * {@code
+	 * PropertyPath propertyPath = PropertyPath.from("a.b.c", Some.class);
+	 * for (p : propertyPath.iterator()) {
+	 *     System.out.println(p.toDotPath());
+	 * };
+	 * }
+	 * 
+ * + * Results in the output: + * + *
+	 * {@code
+	 * a.b.c
+	 * b.c
+	 * c
+	 * }
+	 * 
+ */ public Iterator iterator() { return new Iterator() { @@ -289,11 +328,18 @@ public class PropertyPath implements Streamable { } /** - * Extracts the {@link PropertyPath} chain from the given source {@link String} and type. + * Extracts the {@link PropertyPath} chain from the given source {@link String} and {@link TypeInformation}.
+ * Uses {@link #SPLITTER} by default and {@link #SPLITTER_FOR_QUOTED} for {@link Pattern#quote(String) quoted} + * literals. + *

+ * Separate parts of the path may be separated by {@code "."} or by {@code "_"} or by camel case. When the match to + * properties is ambiguous longer property names are preferred. So for "userAddressCity" the interpretation + * "userAddress.city" is preferred over "user.address.city". + *

* - * @param source - * @param type - * @return + * @param source a String denoting the property path. Must not be {@literal null}. + * @param type the owning type of the property path. Must not be {@literal null}. + * @return a new {@link PropertyPath} guaranteed to be not {@literal null}. */ public static PropertyPath from(String source, Class type) { return from(source, TypeInformation.of(type)); @@ -303,10 +349,15 @@ public class PropertyPath implements Streamable { * Extracts the {@link PropertyPath} chain from the given source {@link String} and {@link TypeInformation}.
* Uses {@link #SPLITTER} by default and {@link #SPLITTER_FOR_QUOTED} for {@link Pattern#quote(String) quoted} * literals. + *

+ * Separate parts of the path may be separated by {@code "."} or by {@code "_"} or by camel case. When the match to + * properties is ambiguous longer property names are preferred. So for "userAddressCity" the interpretation + * "userAddress.city" is preferred over "user.address.city". + *

* - * @param source must not be {@literal null}. - * @param type - * @return + * @param source a String denoting the property path. Must not be {@literal null}. + * @param type the owning type of the property path. Must not be {@literal null}. + * @return a new {@link PropertyPath} guaranteed to be not {@literal null}. */ public static PropertyPath from(String source, TypeInformation type) { diff --git a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java index b11091625..ebb159b92 100755 --- a/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; +import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; /** @@ -31,6 +32,7 @@ import org.springframework.data.util.TypeInformation; * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Jens Schauder */ @SuppressWarnings("unused") class PropertyPathUnitTests { @@ -185,6 +187,16 @@ class PropertyPathUnitTests { assertThat(iterator.hasNext()).isFalse(); } + @Test // GH-2491 + public void nextReturnsPathWithoutFirstElement() { + + PropertyPath propertyPath = PropertyPath.from("bar.user.name", Sample.class); + + final PropertyPath next = propertyPath.next(); + assertThat(next).isNotNull(); + assertThat(next.toDotPath()).isEqualTo("user.name"); + } + @Test // DATACMNS-139, GH-2395 void rejectsInvalidPropertyWithLeadingUnderscore() {