Skip to content

Commit f383e78

Browse files
committed
Expose PropertyPathUtil.
Allow generic parsing of property paths from serialized lambdas.
1 parent 62427f4 commit f383e78

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

src/main/java/org/springframework/data/core/PropertyUtil.java renamed to src/main/java/org/springframework/data/core/PropertyPathUtil.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,62 @@
1515
*/
1616
package org.springframework.data.core;
1717

18+
import java.io.Serializable;
19+
import java.lang.invoke.SerializedLambda;
20+
import java.lang.reflect.Method;
1821
import java.util.Objects;
1922

2023
import org.jspecify.annotations.Nullable;
2124

25+
import org.springframework.dao.InvalidDataAccessApiUsageException;
26+
import org.springframework.util.Assert;
27+
import org.springframework.util.ReflectionUtils;
28+
2229
/**
2330
* Utility class for {@link PropertyPath} and {@link PropertyReference} implementations.
2431
*
2532
* @author Mark Paluch
2633
* @since 4.1
2734
*/
28-
class PropertyUtil {
35+
public class PropertyPathUtil {
36+
37+
/**
38+
* Resolve a {@link PropertyPath} from a {@link Serializable} lambda implementing a functional interface accepting a
39+
* single method argument and returning a value. The form of the interface must follow a design aligned with
40+
* {@link org.springframework.core.convert.converter.Converter} or {@link java.util.function.Function}.
41+
*
42+
* @param obj the serializable lambda object.
43+
* @return the resolved property path.
44+
*/
45+
public static PropertyPath resolve(Object obj) {
46+
47+
Assert.isInstanceOf(Serializable.class, obj, "Object must be Serializable");
48+
49+
return TypedPropertyPaths.of(new SerializableWrapper((Serializable) obj));
50+
}
51+
52+
private record SerializableWrapper(Serializable serializable) implements PropertyReference<Object, Object> {
53+
54+
@Override
55+
public @Nullable Object get(Object obj) {
56+
return null;
57+
}
58+
59+
// serializable bridge
60+
public SerializedLambda writeReplace() {
61+
62+
Method method = ReflectionUtils.findMethod(serializable.getClass(), "writeReplace");
63+
64+
if (method == null) {
65+
throw new InvalidDataAccessApiUsageException(
66+
"Cannot find writeReplace method on " + serializable.getClass().getName());
67+
}
68+
69+
ReflectionUtils.makeAccessible(method);
70+
return (SerializedLambda) ReflectionUtils.invokeMethod(method, serializable);
71+
}
72+
73+
}
2974

3075
/**
3176
* Compute the hash code for the given {@link PropertyPath} based on its {@link Object#toString() string}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.core;
17+
18+
import java.io.Serializable;
19+
20+
import org.jspecify.annotations.Nullable;
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.core.convert.converter.Converter;
24+
25+
/**
26+
* Unit test {@link PropertyPathUtil}.
27+
*
28+
* @author Mark Paluch
29+
*/
30+
class PropertyPathUtilUnitTests {
31+
32+
@Test
33+
void shouldResolvePropertyPath() {
34+
35+
Converter<Person, String> c = convert(Person::getName);
36+
37+
System.out.println(PropertyPathUtil.resolve(c));
38+
}
39+
40+
static <T, P, C extends Converter<T, P> & Serializable> Serializable of(C mapping) {
41+
return mapping;
42+
}
43+
44+
static <A, B, T extends Converter<A, B> & Serializable> T convert(T converter) {
45+
return converter;
46+
}
47+
48+
static class Person {
49+
50+
private String name;
51+
private @Nullable Integer age;
52+
53+
// Getters
54+
public String getName() {
55+
return name;
56+
}
57+
58+
public @Nullable Integer getAge() {
59+
return age;
60+
}
61+
62+
}
63+
}

0 commit comments

Comments
 (0)