Skip to content

Commit a9e3b2c

Browse files
committed
Clarify behaviour of PropertyPath, both in tests and documentation.
1 parent a11340b commit a9e3b2c

File tree

2 files changed

+120
-72
lines changed

2 files changed

+120
-72
lines changed

src/main/java/org/springframework/data/mapping/PropertyPath.java

Lines changed: 109 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* @author Christoph Strobl
4242
* @author Mark Paluch
4343
* @author Mariusz Mączkowski
44+
* @author Jens Schauder
4445
*/
4546
public class PropertyPath implements Streamable<PropertyPath> {
4647

@@ -108,12 +109,53 @@ public TypeInformation<?> getOwningType() {
108109
}
109110

110111
/**
111-
* Returns the name of the {@link PropertyPath}.
112+
* Extracts the {@link PropertyPath} chain from the given source {@link String} and {@link TypeInformation}. <br />
113+
* Uses {@link #SPLITTER} by default and {@link #SPLITTER_FOR_QUOTED} for {@link Pattern#quote(String) quoted}
114+
* literals. Separate parts of the path may be separated by {@code "."} or by {@code "_"} or by camel case. When the
115+
* match to properties is ambiguous longer property names are preferred. So for "userAddressCity" the interpretation
116+
* "userAddress.city" is preferred over "user.address.city".
112117
*
113-
* @return the name will never be {@literal null}.
118+
* @param source must not be {@literal null}.
119+
* @param type
120+
* @return
114121
*/
115-
public String getSegment() {
116-
return name;
122+
public static PropertyPath from(String source, TypeInformation<?> type) {
123+
124+
Assert.hasText(source, "Source must not be null or empty!");
125+
Assert.notNull(type, "TypeInformation must not be null or empty!");
126+
127+
return cache.computeIfAbsent(Key.of(type, source), it -> {
128+
129+
List<String> iteratorSource = new ArrayList<>();
130+
131+
Matcher matcher = isQuoted(it.path) ? SPLITTER_FOR_QUOTED.matcher(it.path.replace("\\Q", "").replace("\\E", ""))
132+
: SPLITTER.matcher("_" + it.path);
133+
134+
while (matcher.find()) {
135+
iteratorSource.add(matcher.group(1));
136+
}
137+
138+
Iterator<String> parts = iteratorSource.iterator();
139+
140+
PropertyPath result = null;
141+
Stack<PropertyPath> current = new Stack<>();
142+
143+
while (parts.hasNext()) {
144+
if (result == null) {
145+
result = create(parts.next(), it.type, current);
146+
current.push(result);
147+
} else {
148+
current.push(create(parts.next(), current));
149+
}
150+
}
151+
152+
if (result == null) {
153+
throw new IllegalStateException(
154+
String.format("Expected parsing to yield a PropertyPath from %s but got null!", source));
155+
}
156+
157+
return result;
158+
});
117159
}
118160

119161
/**
@@ -156,14 +198,20 @@ public TypeInformation<?> getTypeInformation() {
156198
}
157199

158200
/**
159-
* Returns the next nested {@link PropertyPath}.
201+
* Returns the first part of the {@link PropertyPath}.
160202
*
161-
* @return the next nested {@link PropertyPath} or {@literal null} if no nested {@link PropertyPath} available.
162-
* @see #hasNext()
203+
* <pre>
204+
* {@code
205+
* PropertyPath.from("a.b.c", Some.class).getSegment();
206+
* }
207+
* </pre>
208+
*
209+
* will result in {@code "a"}
210+
*
211+
* @return the name will never be {@literal null}.
163212
*/
164-
@Nullable
165-
public PropertyPath next() {
166-
return next;
213+
public String getSegment() {
214+
return name;
167215
}
168216

169217
/**
@@ -214,37 +262,23 @@ public PropertyPath nested(String path) {
214262
return PropertyPath.from(lookup, owningType);
215263
}
216264

217-
/*
218-
* (non-Javadoc)
219-
* @see java.lang.Iterable#iterator()
265+
/**
266+
* Returns the {@link PropertyPath} path that results from removing the first element of the current one.
267+
*
268+
* <pre>
269+
* {@code
270+
* System.out.println(PropertyPath.from("a.b.c", Some.class).next().toDotPath());
271+
* }
272+
* </pre>
273+
*
274+
* Will result in the output: {@code b.c}
275+
*
276+
* @return the next nested {@link PropertyPath} or {@literal null} if no nested {@link PropertyPath} available.
277+
* @see #hasNext()
220278
*/
221-
public Iterator<PropertyPath> iterator() {
222-
223-
return new Iterator<PropertyPath>() {
224-
225-
private @Nullable PropertyPath current = PropertyPath.this;
226-
227-
public boolean hasNext() {
228-
return current != null;
229-
}
230-
231-
@Nullable
232-
public PropertyPath next() {
233-
234-
PropertyPath result = current;
235-
236-
if (result == null) {
237-
return null;
238-
}
239-
240-
this.current = result.next();
241-
return result;
242-
}
243-
244-
public void remove() {
245-
throw new UnsupportedOperationException();
246-
}
247-
};
279+
@Nullable
280+
public PropertyPath next() {
281+
return next;
248282
}
249283

250284
/*
@@ -332,51 +366,55 @@ public static PropertyPath from(String source, Class<?> type) {
332366
}
333367

334368
/**
335-
* Extracts the {@link PropertyPath} chain from the given source {@link String} and {@link TypeInformation}. <br />
336-
* Uses {@link #SPLITTER} by default and {@link #SPLITTER_FOR_QUOTED} for {@link Pattern#quote(String) quoted}
337-
* literals.
369+
* Returns an {@link Iterator<PropertyPath>} that iterates over all the partial property paths with the same leaf type
370+
* but decreasing length.
338371
*
339-
* @param source must not be {@literal null}.
340-
* @param type
341-
* @return
372+
* <pre>
373+
* {@code
374+
* PropertyPath propertyPath = PropertyPath.from("a.b.c", Some.class);
375+
* for (p : propertyPath.iterator()) {
376+
* System.out.println(p.toDotPath());
377+
* };
378+
* }
379+
* </pre>
380+
*
381+
* Results in the output:
382+
*
383+
* <pre>
384+
* {@code
385+
* a.b.c
386+
* b.c
387+
* c
388+
* }
389+
* </pre>
342390
*/
343-
public static PropertyPath from(String source, TypeInformation<?> type) {
344-
345-
Assert.hasText(source, "Source must not be null or empty!");
346-
Assert.notNull(type, "TypeInformation must not be null or empty!");
347-
348-
return cache.computeIfAbsent(Key.of(type, source), it -> {
391+
public Iterator<PropertyPath> iterator() {
349392

350-
List<String> iteratorSource = new ArrayList<>();
393+
return new Iterator<PropertyPath>() {
351394

352-
Matcher matcher = isQuoted(it.path) ? SPLITTER_FOR_QUOTED.matcher(it.path.replace("\\Q", "").replace("\\E", ""))
353-
: SPLITTER.matcher("_" + it.path);
395+
private @Nullable PropertyPath current = PropertyPath.this;
354396

355-
while (matcher.find()) {
356-
iteratorSource.add(matcher.group(1));
397+
public boolean hasNext() {
398+
return current != null;
357399
}
358400

359-
Iterator<String> parts = iteratorSource.iterator();
401+
@Nullable
402+
public PropertyPath next() {
360403

361-
PropertyPath result = null;
362-
Stack<PropertyPath> current = new Stack<>();
404+
PropertyPath result = current;
363405

364-
while (parts.hasNext()) {
365406
if (result == null) {
366-
result = create(parts.next(), it.type, current);
367-
current.push(result);
368-
} else {
369-
current.push(create(parts.next(), current));
407+
return null;
370408
}
371-
}
372409

373-
if (result == null) {
374-
throw new IllegalStateException(
375-
String.format("Expected parsing to yield a PropertyPath from %s but got null!", source));
410+
this.current = result.next();
411+
return result;
376412
}
377413

378-
return result;
379-
});
414+
public void remove() {
415+
throw new UnsupportedOperationException();
416+
}
417+
};
380418
}
381419

382420
private static boolean isQuoted(String source) {

src/test/java/org/springframework/data/mapping/PropertyPathUnitTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.regex.Pattern;
2525

2626
import org.junit.jupiter.api.Test;
27-
2827
import org.springframework.data.util.ClassTypeInformation;
2928
import org.springframework.data.util.TypeInformation;
3029

@@ -34,6 +33,7 @@
3433
* @author Oliver Gierke
3534
* @author Christoph Strobl
3635
* @author Mark Paluch
36+
* @author Jens Schauder
3737
*/
3838
@SuppressWarnings("unused")
3939
public class PropertyPathUnitTests {
@@ -188,6 +188,16 @@ public void returnsCorrectIteratorForMultipleElement() {
188188
assertThat(iterator.hasNext()).isFalse();
189189
}
190190

191+
@Test
192+
public void nextReturnsPathWithoutFirstElement() {
193+
194+
PropertyPath propertyPath = PropertyPath.from("bar.user.name", Sample.class);
195+
196+
final PropertyPath next = propertyPath.next();
197+
assertThat(next).isNotNull();
198+
assertThat(next.toDotPath()).isEqualTo("user.name");
199+
}
200+
191201
@Test // DATACMNS-139
192202
public void rejectsInvalidPropertyWithLeadingUnderscore() {
193203

0 commit comments

Comments
 (0)