diff --git a/src/main/java/org/springframework/data/mapping/TypedPropertyPath.java b/src/main/java/org/springframework/data/core/TypedPropertyPath.java similarity index 97% rename from src/main/java/org/springframework/data/mapping/TypedPropertyPath.java rename to src/main/java/org/springframework/data/core/TypedPropertyPath.java index 36df105ac..e0f5d2576 100644 --- a/src/main/java/org/springframework/data/mapping/TypedPropertyPath.java +++ b/src/main/java/org/springframework/data/core/TypedPropertyPath.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mapping; +package org.springframework.data.core; import java.io.Serializable; import java.util.Collections; import java.util.Iterator; import org.jspecify.annotations.Nullable; -import org.springframework.data.util.TypeInformation; /** * Type-safe representation of a property path expressed through method references. @@ -32,13 +31,13 @@ import org.springframework.data.util.TypeInformation; *

* Typed property paths can be created directly they are accepted used or conveniently using the static factory method * {@link #of(TypedPropertyPath)} with method references: - * + * *

  * PropertyPath.of(Person::getName);
  * 
- * + * * Property paths can be composed to navigate nested properties using {@link #then(TypedPropertyPath)}: - * + * *
  * PropertyPath.of(Person::getAddress).then(Address::getCountry).then(Country::getName);
  * 
@@ -56,7 +55,7 @@ import org.springframework.data.util.TypeInformation; *

* Note that using lambda expressions requires bytecode analysis of the declaration site classes and therefore presence * of their class files. - * + * * @param the owning type of the property path segment, but typically the root type for composed property paths. * @param

the property value type at this path segment. * @author Mark Paluch diff --git a/src/main/java/org/springframework/data/mapping/TypedPropertyPaths.java b/src/main/java/org/springframework/data/core/TypedPropertyPaths.java similarity index 98% rename from src/main/java/org/springframework/data/mapping/TypedPropertyPaths.java rename to src/main/java/org/springframework/data/core/TypedPropertyPaths.java index 55d2f3f2a..18c36769f 100644 --- a/src/main/java/org/springframework/data/mapping/TypedPropertyPaths.java +++ b/src/main/java/org/springframework/data/core/TypedPropertyPaths.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mapping; +package org.springframework.data.core; import java.beans.Introspector; import java.beans.PropertyDescriptor; @@ -46,8 +46,6 @@ import org.springframework.asm.Type; import org.springframework.beans.BeanUtils; import org.springframework.core.ResolvableType; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.mapping.model.Property; -import org.springframework.data.util.TypeInformation; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; @@ -59,7 +57,7 @@ import org.springframework.util.ReflectionUtils; class TypedPropertyPaths { private static final Map> lambdas = new WeakHashMap<>(); - private static final Map>> resolved = new WeakHashMap<>(); + private static final Map, ResolvedTypedPropertyPath>> resolved = new WeakHashMap<>(); /** * Retrieve {@link PropertyPathInformation} for a given {@link TypedPropertyPath}. @@ -92,7 +90,7 @@ class TypedPropertyPaths { return lambda; } - Map> cache; + Map, ResolvedTypedPropertyPath> cache; synchronized (resolved) { cache = resolved.computeIfAbsent(lambda.getClass().getClassLoader(), k -> new ConcurrentReferenceHashMap<>()); } @@ -168,7 +166,7 @@ class TypedPropertyPaths { Field field = ReflectionUtils.findField(owner, fieldName, fieldType); if (field == null) { - throw new IllegalArgumentException("Field %s.%s() not found".formatted(owner.getName(), field)); + throw new IllegalArgumentException("Field %s.%s() not found".formatted(owner.getName(), fieldName)); } return new PropertyPathInformation(TypeInformation.of(owner), diff --git a/src/main/java/org/springframework/data/domain/ExampleMatcher.java b/src/main/java/org/springframework/data/domain/ExampleMatcher.java index e59698a71..cc842b8f3 100644 --- a/src/main/java/org/springframework/data/domain/ExampleMatcher.java +++ b/src/main/java/org/springframework/data/domain/ExampleMatcher.java @@ -15,6 +15,7 @@ */ package org.springframework.data.domain; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -24,6 +25,7 @@ import java.util.function.Function; import org.jspecify.annotations.Nullable; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.lang.CheckReturnValue; import org.springframework.lang.Contract; import org.springframework.util.Assert; @@ -77,6 +79,21 @@ public interface ExampleMatcher { return new TypedExampleMatcher().withMode(MatchMode.ALL); } + /** + * Returns a copy of this {@link ExampleMatcher} with the specified {@code propertyPaths}. This instance is immutable + * and unaffected by this method call. + * + * @param ignoredPaths must not be {@literal null} and not empty. + * @return new instance of {@link ExampleMatcher}. + * @since 4.1 + */ + @Contract("_ -> new") + @CheckReturnValue + default ExampleMatcher withIgnorePaths(TypedPropertyPath... ignoredPaths) { + return withIgnorePaths(Arrays.stream(ignoredPaths).map(TypedPropertyPath::of).map(TypedPropertyPath::toDotPath) + .toArray(String[]::new)); + } + /** * Returns a copy of this {@link ExampleMatcher} with the specified {@code propertyPaths}. This instance is immutable * and unaffected by this method call. @@ -122,6 +139,22 @@ public interface ExampleMatcher { @CheckReturnValue ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase); + /** + * Returns a copy of this {@link ExampleMatcher} with the specified {@code GenericPropertyMatcher} for the + * {@code propertyPath}. This instance is immutable and unaffected by this method call. + * + * @param propertyPath must not be {@literal null}. + * @param matcherConfigurer callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}. + * @return new instance of {@link ExampleMatcher}. + * @since 4.1 + */ + @Contract("_, _ -> new") + @CheckReturnValue + default ExampleMatcher withMatcher(TypedPropertyPath propertyPath, + MatcherConfigurer matcherConfigurer) { + return withMatcher(propertyPath.toDotPath(), matcherConfigurer); + } + /** * Returns a copy of this {@link ExampleMatcher} with the specified {@code GenericPropertyMatcher} for the * {@code propertyPath}. This instance is immutable and unaffected by this method call. @@ -143,6 +176,21 @@ public interface ExampleMatcher { return withMatcher(propertyPath, genericPropertyMatcher); } + /** + * Returns a copy of this {@link ExampleMatcher} with the specified {@code GenericPropertyMatcher} for the + * {@code propertyPath}. This instance is immutable and unaffected by this method call. + * + * @param propertyPath must not be {@literal null}. + * @param genericPropertyMatcher callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}. + * @return new instance of {@link ExampleMatcher}. + */ + @Contract("_, _ -> new") + @CheckReturnValue + default ExampleMatcher withMatcher(TypedPropertyPath propertyPath, + GenericPropertyMatcher genericPropertyMatcher) { + return withMatcher(propertyPath.toDotPath(), genericPropertyMatcher); + } + /** * Returns a copy of this {@link ExampleMatcher} with the specified {@code GenericPropertyMatcher} for the * {@code propertyPath}. This instance is immutable and unaffected by this method call. @@ -155,6 +203,22 @@ public interface ExampleMatcher { @CheckReturnValue ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher); + /** + * Returns a copy of this {@link ExampleMatcher} with the specified {@code PropertyValueTransformer} for the + * {@code propertyPath}. + * + * @param propertyPath must not be {@literal null}. + * @param propertyValueTransformer must not be {@literal null}. + * @return new instance of {@link ExampleMatcher}. + * @since 4.1 + */ + @Contract("_, _ -> new") + @CheckReturnValue + default ExampleMatcher withTransformer(TypedPropertyPath propertyPath, + PropertyValueTransformer propertyValueTransformer) { + return withTransformer(propertyPath.toDotPath(), propertyValueTransformer); + } + /** * Returns a copy of this {@link ExampleMatcher} with the specified {@code PropertyValueTransformer} for the * {@code propertyPath}. @@ -167,6 +231,20 @@ public interface ExampleMatcher { @CheckReturnValue ExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer); + /** + * Returns a copy of this {@link ExampleMatcher} with ignore case sensitivity for the {@code propertyPaths}. This + * instance is immutable and unaffected by this method call. + * + * @param propertyPaths must not be {@literal null} and not empty. + * @return new instance of {@link ExampleMatcher}. + */ + @Contract("_ -> new") + @CheckReturnValue + default ExampleMatcher withIgnoreCase(TypedPropertyPath... propertyPaths) { + return withIgnoreCase(Arrays.stream(propertyPaths).map(TypedPropertyPath::of).map(TypedPropertyPath::toDotPath) + .toArray(String[]::new)); + } + /** * Returns a copy of this {@link ExampleMatcher} with ignore case sensitivity for the {@code propertyPaths}. This * instance is immutable and unaffected by this method call. diff --git a/src/main/java/org/springframework/data/domain/Sort.java b/src/main/java/org/springframework/data/domain/Sort.java index 4b3974933..44aad6693 100644 --- a/src/main/java/org/springframework/data/domain/Sort.java +++ b/src/main/java/org/springframework/data/domain/Sort.java @@ -29,11 +29,11 @@ import java.util.stream.Collectors; import org.jspecify.annotations.Nullable; +import org.springframework.data.core.PropertyPath; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.util.MethodInvocationRecorder; import org.springframework.data.util.MethodInvocationRecorder.Recorded; -import org.springframework.data.util.PropertyPath; import org.springframework.data.util.Streamable; -import org.springframework.data.util.TypedPropertyPath; import org.springframework.lang.CheckReturnValue; import org.springframework.lang.Contract; import org.springframework.util.Assert; @@ -797,7 +797,7 @@ public class Sort implements Streamable extends Sort { diff --git a/src/test/java/org/springframework/data/mapping/TypedPropertyPathUnitTests.java b/src/test/java/org/springframework/data/core/TypedPropertyPathUnitTests.java similarity index 99% rename from src/test/java/org/springframework/data/mapping/TypedPropertyPathUnitTests.java rename to src/test/java/org/springframework/data/core/TypedPropertyPathUnitTests.java index 8e9a36562..b4dd02671 100644 --- a/src/test/java/org/springframework/data/mapping/TypedPropertyPathUnitTests.java +++ b/src/test/java/org/springframework/data/core/TypedPropertyPathUnitTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mapping; +package org.springframework.data.core; import static org.assertj.core.api.Assertions.*; diff --git a/src/test/java/org/springframework/data/domain/ExampleMatcherUnitTests.java b/src/test/java/org/springframework/data/domain/ExampleMatcherUnitTests.java index 20875f0b2..72209cd23 100755 --- a/src/test/java/org/springframework/data/domain/ExampleMatcherUnitTests.java +++ b/src/test/java/org/springframework/data/domain/ExampleMatcherUnitTests.java @@ -121,6 +121,15 @@ class ExampleMatcherUnitTests { assertThat(matcher.getIgnoredPaths()).hasSize(2); } + @Test // + void ignoredPropertyPathsShouldReturnUniqueProperties() { + + matcher = matching().withIgnorePaths(Person::getFirstname, Person::getLastname, Person::getFirstname); + + assertThat(matcher.getIgnoredPaths()).contains("firstname", "lastname"); + assertThat(matcher.getIgnoredPaths()).hasSize(2); + } + @Test // DATACMNS-810 void withCreatesNewInstance() { @@ -160,11 +169,11 @@ class ExampleMatcherUnitTests { void shouldCompareUsingHashCodeAndEquals() { matcher = matching() // - .withIgnorePaths("foo", "bar", "baz") // + .withIgnorePaths(Random::getFoo, Random::getBar, Random::getBaz) // .withNullHandler(NullHandler.IGNORE) // .withIgnoreCase("ignored-case") // - .withMatcher("hello", GenericPropertyMatchers.contains().caseSensitive()) // - .withMatcher("world", GenericPropertyMatcher::endsWith); + .withMatcher(Random::getHello, GenericPropertyMatchers.contains().caseSensitive()) // + .withMatcher(Random::getWorld, GenericPropertyMatcher::endsWith); var sameAsMatcher = matching() // .withIgnorePaths("foo", "bar", "baz") // @@ -182,8 +191,54 @@ class ExampleMatcherUnitTests { assertThat(matcher).isEqualTo(sameAsMatcher).isNotEqualTo(different); } + static class Random { + + String foo; + String bar; + String baz; + String hello; + String world; + + public String getFoo() { + return foo; + } + + public String getBar() { + return bar; + } + + public String getBaz() { + return baz; + } + + public String getHello() { + return hello; + } + + public String getWorld() { + return world; + } + } + static class Person { String firstname; + String lastname; + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } } } diff --git a/src/test/java/org/springframework/data/domain/SortUnitTests.java b/src/test/java/org/springframework/data/domain/SortUnitTests.java index 4e0b1c853..498a6ae96 100755 --- a/src/test/java/org/springframework/data/domain/SortUnitTests.java +++ b/src/test/java/org/springframework/data/domain/SortUnitTests.java @@ -21,11 +21,12 @@ import static org.springframework.data.domain.Sort.NullHandling.*; import java.util.Collection; import org.junit.jupiter.api.Test; + +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.data.geo.Circle; import org.springframework.data.mapping.Person; -import org.springframework.data.util.TypedPropertyPath; /** * Unit test for {@link Sort}.