From a73e2ed3e0686927fabe10e46dcf47dcf384103c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Sun, 9 Oct 2011 13:03:50 +0200 Subject: [PATCH] DATACMNS-85 - Extended PersistenPropertyPath. PersistentPropertyPath got a few new methods to build String representations of it. Beyond that we introduced accessor methods for the base and leaf path as well as the ability to detect base paths and create path extensions based on a given base path. --- .../data/mapping/PropertyPath.java | 2 +- .../DefaultPersistentPropertyPath.java | 140 +++++++++++++++--- .../context/PersistentPropertyPath.java | 63 +++++++- .../DefaultPersistenPropertyPathUnitTest.java | 47 +++++- 4 files changed, 215 insertions(+), 37 deletions(-) diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java index 6daba9ad5..6cca43841 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java @@ -149,7 +149,7 @@ public class PropertyPath implements Iterable { } /** - * Returns the {@link PropertyPath} path in dot notation. + * Returns the {@link PropertyPath} in dot notation. * * @return */ diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java index 759b3192b..fd4ce11c3 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/DefaultPersistentPropertyPath.java @@ -26,50 +26,144 @@ import org.springframework.util.StringUtils; /** * Abstraction of a path of {@link PersistentProperty}s. - * + * * @author Oliver Gierke */ class DefaultPersistentPropertyPath> implements PersistentPropertyPath { - - private final Iterable properties; + + private enum PropertyNameConverter implements Converter, String> { + + INSTANCE; + + public String convert(PersistentProperty source) { + return source.getName(); + } + } + + private final List properties; /** * Creates a new {@link DefaultPersistentPropertyPath} for the given {@link PersistentProperty}s. * * @param properties must not be {@literal null}. */ - public DefaultPersistentPropertyPath(Iterable properties) { + public DefaultPersistentPropertyPath(List properties) { Assert.notNull(properties); + Assert.isTrue(!properties.isEmpty()); this.properties = properties; } - + /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath() */ public String toDotPath() { - return toDotPath(new Converter() { - public String convert(T source) { - return source.getName(); - } - }); + return toPath(null, null); } - + /* * (non-Javadoc) * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath(org.springframework.core.convert.converter.Converter) */ public String toDotPath(Converter converter) { - + return toPath(null, converter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String) + */ + public String toPath(String delimiter) { + return toPath(delimiter, null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String, org.springframework.core.convert.converter.Converter) + */ + public String toPath(String delimiter, Converter converter) { + + @SuppressWarnings("unchecked") + Converter converterToUse = (Converter) (converter == null ? PropertyNameConverter.INSTANCE + : converter); + String delimiterToUse = delimiter == null ? "." : delimiter; + List result = new ArrayList(); - + for (T property : properties) { - result.add(converter.convert(property)); + result.add(converterToUse.convert(property)); } - - return StringUtils.collectionToDelimitedString(result, "."); + + return StringUtils.collectionToDelimitedString(result, delimiterToUse); } - + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#getLeafProperty() + */ + public T getLeafProperty() { + return properties.get(properties.size() - 1); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#getBaseProperty() + */ + public T getBaseProperty() { + return properties.get(0); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#isBasePathOf(org.springframework.data.mapping.context.PersistentPropertyPath) + */ + public boolean isBasePathOf(PersistentPropertyPath path) { + + if (path == null) { + return false; + } + + Iterator iterator = path.iterator(); + + for (T property : this) { + + if (!iterator.hasNext()) { + return false; + } + + T reference = iterator.next(); + + if (!property.equals(reference)) { + return false; + } + } + + return true; + } + + /* (non-Javadoc) + * @see org.springframework.data.mapping.context.PersistentPropertyPath#getExtensionForBaseOf(org.springframework.data.mapping.context.PersistentPropertyPath) + */ + public PersistentPropertyPath getExtensionForBaseOf(PersistentPropertyPath base) { + + if (!base.isBasePathOf(this)) { + return this; + } + + List properties = new ArrayList(); + Iterator iterator = iterator(); + + for (@SuppressWarnings("unused") T candidate : base) { + iterator.next(); + } + + while (iterator.hasNext()) { + properties.add(iterator.next()); + } + + return new DefaultPersistentPropertyPath(properties); + } + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() @@ -77,27 +171,27 @@ class DefaultPersistentPropertyPath> implements public Iterator iterator() { return properties.iterator(); } - + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { - + if (this == obj) { return true; } - + if (obj == null || !getClass().equals(obj.getClass())) { return false; } - + DefaultPersistentPropertyPath that = (DefaultPersistentPropertyPath) obj; - + return this.properties.equals(that.properties); } - + /* * (non-Javadoc) * @see java.lang.Object#hashCode() diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java index aca089213..4cfe13c5c 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPath.java @@ -15,20 +15,18 @@ */ package org.springframework.data.mapping.context; -import java.util.Iterator; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.mapping.PersistentProperty; /** * Abstraction of a path of {@link PersistentProperty}s. - * + * * @author Oliver Gierke */ public interface PersistentPropertyPath> extends Iterable { /** - * Returns the dot based path notation using the {@link PersistentProperty}'s name attribute. + * Returns the dot based path notation using {@link PersistentProperty#getName()}. * * @return */ @@ -43,9 +41,58 @@ public interface PersistentPropertyPath> extends */ String toDotPath(Converter converter); - /* - * (non-Javadoc) - * @see java.lang.Iterable#iterator() + /** + * Returns a {@link String} path with the given delimiter based on the {@link PersistentProperty#getName()}. + * + * @param delimiter will default to {@code .} if {@literal null} is given. + * @return + */ + String toPath(String delimiter); + + /** + * Returns a {@link String} path with the given delimiter using the given {@link Converter} for + * {@link PersistentProperty} to String conversion. + * + * @param delimiter will default to {@code .} if {@literal null} is given. + * @param converter will default to use {@link PersistentProperty#getName()}. + * @return + */ + String toPath(String delimiter, Converter converter); + + /** + * Returns the last property in the {@link PersistentPropertyPath}. So for {@code foo.bar} it will return the + * {@link PersistentProperty} for {@code bar}. For a simple {@code foo} it returns {@link PersistentProperty} for + * {@code foo}. + * + * @return + */ + T getLeafProperty(); + + /** + * Returns the first property in the {@link PersistentPropertyPath}. So for {@code foo.bar} it will return the + * {@link PersistentProperty} for {@code foo}. For a simple {@code foo} it returns {@link PersistentProperty} for + * {@code foo}. + * + * @return + */ + T getBaseProperty(); + + /** + * Returns whether the given {@link PersistentPropertyPath} is a base path of the current one. This means that the + * current {@link PersistentPropertyPath} is basically an extension of the given one. + * + * @param path + * @return + */ + boolean isBasePathOf(PersistentPropertyPath path); + + /** + * Returns the sub-path of the current one as if it was based on the given base path. So for a current path + * {@code foo.bar} and a given base {@code foo} it would return {@code bar}. If the given path is not a base of the + * the current one the current {@link PersistentPropertyPath} will be returned as is. + * + * @param base + * @return */ - Iterator iterator(); + PersistentPropertyPath getExtensionForBaseOf(PersistentPropertyPath base); } \ No newline at end of file diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/DefaultPersistenPropertyPathUnitTest.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/DefaultPersistenPropertyPathUnitTest.java index 2279229c9..3644d5b20 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/DefaultPersistenPropertyPathUnitTest.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/DefaultPersistenPropertyPathUnitTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.*; import java.util.Arrays; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -43,6 +44,16 @@ public class DefaultPersistenPropertyPathUnitTest converter; + + PersistentPropertyPath oneLeg; + PersistentPropertyPath twoLegs; + + @Before + @SuppressWarnings("unchecked") + public void setUp() { + oneLeg = new DefaultPersistentPropertyPath(Arrays.asList(first)); + twoLegs = new DefaultPersistentPropertyPath(Arrays.asList(first, second)); + } @Test(expected = IllegalArgumentException.class) public void rejectsNullProperties() { @@ -50,14 +61,12 @@ public class DefaultPersistenPropertyPathUnitTest path = new DefaultPersistentPropertyPath(Arrays.asList(first, second)); - assertThat(path.toDotPath(), is("foo.bar")); + assertThat(twoLegs.toDotPath(), is("foo.bar")); } @Test @@ -66,7 +75,35 @@ public class DefaultPersistenPropertyPathUnitTest path = new DefaultPersistentPropertyPath(Arrays.asList(first, second)); - assertThat(path.toDotPath(converter), is("foo.foo")); + assertThat(twoLegs.toDotPath(converter), is("foo.foo")); + } + + @Test + public void returnsCorrectLeafProperty() { + + assertThat(twoLegs.getLeafProperty(), is(second)); + assertThat(oneLeg.getLeafProperty(), is(first)); + } + + @Test + public void returnsCorrectBaseProperty() { + + assertThat(twoLegs.getBaseProperty(), is(first)); + assertThat(oneLeg.getBaseProperty(), is(first)); + } + + @Test + public void detectsBasePathCorrectly() { + + assertThat(oneLeg.isBasePathOf(twoLegs), is(true)); + assertThat(twoLegs.isBasePathOf(oneLeg), is(false)); + } + + @Test + @SuppressWarnings("unchecked") + public void calculatesExtensionCorrectly() { + + PersistentPropertyPath extension = twoLegs.getExtensionForBaseOf(oneLeg); + assertThat(extension, is((PersistentPropertyPath) new DefaultPersistentPropertyPath(Arrays.asList(second)))); } }