1 /*
2  * Copyright 2011-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 kotlin.jvm.JvmClassMappingKt;
19 import kotlin.reflect.KFunction;
20 import kotlin.reflect.full.KClasses;
21 import kotlin.reflect.jvm.ReflectJvmMapping;
22
23 import java.lang.annotation.Annotation;
24 import java.lang.reflect.Constructor;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28
29 import org.springframework.core.DefaultParameterNameDiscoverer;
30 import org.springframework.core.ParameterNameDiscoverer;
31 import org.springframework.data.annotation.PersistenceConstructor;
32 import org.springframework.data.mapping.PersistentEntity;
33 import org.springframework.data.mapping.PersistentProperty;
34 import org.springframework.data.mapping.PreferredConstructor;
35 import org.springframework.data.mapping.PreferredConstructor.Parameter;
36 import org.springframework.data.util.ClassTypeInformation;
37 import org.springframework.data.util.ReflectionUtils;
38 import org.springframework.data.util.TypeInformation;
39 import org.springframework.lang.Nullable;
40 import org.springframework.util.Assert;
41
42 /**
43  * Helper class to find a {@link PreferredConstructor}.
44  *
45  * @author Oliver Gierke
46  * @author Christoph Strobl
47  * @author Roman Rodov
48  * @author Mark Paluch
49  */

50 public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<P>> {
51
52     /**
53      * Discovers the {@link PreferredConstructor} for the given type.
54      *
55      * @param type must not be {@literal null}.
56      * @return the {@link PreferredConstructor} if found or {@literal null}.
57      */

58     @Nullable
59     static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(Class<T> type) {
60
61         Assert.notNull(type, "Type must not be null!");
62
63         return Discoverers.findDiscoverer(type) //
64                 .discover(ClassTypeInformation.from(type), null);
65     }
66
67     /**
68      * Discovers the {@link PreferredConstructorDiscoverer} for the given {@link PersistentEntity}.
69      *
70      * @param entity must not be {@literal null}.
71      * @return the {@link PreferredConstructor} if found or {@literal null}.
72      */

73     @Nullable
74     static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(PersistentEntity<T, P> entity) {
75
76         Assert.notNull(entity, "PersistentEntity must not be null!");
77
78         return Discoverers.findDiscoverer(entity.getType()) //
79                 .discover(entity.getTypeInformation(), entity);
80     }
81
82     /**
83      * Helper class to find a {@link PreferredConstructor}.
84      *
85      * @author Oliver Gierke
86      * @author Christoph Strobl
87      * @author Roman Rodov
88      * @author Mark Paluch
89      * @since 2.0
90      */

91     enum Discoverers {
92
93         /**
94          * Discovers a {@link PreferredConstructor} for Java types.
95          */

96         DEFAULT {
97
98             /*
99              * (non-Javadoc)
100              * @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity)
101              */

102             @Nullable
103             @Override
104             <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
105                     @Nullable PersistentEntity<T, P> entity) {
106
107                 Class<?> rawOwningType = type.getType();
108
109                 List<Constructor<?>> candidates = new ArrayList<>();
110                 Constructor<?> noArg = null;
111                 for (Constructor<?> candidate : rawOwningType.getDeclaredConstructors()) {
112
113                     // Synthetic constructors should not be considered
114                     if (candidate.isSynthetic()) {
115                         continue;
116                     }
117
118                     if (candidate.isAnnotationPresent(PersistenceConstructor.class)) {
119                         return buildPreferredConstructor(candidate, type, entity);
120                     }
121
122                     if (candidate.getParameterCount() == 0) {
123                         noArg = candidate;
124                     } else {
125                         candidates.add(candidate);
126                     }
127                 }
128
129                 if (noArg != null) {
130                     return buildPreferredConstructor(noArg, type, entity);
131                 }
132
133                 return candidates.size() > 1 || candidates.isEmpty() ? null
134                         : buildPreferredConstructor(candidates.iterator().next(), type, entity);
135             }
136         },
137
138         /**
139          * Discovers a {@link PreferredConstructor} for Kotlin types.
140          */

141         KOTLIN {
142
143             /*
144              * (non-Javadoc)
145              * @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity)
146              */

147             @Nullable
148             @Override
149             <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
150                     @Nullable PersistentEntity<T, P> entity) {
151
152                 Class<?> rawOwningType = type.getType();
153
154                 return Arrays.stream(rawOwningType.getDeclaredConstructors()) //
155                         .filter(it -> !it.isSynthetic()) // Synthetic constructors should not be considered
156                         .filter(it -> it.isAnnotationPresent(PersistenceConstructor.class)) // Explicitly defined constructor trumps
157                                                                                                                                                                 // all
158                         .map(it -> buildPreferredConstructor(it, type, entity)) //
159                         .findFirst() //
160                         .orElseGet(() -> {
161
162                             KFunction<T> primaryConstructor = KClasses
163                                     .getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(type.getType()));
164
165                             if (primaryConstructor == null) {
166                                 return DEFAULT.discover(type, entity);
167                             }
168
169                             Constructor<T> javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
170
171                             return javaConstructor != null ? buildPreferredConstructor(javaConstructor, type, entity) : null;
172                         });
173             }
174         };
175
176         private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
177
178         /**
179          * Find the appropriate discoverer for {@code type}.
180          *
181          * @param type must not be {@literal null}.
182          * @return the appropriate discoverer for {@code type}.
183          */

184         private static Discoverers findDiscoverer(Class<?> type) {
185             return ReflectionUtils.isSupportedKotlinClass(type) ? KOTLIN : DEFAULT;
186         }
187
188         /**
189          * Discovers a constructor for the given type.
190          *
191          * @param type must not be {@literal null}.
192          * @param entity may be {@literal null}.
193          * @return the {@link PreferredConstructor} if found or {@literal null}.
194          */

195         @Nullable
196         abstract <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
197                 @Nullable PersistentEntity<T, P> entity);
198
199         @SuppressWarnings({ "unchecked""rawtypes" })
200         private static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> buildPreferredConstructor(
201                 Constructor<?> constructor, TypeInformation<T> typeInformation, @Nullable PersistentEntity<T, P> entity) {
202
203             if (constructor.getParameterCount() == 0) {
204                 return new PreferredConstructor<>((Constructor<T>) constructor);
205             }
206
207             List<TypeInformation<?>> parameterTypes = typeInformation.getParameterTypes(constructor);
208             String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(constructor);
209
210             Parameter<Object, P>[] parameters = new Parameter[parameterTypes.size()];
211             Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
212
213             for (int i = 0; i < parameterTypes.size(); i++) {
214
215                 String name = parameterNames == null ? null : parameterNames[i];
216                 TypeInformation<?> type = parameterTypes.get(i);
217                 Annotation[] annotations = parameterAnnotations[i];
218
219                 parameters[i] = new Parameter(name, type, annotations, entity);
220             }
221
222             return new PreferredConstructor<>((Constructor<T>) constructor, parameters);
223         }
224     }
225 }
226