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)))); } }