1 /*
2  * Copyright 2017-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.reflect.KFunction;
19 import kotlin.reflect.KParameter;
20 import kotlin.reflect.jvm.ReflectJvmMapping;
21
22 import java.lang.reflect.Constructor;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.stream.IntStream;
26
27 import org.springframework.data.mapping.PersistentEntity;
28 import org.springframework.data.mapping.PersistentProperty;
29 import org.springframework.data.mapping.PreferredConstructor;
30 import org.springframework.data.mapping.PreferredConstructor.Parameter;
31 import org.springframework.data.util.ReflectionUtils;
32 import org.springframework.lang.Nullable;
33
34 /**
35  * Kotlin-specific extension to {@link ClassGeneratingEntityInstantiator} that adapts Kotlin constructors with
36  * defaulting.
37  *
38  * @author Mark Paluch
39  * @author Oliver Gierke
40  * @since 2.0
41  */

42 class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInstantiator {
43
44     /*
45      * (non-Javadoc)
46      * @see org.springframework.data.convert.ClassGeneratingEntityInstantiator#doCreateEntityInstantiator(org.springframework.data.mapping.PersistentEntity)
47      */

48     @Override
49     protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
50
51         PreferredConstructor<?, ?> constructor = entity.getPersistenceConstructor();
52
53         if (ReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) {
54
55             PreferredConstructor<?, ?> defaultConstructor = new DefaultingKotlinConstructorResolver(entity)
56                     .getDefaultConstructor();
57
58             if (defaultConstructor != null) {
59
60                 ObjectInstantiator instantiator = createObjectInstantiator(entity, defaultConstructor);
61
62                 return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor);
63             }
64         }
65
66         return super.doCreateEntityInstantiator(entity);
67     }
68
69     /**
70      * Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters
71      * suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}.
72      *
73      * @since 2.0
74      * @author Mark Paluch
75      */

76     static class DefaultingKotlinConstructorResolver {
77
78         private final @Nullable PreferredConstructor<?, ?> defaultConstructor;
79
80         @SuppressWarnings("unchecked")
81         DefaultingKotlinConstructorResolver(PersistentEntity<?, ?> entity) {
82
83             Constructor<?> hit = resolveDefaultConstructor(entity);
84             PreferredConstructor<?, ?> persistenceConstructor = entity.getPersistenceConstructor();
85
86             if (hit != null && persistenceConstructor != null) {
87                 this.defaultConstructor = new PreferredConstructor<>(hit,
88                         persistenceConstructor.getParameters().toArray(new Parameter[0]));
89             } else {
90                 this.defaultConstructor = null;
91             }
92         }
93
94         @Nullable
95         private static Constructor<?> resolveDefaultConstructor(PersistentEntity<?, ?> entity) {
96
97             PreferredConstructor<?, ?> persistenceConstructor = entity.getPersistenceConstructor();
98
99             if (persistenceConstructor == null) {
100                 return null;
101             }
102
103             Constructor<?> hit = null;
104             Constructor<?> constructor = persistenceConstructor.getConstructor();
105
106             for (Constructor<?> candidate : entity.getType().getDeclaredConstructors()) {
107
108                 // use only synthetic constructors
109                 if (!candidate.isSynthetic()) {
110                     continue;
111                 }
112
113                 // candidates must contain at least two additional parameters (int, DefaultConstructorMarker).
114                 // Number of defaulting masks derives from the original constructor arg count
115                 int syntheticParameters = KotlinDefaultMask.getMaskCount(constructor.getParameterCount())
116                         + /* DefaultConstructorMarker */ 1;
117
118                 if (constructor.getParameterCount() + syntheticParameters != candidate.getParameterCount()) {
119                     continue;
120                 }
121
122                 java.lang.reflect.Parameter[] constructorParameters = constructor.getParameters();
123                 java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters();
124
125                 if (!candidateParameters[candidateParameters.length - 1].getType().getName()
126                         .equals("kotlin.jvm.internal.DefaultConstructorMarker")) {
127                     continue;
128                 }
129
130                 if (parametersMatch(constructorParameters, candidateParameters)) {
131                     hit = candidate;
132                     break;
133                 }
134             }
135
136             return hit;
137         }
138
139         private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters,
140                 java.lang.reflect.Parameter[] candidateParameters) {
141
142             return IntStream.range(0, constructorParameters.length)
143                     .allMatch(i -> constructorParameters[i].getType().equals(candidateParameters[i].getType()));
144         }
145
146         @Nullable
147         PreferredConstructor<?, ?> getDefaultConstructor() {
148             return defaultConstructor;
149         }
150     }
151
152     /**
153      * Entity instantiator for Kotlin constructors that apply parameter defaulting. Kotlin constructors that apply
154      * argument defaulting are marked with {@link kotlin.jvm.internal.DefaultConstructorMarker} and accept additional
155      * parameters besides the regular (user-space) parameters. Additional parameters are:
156      * <ul>
157      * <li>defaulting bitmask ({@code int}), a bit mask slot for each 32 parameters</li>
158      * <li>{@code DefaultConstructorMarker} (usually null)</li>
159      * </ul>
160      * <strong>Defaulting bitmask</strong>
161      * <p/>
162      * The defaulting bitmask is a 32 bit integer representing which positional argument should be defaulted. Defaulted
163      * arguments are passed as {@literal null} and require the appropriate positional bit set ( {@code 1 << 2} for the 2.
164      * argument)). Since the bitmask represents only 32 bit states, it requires additional masks (slots) if more than 32
165      * arguments are represented.
166      *
167      * @author Mark Paluch
168      * @since 2.0
169      */

170     static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantiator {
171
172         private final ObjectInstantiator instantiator;
173         private final KFunction<?> constructor;
174         private final List<KParameter> kParameters;
175         private final Constructor<?> synthetic;
176
177         DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator, PreferredConstructor<?, ?> constructor) {
178
179             KFunction<?> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(constructor.getConstructor());
180
181             if (kotlinConstructor == null) {
182                 throw new IllegalArgumentException(
183                         "No corresponding Kotlin constructor found for " + constructor.getConstructor());
184             }
185
186             this.instantiator = instantiator;
187             this.constructor = kotlinConstructor;
188             this.kParameters = kotlinConstructor.getParameters();
189             this.synthetic = constructor.getConstructor();
190         }
191
192         /*
193          * (non-Javadoc)
194          * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
195          */

196         @Override
197         @SuppressWarnings("unchecked")
198         public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
199                 ParameterValueProvider<P> provider) {
200
201             Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
202
203             try {
204                 return (T) instantiator.newInstance(params);
205             } catch (Exception e) {
206                 throw new MappingInstantiationException(entity, Arrays.asList(params), e);
207             }
208         }
209
210         private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
211                 @Nullable PreferredConstructor<? extends T, P> preferredConstructor, ParameterValueProvider<P> provider) {
212
213             if (preferredConstructor == null) {
214                 throw new IllegalArgumentException("PreferredConstructor must not be null!");
215             }
216
217             Object[] params = allocateArguments(synthetic.getParameterCount()
218                     + KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1);
219             int userParameterCount = kParameters.size();
220
221             List<Parameter<Object, P>> parameters = preferredConstructor.getParameters();
222
223             // Prepare user-space arguments
224             for (int i = 0; i < userParameterCount; i++) {
225
226                 Parameter<Object, P> parameter = parameters.get(i);
227                 params[i] = provider.getParameterValue(parameter);
228             }
229
230             KotlinDefaultMask defaultMask = KotlinDefaultMask.from(constructor, it -> {
231
232                 int index = kParameters.indexOf(it);
233
234                 Parameter<Object, P> parameter = parameters.get(index);
235                 Class<Object> type = parameter.getType().getType();
236
237                 if (it.isOptional() && params[index] == null) {
238                     if (type.isPrimitive()) {
239
240                         // apply primitive defaulting to prevent NPE on primitive downcast
241                         params[index] = ReflectionUtils.getPrimitiveDefault(type);
242                     }
243                     return false;
244                 }
245
246                 return true;
247             });
248
249             int[] defaulting = defaultMask.getDefaulting();
250             // append nullability masks to creation arguments
251             for (int i = 0; i < defaulting.length; i++) {
252                 params[userParameterCount + i] = defaulting[i];
253             }
254
255             return params;
256         }
257     }
258 }
259