Skip to content

Commit e413758

Browse files
committed
Documentation.
1 parent b38a1a3 commit e413758

File tree

4 files changed

+95
-31
lines changed

4 files changed

+95
-31
lines changed

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@
3535
public interface PropertyPath extends Streamable<PropertyPath> {
3636

3737
/**
38-
* Syntax sugar to create a {@link TypedPropertyPath} from an existing one, ideal for method handles.
38+
* Syntax sugar to create a {@link TypedPropertyPath} from a method reference or lambda.
39+
* <p>
40+
* This method returns a resolved {@link TypedPropertyPath} by introspecting the given method reference or lambda.
3941
*
40-
* @param propertyPath
41-
* @return
42+
* @param propertyPath the method reference or lambda.
4243
* @param <T> owning type.
43-
* @param <R> property type.
44-
* @since xxx
44+
* @param <P> property type.
45+
* @return the typed property path.
4546
*/
46-
public static <T, R> TypedPropertyPath<T, R> of(TypedPropertyPath<T, R> propertyPath) {
47-
return TypedPropertyPath.of(propertyPath);
47+
static <T, P> TypedPropertyPath<T, P> of(TypedPropertyPath<T, P> propertyPath) {
48+
return TypedPropertyPaths.of(propertyPath);
4849
}
4950

5051
/**

src/main/java/org/springframework/data/core/SamParser.java

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,52 @@
5050
import org.springframework.util.StringUtils;
5151

5252
/**
53-
* Utility to parse Single Abstract Method (SAM) method references and lambdas to extract the owning type and the member
54-
* that is being accessed from it.
53+
* Utility to parse and introspect objects implementing a Single Abstract Method (SAM) interfaces (functional
54+
* interfaces) through lambda expressions and method references. Their declarative nature makes method references in
55+
* general and a constrained subset of lambda expressions suitable to declare property references in the sense of Java
56+
* Beans properties. Although lambdas and method references are primarily used in a declarative functional programming
57+
* models to express behavior, lambda serialization allows for further introspection such as parsing the lambda method
58+
* bytecode for property or member access information and not taking the functional behavior into account.
59+
* <p>
60+
* Objects can be Java method references and lambda expressions. The actual interface is not constrained by a base type,
61+
* however the object must:
62+
* <ul>
63+
* <li>Implement a functional Java interface</li>
64+
* <li>Must be the top-level lambda (i.e. not wrapped or a functional composition)</li>
65+
* <li>Implement Serializable (either through the actual interface or through inference)</li>
66+
* <li>Declare a single method to be implemented</li>
67+
* <li>Accept a single method argument and return a value</li>
68+
* </ul>
69+
* Ideally, the interface has a similar format to {@link Function}, for example:
70+
*
71+
* <pre class="code">
72+
* interface XtoYFunction<X, Y> (optional: extends Serializable) {
73+
* Y &lt;method-name&gt;(X someArgument);
74+
* }
75+
* </pre>
76+
*
77+
* This parser extracts the owning type and the member that is being accessed from it.
78+
* <p>
79+
* Supported ways to implement such an object are:
80+
* <ul>
81+
* <li>Method references, e.g. {@code SomeType::getSomeProperty}</li>
82+
* <li>Lambda expressions that access a property (no-arg method calls), e.g.
83+
* {@code someType -> someType.getSomeProperty()}</li>
84+
* <li>Lambda expressions that access a field, e.g. {@code someType -> someType.someField}</li>
85+
* </ul>
86+
* Unsupported:
87+
* <ul>
88+
* <li>Constructor references, e.g. {@code SomeType::new}</li>
89+
* <li>Method calls to methods accepting one or more arguments</li>
90+
* <li>Lambda expressions that do more than property access, e.g. {@code someType -> {
91+
* someType.setSomeProperty("value"); return someType.getSomeProperty(); }}</li>
92+
* <li>Arithmetic operations, arbitrary calls</li>
93+
* <li>Objects created through functional composition (i.e. {@code Function.andThen(…)} or wrappers around the
94+
* object</li>
95+
* </ul>
5596
*
5697
* @author Mark Paluch
98+
* @since 4.1
5799
*/
58100
class SamParser {
59101

@@ -88,6 +130,17 @@ private static boolean isEnabled(String property, boolean defaultValue) {
88130
this.entryPoints = Arrays.asList(entryPoints);
89131
}
90132

133+
/**
134+
* Parse the given lambda object and extract a reference to a {@link Member} such as a field or method.
135+
* <p>
136+
* Ideally used with an interface resembling {@link java.util.function.Function}
137+
*
138+
* @param classLoader class loader providing access to types used within the lambda.
139+
* @param lambdaObject the actual lambda object, must be {@link java.io.Serializable}.
140+
* @return the member reference.
141+
* @throws InvalidDataAccessApiUsageException if the lambda object doesn't represent a valid property reference or
142+
* hits any of the mentioned limitations.
143+
*/
91144
public MemberReference parse(ClassLoader classLoader, Object lambdaObject) {
92145

93146
try {

src/main/java/org/springframework/data/core/TypedPropertyPath.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* @param <T> the owning type of the property path segment, but typically the root type for composed property paths.
6060
* @param <P> the property value type at this path segment.
6161
* @author Mark Paluch
62+
* @since 4.1
6263
* @see PropertyPath
6364
* @see #of(TypedPropertyPath)
6465
* @see #then(TypedPropertyPath)
@@ -69,15 +70,15 @@ public interface TypedPropertyPath<T, P> extends PropertyPath, Serializable {
6970
/**
7071
* Syntax sugar to create a {@link TypedPropertyPath} from a method reference or lambda.
7172
* <p>
72-
* This method returns a resolved {@link TypedPropertyPath} by introspecting the given lambda.
73+
* This method returns a resolved {@link TypedPropertyPath} by introspecting the given method reference or lambda.
7374
*
74-
* @param lambda the method reference or lambda.
75+
* @param propertyPath the method reference or lambda.
7576
* @param <T> owning type.
7677
* @param <P> property type.
7778
* @return the typed property path.
7879
*/
79-
static <T, P> TypedPropertyPath<T, P> of(TypedPropertyPath<T, P> lambda) {
80-
return TypedPropertyPaths.of(lambda);
80+
static <T, P> TypedPropertyPath<T, P> of(TypedPropertyPath<T, P> propertyPath) {
81+
return TypedPropertyPaths.of(propertyPath);
8182
}
8283

8384
/**
@@ -121,13 +122,15 @@ default Iterator<PropertyPath> iterator() {
121122
}
122123

123124
/**
124-
* Extend the property path by appending the {@code next} path segment and returning a new property path instance..
125+
* Extend the property path by appending the {@code next} path segment and returning a new property path instance.
125126
*
126-
* @param next the next property path segment accepting a property path owned by the {@code P} type.
127+
* @param next the next property path segment as method reference or lambda accepting the owner object {@code P} type
128+
* and returning {@code N} as result of accessing a property.
127129
* @param <N> the new property value type.
128130
* @return a new composed {@code TypedPropertyPath}.
129131
*/
130132
default <N> TypedPropertyPath<T, N> then(TypedPropertyPath<P, N> next) {
131133
return TypedPropertyPaths.compose(this, of(next));
132134
}
135+
133136
}

src/main/java/org/springframework/data/core/TypedPropertyPaths.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131

3232
/**
3333
* Utility class to parse and resolve {@link TypedPropertyPath} instances.
34+
*
35+
* @author Mark Paluch
36+
* @since 4.1
3437
*/
3538
class TypedPropertyPaths {
3639

@@ -46,12 +49,13 @@ class TypedPropertyPaths {
4649
public static PropertyPathInformation getPropertyPathInformation(TypedPropertyPath<?, ?> lambda) {
4750

4851
Map<Object, PropertyPathInformation> cache;
52+
ClassLoader cl = lambda.getClass().getClassLoader();
4953
synchronized (lambdas) {
50-
cache = lambdas.computeIfAbsent(lambda.getClass().getClassLoader(), k -> new ConcurrentReferenceHashMap<>());
54+
cache = lambdas.computeIfAbsent(cl, k -> new ConcurrentReferenceHashMap<>());
5155
}
5256
Map<Object, PropertyPathInformation> lambdaMap = cache;
5357

54-
return lambdaMap.computeIfAbsent(lambda, o -> extractPath(lambda.getClass().getClassLoader(), lambda));
58+
return lambdaMap.computeIfAbsent(lambda, o -> extractPath(cl, lambda));
5559
}
5660

5761
private static PropertyPathInformation extractPath(ClassLoader classLoader, TypedPropertyPath<?, ?> lambda) {
@@ -106,38 +110,41 @@ public static PropertyPathInformation ofMethod(SamParser.MethodInformation metho
106110
String methodName = method.getMember().getName();
107111

108112
if (descriptor == null) {
109-
String propertyName;
110-
111-
if (methodName.startsWith("is")) {
112-
propertyName = Introspector.decapitalize(methodName.substring(2));
113-
} else if (methodName.startsWith("get")) {
114-
propertyName = Introspector.decapitalize(methodName.substring(3));
115-
} else {
116-
propertyName = methodName;
117-
}
118113

114+
String propertyName = getPropertyName(methodName);
119115
TypeInformation<?> owner = TypeInformation.of(method.owner());
120116
TypeInformation<?> fallback = owner.getProperty(propertyName);
117+
121118
if (fallback != null) {
122119
return new PropertyPathInformation(owner, fallback,
123120
propertyName);
124121
}
125122

126123
throw new IllegalArgumentException(
127-
"Cannot find PropertyDescriptor from method %s.%s".formatted(method.owner().getName(), methodName));
124+
"Cannot find PropertyDescriptor from method '%s.%s()'".formatted(method.owner().getName(), methodName));
128125
}
129126

130127
return new PropertyPathInformation(TypeInformation.of(method.getOwner()), TypeInformation.of(method.getType()),
131128
descriptor.getName());
132129
}
133130

131+
private static String getPropertyName(String methodName) {
132+
133+
if (methodName.startsWith("is")) {
134+
return Introspector.decapitalize(methodName.substring(2));
135+
} else if (methodName.startsWith("get")) {
136+
return Introspector.decapitalize(methodName.substring(3));
137+
}
138+
139+
return methodName;
140+
}
141+
134142
public static PropertyPathInformation ofField(SamParser.FieldInformation field) {
135143
return new PropertyPathInformation(TypeInformation.of(field.owner()), TypeInformation.of(field.getType()),
136144
field.getMember().getName());
137145
}
138146
}
139147

140-
141148
static class ResolvedTypedPropertyPath<T, P> implements TypedPropertyPath<T, P> {
142149

143150
private final TypedPropertyPath<T, P> function;
@@ -191,7 +198,7 @@ public boolean equals(Object obj) {
191198
return true;
192199
if (obj == null || obj.getClass() != this.getClass())
193200
return false;
194-
var that = (ResolvedTypedPropertyPath) obj;
201+
var that = (ResolvedTypedPropertyPath<?, ?>) obj;
195202
return Objects.equals(this.function, that.function) && Objects.equals(this.information, that.information);
196203
}
197204

@@ -206,8 +213,6 @@ public String toString() {
206213
}
207214
}
208215

209-
210-
211216
record ComposedPropertyPath<T, M, R>(TypedPropertyPath<T, M> first, TypedPropertyPath<M, R> second,
212217
String dotPath) implements TypedPropertyPath<T, R> {
213218

@@ -270,5 +275,7 @@ public Iterator<PropertyPath> iterator() {
270275
public String toString() {
271276
return getOwningType().getType().getSimpleName() + "." + toDotPath();
272277
}
278+
273279
}
280+
274281
}

0 commit comments

Comments
 (0)