2020import java .util .Iterator ;
2121
2222import org .jspecify .annotations .Nullable ;
23-
2423import org .springframework .data .util .TypeInformation ;
2524
2625/**
26+ * Type-safe representation of a property path expressed through method references.
27+ * <p>
28+ * This functional interface extends {@link PropertyPath} to provide compile-time type safety when declaring property
29+ * paths. Instead of using {@link PropertyPath#from(String, TypeInformation) string-based property paths} that represent
30+ * references to properties textually and that are prone to refactoring issues, {@code TypedPropertyPath} leverages
31+ * Java's declarative method references and lambda expressions to ensure type-safe property access.
32+ * <p>
33+ * Typed property paths can be created directly they are accepted used or conveniently using the static factory method
34+ * {@link #of(TypedPropertyPath)} with method references:
35+ *
36+ * <pre class="code">
37+ * PropertyPath.of(Person::getName);
38+ * </pre>
39+ *
40+ * Property paths can be composed to navigate nested properties using {@link #then(TypedPropertyPath)}:
41+ *
42+ * <pre class="code">
43+ * PropertyPath.of(Person::getAddress).then(Address::getCountry).then(Country::getName);
44+ * </pre>
45+ * <p>
46+ * The interface maintains type information throughout the property path chain: the {@code T} type parameter represents
47+ * its owning type (root type for composed paths), while {@code P} represents the property value type at this path
48+ * segment.
49+ * <p>
50+ * As a functional interface, {@code TypedPropertyPath} should be implemented as method reference (recommended).
51+ * Alternatively, the interface can be implemented as lambda extracting a property value from an object of type
52+ * {@code T}. Implementations must ensure that the method reference or lambda correctly represents a property access
53+ * through a method invocation or by field access. Arbitrary calls to non-getter methods (i.e. methods accepting
54+ * parameters or arbitrary method calls on types other than the owning type are not allowed and will fail with
55+ * {@link org.springframework.dao.InvalidDataAccessApiUsageException}.
56+ * <p>
57+ * Note that using lambda expressions requires bytecode analysis of the declaration site classes and therefore presence
58+ * of their class files.
59+ *
60+ * @param <T> the owning type of the property path segment, but typically the root type for composed property paths.
61+ * @param <P> the property value type at this path segment.
2762 * @author Mark Paluch
63+ * @see PropertyPath
64+ * @see #of(TypedPropertyPath)
65+ * @see #then(TypedPropertyPath)
2866 */
2967@ FunctionalInterface
30- public interface TypedPropertyPath <T , R > extends Serializable , PropertyPath {
68+ public interface TypedPropertyPath <T , P > extends PropertyPath , Serializable {
3169
32- R get (T obj );
70+ /**
71+ * Syntax sugar to create a {@link TypedPropertyPath} from a method reference or lambda.
72+ * <p>
73+ * This method returns a resolved {@link TypedPropertyPath} by introspecting the given lambda.
74+ *
75+ * @param lambda the method reference or lambda.
76+ * @param <T> owning type.
77+ * @param <P> property type.
78+ * @return the typed property path.
79+ */
80+ static <T , P > TypedPropertyPath <T , P > of (TypedPropertyPath <T , P > lambda ) {
81+ return TypedPropertyPaths .of (lambda );
82+ }
83+
84+ /**
85+ * Get the property value for the given object.
86+ *
87+ * @param obj the object to get the property value from.
88+ * @return the property value.
89+ */
90+ @ Nullable
91+ P get (T obj );
3392
3493 @ Override
3594 default TypeInformation <?> getOwningType () {
36- return PropertyPathExtractor .getPropertyPathInformation (this ).owner ();
95+ return TypedPropertyPaths .getPropertyPathInformation (this ).owner ();
3796 }
3897
3998 @ Override
4099 default String getSegment () {
41- return PropertyPathExtractor .getPropertyPathInformation (this ).property ().getName ();
100+ return TypedPropertyPaths .getPropertyPathInformation (this ).property ().getName ();
42101 }
43102
44103 @ Override
45104 default TypeInformation <?> getTypeInformation () {
46- return PropertyPathExtractor .getPropertyPathInformation (this ).propertyType ();
105+ return TypedPropertyPaths .getPropertyPathInformation (this ).propertyType ();
47106 }
48107
49108 @ Override
@@ -62,8 +121,14 @@ default Iterator<PropertyPath> iterator() {
62121 return Collections .singletonList ((PropertyPath ) this ).iterator ();
63122 }
64123
65- default <N > TypedPropertyPath <T , N > then (TypedPropertyPath <R , N > next ) {
66- return new ComposedPropertyPath <>(this , next );
124+ /**
125+ * Extend the property path by appending the {@code next} path segment and returning a new property path instance..
126+ *
127+ * @param next the next property path segment accepting a property path owned by the {@code P} type.
128+ * @param <N> the new property value type.
129+ * @return a new composed {@code TypedPropertyPath}.
130+ */
131+ default <N > TypedPropertyPath <T , N > then (TypedPropertyPath <P , N > next ) {
132+ return TypedPropertyPaths .compose (this , of (next ));
67133 }
68-
69134}
0 commit comments