1
16 package org.springframework.data.mapping.model;
17
18 import lombok.Getter;
19
20 import java.beans.FeatureDescriptor;
21 import java.beans.PropertyDescriptor;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.Method;
24 import java.util.Optional;
25 import java.util.concurrent.atomic.AtomicReference;
26 import java.util.function.Function;
27
28 import org.springframework.data.util.Lazy;
29 import org.springframework.data.util.Optionals;
30 import org.springframework.data.util.TypeInformation;
31 import org.springframework.lang.Nullable;
32 import org.springframework.util.Assert;
33 import org.springframework.util.ReflectionUtils;
34 import org.springframework.util.StringUtils;
35
36
43 public class Property {
44
45 private final @Getter Optional<Field> field;
46 private final Optional<PropertyDescriptor> descriptor;
47
48 private final Class<?> rawType;
49 private final Lazy<Integer> hashCode;
50 private final Optional<Method> getter;
51 private final Optional<Method> setter;
52
53 private final Lazy<String> name;
54 private final Lazy<String> toString;
55 private final Lazy<Optional<Method>> wither;
56
57 private Property(TypeInformation<?> type, Optional<Field> field, Optional<PropertyDescriptor> descriptor) {
58
59 Assert.notNull(type, "Type must not be null!");
60 Assert.isTrue(Optionals.isAnyPresent(field, descriptor), "Either field or descriptor has to be given!");
61
62 this.field = field;
63 this.descriptor = descriptor;
64
65 this.rawType = withFieldOrDescriptor(
66 it -> type.getRequiredProperty(it.getName()).getType(),
67 it -> type.getRequiredProperty(it.getName()).getType()
68 );
69 this.hashCode = Lazy.of(() -> withFieldOrDescriptor(Object::hashCode));
70 this.name = Lazy.of(() -> withFieldOrDescriptor(Field::getName, FeatureDescriptor::getName));
71 this.toString = Lazy.of(() -> withFieldOrDescriptor(Object::toString,
72 it -> String.format("%s.%s", type.getType().getName(), it.getDisplayName())));
73
74 this.getter = descriptor.map(PropertyDescriptor::getReadMethod)
75 .filter(it -> getType() != null)
76 .filter(it -> getType().isAssignableFrom(type.getReturnType(it).getType()));
77
78 this.setter = descriptor.map(PropertyDescriptor::getWriteMethod)
79 .filter(it -> getType() != null)
80 .filter(it -> type.getParameterTypes(it).get(0).getType().isAssignableFrom(getType()));
81
82 this.wither = Lazy.of(() -> findWither(type, getName(), getType()));
83 }
84
85
92 public static Property of(TypeInformation<?> type, Field field) {
93
94 Assert.notNull(field, "Field must not be null!");
95
96 return new Property(type, Optional.of(field), Optional.empty());
97 }
98
99
107 public static Property of(TypeInformation<?> type, Field field, PropertyDescriptor descriptor) {
108
109 Assert.notNull(field, "Field must not be null!");
110 Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
111
112 return new Property(type, Optional.of(field), Optional.of(descriptor));
113 }
114
115
124 public static Property of(TypeInformation<?> type, PropertyDescriptor descriptor) {
125
126 Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
127
128 return new Property(type, Optional.empty(), Optional.of(descriptor));
129 }
130
131
138 public static boolean supportsStandalone(PropertyDescriptor descriptor) {
139
140 Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
141
142 return descriptor.getPropertyType() != null;
143 }
144
145
150 public boolean isFieldBacked() {
151 return field.isPresent();
152 }
153
154
159 public Optional<Method> getGetter() {
160 return getter;
161 }
162
163
168 public Optional<Method> getSetter() {
169 return setter;
170 }
171
172
177 public Optional<Method> getWither() {
178 return wither.get();
179 }
180
181
186 public boolean hasAccessor() {
187 return getGetter().isPresent() || getSetter().isPresent();
188 }
189
190
195 public String getName() {
196 return this.name.get();
197 }
198
199
204 public Class<?> getType() {
205 return rawType;
206 }
207
208
212 @Override
213 public boolean equals(@Nullable Object obj) {
214
215 if (this == obj) {
216 return true;
217 }
218
219 if (!(obj instanceof Property)) {
220 return false;
221 }
222
223 Property that = (Property) obj;
224
225 return this.field.isPresent() ? this.field.equals(that.field) : this.descriptor.equals(that.descriptor);
226 }
227
228
232 @Override
233 public int hashCode() {
234 return hashCode.get();
235 }
236
237
241 @Override
242 public String toString() {
243 return toString.get();
244 }
245
246
252 private <T> T withFieldOrDescriptor(Function<Object, T> function) {
253 return withFieldOrDescriptor(function, function);
254 }
255
256
263 private <T> T withFieldOrDescriptor(Function<? super Field, T> field,
264 Function<? super PropertyDescriptor, T> descriptor) {
265
266 return Optionals.firstNonEmpty(
267 () -> this.field.map(field),
268 () -> this.descriptor.map(descriptor))
269 .orElseThrow(() -> new IllegalStateException("Should not occur! Either field or descriptor has to be given"));
270 }
271
272 private static Optional<Method> findWither(TypeInformation<?> owner, String propertyName, Class<?> rawType) {
273
274 AtomicReference<Method> resultHolder = new AtomicReference<>();
275 String methodName = String.format("with%s", StringUtils.capitalize(propertyName));
276
277 ReflectionUtils.doWithMethods(owner.getType(), it -> {
278
279 if (owner.isAssignableFrom(owner.getReturnType(it))) {
280 resultHolder.set(it);
281 }
282 }, it -> isMethodWithSingleParameterOfType(it, methodName, rawType));
283
284 Method method = resultHolder.get();
285 return method != null ? Optional.of(method) : Optional.empty();
286 }
287
288 private static boolean isMethodWithSingleParameterOfType(Method method, String name, Class<?> type) {
289
290 return method.getParameterCount() == 1
291 && method.getName().equals(name)
292 && method.getParameterTypes()[0].equals(type);
293 }
294 }
295