1 /*
2  * Copyright 2016-2020 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.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 /**
37  * Value object to abstract the concept of a property backed by a {@link Field} and / or a {@link PropertyDescriptor}.
38  *
39  * @author Oliver Gierke
40  * @author Christoph Strobl
41  * @author Mark Paluch
42  */

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     /**
86      * Creates a new {@link Property} backed by the given field.
87      *
88      * @param type the owning type, must not be {@literal null}.
89      * @param field must not be {@literal null}.
90      * @return
91      */

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     /**
100      * Creates a new {@link Property} backed by the given {@link Field} and {@link PropertyDescriptor}.
101      *
102      * @param type the owning type, must not be {@literal null}.
103      * @param field must not be {@literal null}.
104      * @param descriptor must not be {@literal null}.
105      * @return
106      */

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     /**
116      * Creates a new {@link Property} for the given {@link PropertyDescriptor}. The creation might fail if the given
117      * property is not representing a proper property.
118      *
119      * @param type the owning type, must not be {@literal null}.
120      * @param descriptor must not be {@literal null}.
121      * @return
122      * @see #supportsStandalone(PropertyDescriptor)
123      */

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     /**
132      * Returns whether the given {@link PropertyDescriptor} is supported in for standalone creation of a {@link Property}
133      * instance.
134      *
135      * @param descriptor
136      * @return
137      */

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     /**
146      * Returns whether the property is backed by a field.
147      *
148      * @return
149      */

150     public boolean isFieldBacked() {
151         return field.isPresent();
152     }
153
154     /**
155      * Returns the getter of the property if available and if it matches the type of the property.
156      *
157      * @return will never be {@literal null}.
158      */

159     public Optional<Method> getGetter() {
160         return getter;
161     }
162
163     /**
164      * Returns the setter of the property if available and if its first (only) parameter matches the type of the property.
165      *
166      * @return will never be {@literal null}.
167      */

168     public Optional<Method> getSetter() {
169         return setter;
170     }
171
172     /**
173      * Returns the wither of the property if available and if its first (only) parameter matches the type of the property.
174      *
175      * @return will never be {@literal null}.
176      */

177     public Optional<Method> getWither() {
178         return wither.get();
179     }
180
181     /**
182      * Returns whether the property exposes a getter or a setter.
183      *
184      * @return
185      */

186     public boolean hasAccessor() {
187         return getGetter().isPresent() || getSetter().isPresent();
188     }
189
190     /**
191      * Returns the name of the property.
192      *
193      * @return will never be {@literal null}.
194      */

195     public String getName() {
196         return this.name.get();
197     }
198
199     /**
200      * Returns the type of the property.
201      *
202      * @return will never be {@literal null}.
203      */

204     public Class<?> getType() {
205         return rawType;
206     }
207
208     /*
209      * (non-Javadoc)
210      * @see java.lang.Object#equals(java.lang.Object)
211      */

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     /*
229      * (non-Javadoc)
230      * @see java.lang.Object#hashCode()
231      */

232     @Override
233     public int hashCode() {
234         return hashCode.get();
235     }
236
237     /*
238      * (non-Javadoc)
239      * @see java.lang.Object#toString()
240      */

241     @Override
242     public String toString() {
243         return toString.get();
244     }
245
246     /**
247      * Maps the backing {@link Field} or {@link PropertyDescriptor} using the given {@link Function}.
248      *
249      * @param function must not be {@literal null}.
250      * @return
251      */

252     private <T> T withFieldOrDescriptor(Function<Object, T> function) {
253         return withFieldOrDescriptor(function, function);
254     }
255
256     /**
257      * Maps the backing {@link Field} or {@link PropertyDescriptor} using the given functions.
258      *
259      * @param field must not be {@literal null}.
260      * @param descriptor must not be {@literal null}.
261      * @return
262      */

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