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 static org.springframework.asm.Opcodes.*;
19 import static org.springframework.data.mapping.model.BytecodeUtil.*;
20
21 import lombok.NonNull;
22 import lombok.RequiredArgsConstructor;
23
24 import java.lang.reflect.Constructor;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.Member;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Modifier;
29 import java.security.ProtectionDomain;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Optional;
37 import java.util.Set;
38 import java.util.concurrent.atomic.AtomicInteger;
39 import java.util.function.Function;
40 import java.util.stream.Collectors;
41
42 import org.springframework.asm.ClassWriter;
43 import org.springframework.asm.Label;
44 import org.springframework.asm.MethodVisitor;
45 import org.springframework.asm.Opcodes;
46 import org.springframework.asm.Type;
47 import org.springframework.cglib.core.ReflectUtils;
48 import org.springframework.core.KotlinDetector;
49 import org.springframework.data.mapping.PersistentEntity;
50 import org.springframework.data.mapping.PersistentProperty;
51 import org.springframework.data.mapping.PersistentPropertyAccessor;
52 import org.springframework.data.mapping.SimpleAssociationHandler;
53 import org.springframework.data.mapping.SimplePropertyHandler;
54 import org.springframework.data.mapping.model.KotlinCopyMethod.KotlinCopyByProperty;
55 import org.springframework.data.util.Optionals;
56 import org.springframework.data.util.TypeInformation;
57 import org.springframework.lang.Nullable;
58 import org.springframework.util.Assert;
59 import org.springframework.util.ClassUtils;
60 import org.springframework.util.ReflectionUtils;
61
62 /**
63  * A factory that can generate byte code to speed-up dynamic property access. Uses the {@link PersistentEntity}'s
64  * {@link PersistentProperty} to discover the access to properties. Properties are accessed either using method handles
65  * to overcome Java visibility issues or directly using field access/getter/setter calls.
66  *
67  * @author Mark Paluch
68  * @author Oliver Gierke
69  * @author Christoph Strobl
70  * @author Jens Schauder
71  * @since 1.13
72  */

73 public class ClassGeneratingPropertyAccessorFactory implements PersistentPropertyAccessorFactory {
74
75     // Pooling of parameter arrays to prevent excessive object allocation.
76     private final ThreadLocal<Object[]> argumentCache = ThreadLocal.withInitial(() -> new Object[1]);
77
78     private volatile Map<PersistentEntity<?, ?>, Constructor<?>> constructorMap = new HashMap<>(32);
79     private volatile Map<TypeInformation<?>, Class<PersistentPropertyAccessor<?>>> propertyAccessorClasses = new HashMap<>(
80             32);
81
82     /*
83      * (non-Javadoc)
84      * @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#getPropertyAccessor(org.springframework.data.mapping.PersistentEntity, java.lang.Object)
85      */

86     @Override
87     public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
88
89         Constructor<?> constructor = constructorMap.get(entity);
90
91         if (constructor == null) {
92
93             Class<PersistentPropertyAccessor<?>> accessorClass = potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
94                     entity);
95             constructor = accessorClass.getConstructors()[0];
96
97             Map<PersistentEntity<?, ?>, Constructor<?>> constructorMap = new HashMap<>(this.constructorMap);
98             constructorMap.put(entity, constructor);
99             this.constructorMap = constructorMap;
100         }
101
102         Object[] args = argumentCache.get();
103         args[0] = bean;
104
105         try {
106             return (PersistentPropertyAccessor<T>) constructor.newInstance(args);
107         } catch (Exception e) {
108             throw new IllegalArgumentException(String.format("Cannot create persistent property accessor for %s", entity), e);
109         } finally {
110             args[0] = null;
111         }
112     }
113
114     /**
115      * Checks whether an accessor class can be generated.
116      *
117      * @param entity must not be {@literal null}.
118      * @return {@literal trueif the runtime is equal or greater to Java 1.7, we can access the ClassLoader, the property
119      *         name hash codes are unique and the type has a class loader we can use to re-inject types.
120      * @see PersistentPropertyAccessorFactory#isSupported(PersistentEntity)
121      */

122     @Override
123     public boolean isSupported(PersistentEntity<?, ?> entity) {
124
125         Assert.notNull(entity, "PersistentEntity must not be null!");
126
127         return isClassLoaderDefineClassAvailable(entity) && isTypeInjectable(entity) && hasUniquePropertyHashCodes(entity);
128     }
129
130     private static boolean isClassLoaderDefineClassAvailable(PersistentEntity<?, ?> entity) {
131
132         try {
133             return ReflectionUtils.findMethod(entity.getType().getClassLoader().getClass(), "defineClass", String.class,
134                     byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class) != null;
135         } catch (Exception e) {
136             return false;
137         }
138     }
139
140     private static boolean isTypeInjectable(PersistentEntity<?, ?> entity) {
141
142         Class<?> type = entity.getType();
143         return type.getClassLoader() != null
144                 && (type.getPackage() == null || !type.getPackage().getName().startsWith("java"))
145                 && ClassUtils.isPresent(PersistentPropertyAccessor.class.getName(), type.getClassLoader())
146                 && ClassUtils.isPresent(Assert.class.getName(), type.getClassLoader());
147     }
148
149     private boolean hasUniquePropertyHashCodes(PersistentEntity<?, ?> entity) {
150
151         Set<Integer> hashCodes = new HashSet<>();
152         AtomicInteger propertyCount = new AtomicInteger();
153
154         entity.doWithProperties((SimplePropertyHandler) property -> {
155
156             hashCodes.add(property.getName().hashCode());
157             propertyCount.incrementAndGet();
158         });
159
160         entity.doWithAssociations((SimpleAssociationHandler) association -> {
161
162             if (association.getInverse() != null) {
163
164                 hashCodes.add(association.getInverse().getName().hashCode());
165                 propertyCount.incrementAndGet();
166             }
167         });
168
169         return hashCodes.size() == propertyCount.get();
170     }
171
172     /**
173      * @param entity must not be {@literal null}.
174      */

175     private synchronized Class<PersistentPropertyAccessor<?>> potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
176             PersistentEntity<?, ?> entity) {
177
178         Map<TypeInformation<?>, Class<PersistentPropertyAccessor<?>>> map = this.propertyAccessorClasses;
179         Class<PersistentPropertyAccessor<?>> propertyAccessorClass = map.get(entity.getTypeInformation());
180
181         if (propertyAccessorClass != null) {
182             return propertyAccessorClass;
183         }
184
185         propertyAccessorClass = createAccessorClass(entity);
186
187         map = new HashMap<>(map);
188         map.put(entity.getTypeInformation(), propertyAccessorClass);
189
190         this.propertyAccessorClasses = map;
191
192         return propertyAccessorClass;
193     }
194
195     @SuppressWarnings("unchecked")
196     private Class<PersistentPropertyAccessor<?>> createAccessorClass(PersistentEntity<?, ?> entity) {
197
198         try {
199             return (Class<PersistentPropertyAccessor<?>>) PropertyAccessorClassGenerator.generateCustomAccessorClass(entity);
200         } catch (Exception e) {
201             throw new RuntimeException(e);
202         }
203     }
204
205     /**
206      * Generates {@link PersistentPropertyAccessor} classes to access properties of a {@link PersistentEntity}. This code
207      * uses {@code private final static} held method handles which perform about the speed of native method invocations
208      * for property access which is restricted due to Java rules (such as private fields/methods) or private inner
209      * classes. All other scoped members (package defaultprotected and public) are accessed via field or property access
210      * to bypass reflection overhead. That's only possible if the type and the member access is possible from another
211      * class within the same package and class loader. Mixed access (MethodHandle/getter/setter calls) is possible as
212      * well. Accessing properties using generated accessors imposes some constraints:
213      * <ul>
214      * <li>Runtime must be Java 7 or higher</li>
215      * <li>The generated accessor decides upon generation whether to use field or property access for particular
216      * properties. It's not possible to change the access method once the accessor class is generated.</li>
217      * <li>Property names and their {@link String#hashCode()} must be unique within a {@link PersistentEntity}.</li>
218      * </ul>
219      * These constraints apply to retain the performance gains, otherwise the generated code has to decide which method
220      * (field/property) has to be used. The {@link String#hashCode()} rule originates in dispatching of to the appropriate
221      * {@link java.lang.invoke.MethodHandle}. This is done by {@code LookupSwitch} which is a O(1) operation but requires
222      * a constant input. {@link String#hashCode()} may change but since we run in the same VM, no evil should happen.
223      *
224      * <pre class="code">
225      * public class PersonWithId_Accessor_zd4wnl implements PersistentPropertyAccessor {
226      *     private final Object bean;
227      *     private static final MethodHandle $id_fieldGetter;
228      *     private static final MethodHandle $id_fieldSetter;
229      *
230      *     // ...
231      *     public PersonWithId_Accessor_zd4wnl(Object bean) {
232      *         this.bean = bean;
233      *     }
234      *
235      *     static {
236      *         Method getter;
237      *         Method setter;
238      *         MethodHandles.Lookup lookup = MethodHandles.lookup();
239      *         Class class_1 = Class.forName("org.springframework.data.mapping.Person");
240      *         Class class_2 = Class.forName("org.springframework.data.mapping.PersonWithId");
241      *         Field field = class_2.getDeclaredField("id");
242      *         field.setAccessible(true);
243      *         $id_fieldGetter = lookup.unreflectGetter(field);
244      *         $id_fieldSetter = lookup.unreflectSetter(field);
245      *         // ...
246      *     }
247      *
248      *     public Object getBean() {
249      *         return this.bean;
250      *     }
251      *
252      *     public void setProperty(PersistentProperty<?> property, Object value) {
253      *         Object bean = this.bean;
254      *         switch (property.getName().hashCode()) {
255      *             case 3355:
256      *                 $id_fieldSetter.invoke(bean, value);
257      *                 return;
258      *             case 3357:
259      *                 this.bean = $id_wither.invoke(bean, value);
260      *                 return;
261      *             case 3358:
262      *                 this.bean = bean.withId(value);
263      *                 return;
264      *             case 3359:
265      *                 this.bean = PersonWithId.copy$default(bean, value, 0, null); // Kotlin
266      *                 return;
267      *             // …
268      *         }
269      *         throw new UnsupportedOperationException(
270      *                 String.format("No accessor to set property %s!"new Object[] { property }));
271      *     }
272      *
273      *     public Object getProperty(PersistentProperty<?> property) {
274      *         Object bean = this.bean;
275      *         switch (property.getName().hashCode()) {
276      *             case 3355:
277      *                 return id_fieldGetter.invoke(bean);
278      *             case 3356:
279      *                 return bean.getField();
280      *             // …
281      *             case 3357:
282      *                 return bean.field;
283      *                 // …
284      *                 throw new UnsupportedOperationException(
285      *                         String.format("No accessor to get property %s!"new Object[] { property }));
286      *         }
287      *     }
288      * }
289      * </pre>
290      *
291      * @author Mark Paluch
292      */

293     static class PropertyAccessorClassGenerator {
294
295         private static final String INIT = "<init>";
296         private static final String CLINIT = "<clinit>";
297         private static final String TAG = "_Accessor_";
298         private static final String JAVA_LANG_OBJECT = "java/lang/Object";
299         private static final String JAVA_LANG_STRING = "java/lang/String";
300         private static final String JAVA_LANG_REFLECT_METHOD = "java/lang/reflect/Method";
301         private static final String JAVA_LANG_INVOKE_METHOD_HANDLE = "java/lang/invoke/MethodHandle";
302         private static final String JAVA_LANG_CLASS = "java/lang/Class";
303         private static final String BEAN_FIELD = "bean";
304         private static final String THIS_REF = "this";
305         private static final String PERSISTENT_PROPERTY = "org/springframework/data/mapping/PersistentProperty";
306         private static final String SET_ACCESSIBLE = "setAccessible";
307         private static final String JAVA_LANG_REFLECT_FIELD = "java/lang/reflect/Field";
308         private static final String JAVA_LANG_INVOKE_METHOD_HANDLES = "java/lang/invoke/MethodHandles";
309         private static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java/lang/invoke/MethodHandles$Lookup";
310         private static final String JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION = "java/lang/UnsupportedOperationException";
311
312         private static final String[] IMPLEMENTED_INTERFACES = new String[] {
313                 Type.getInternalName(PersistentPropertyAccessor.class) };
314
315         /**
316          * Generate a new class for the given {@link PersistentEntity}.
317          */

318         static Class<?> generateCustomAccessorClass(PersistentEntity<?, ?> entity) {
319
320             String className = generateClassName(entity);
321             Class<?> type = entity.getType();
322             ClassLoader classLoader = type.getClassLoader();
323
324             if (ClassUtils.isPresent(className, classLoader)) {
325
326                 try {
327                     return ClassUtils.forName(className, classLoader);
328                 } catch (Exception o_O) {
329                     throw new IllegalStateException(o_O);
330                 }
331             }
332
333             byte[] bytecode = generateBytecode(className.replace('.', '/'), entity);
334
335             try {
336
337                 return ReflectUtils.defineClass(className, bytecode, classLoader, type.getProtectionDomain(), type);
338
339             } catch (Exception o_O) {
340                 throw new IllegalStateException(o_O);
341             }
342         }
343
344         /**
345          * Generate a new class for the given {@link PersistentEntity}.
346          */

347         static byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity) {
348
349             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
350             cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, internalClassName, null, JAVA_LANG_OBJECT, IMPLEMENTED_INTERFACES);
351
352             List<PersistentProperty<?>> persistentProperties = getPersistentProperties(entity);
353
354             visitFields(entity, persistentProperties, cw);
355             visitDefaultConstructor(entity, internalClassName, cw);
356             visitStaticInitializer(entity, persistentProperties, internalClassName, cw);
357             visitBeanGetter(entity, internalClassName, cw);
358             visitSetProperty(entity, persistentProperties, internalClassName, cw);
359             visitGetProperty(entity, persistentProperties, internalClassName, cw);
360
361             cw.visitEnd();
362
363             return cw.toByteArray();
364         }
365
366         private static List<PersistentProperty<?>> getPersistentProperties(PersistentEntity<?, ?> entity) {
367
368             final List<PersistentProperty<?>> persistentProperties = new ArrayList<>();
369
370             entity.doWithAssociations((SimpleAssociationHandler) association -> {
371                 if (association.getInverse() != null) {
372                     persistentProperties.add(association.getInverse());
373                 }
374             });
375
376             entity.doWithProperties((SimplePropertyHandler) property -> persistentProperties.add(property));
377
378             return persistentProperties;
379         }
380
381         /**
382          * Generates field declarations for private-visibility properties.
383          *
384          * <pre class="code">
385          * private final Object bean;
386          * private static final MethodHandle $id_fieldGetter;
387          * private static final MethodHandle $id_fieldSetter;
388          * // …
389          * </pre>
390          */

391         private static void visitFields(PersistentEntity<?, ?> entity, List<PersistentProperty<?>> persistentProperties,
392                 ClassWriter cw) {
393
394             cw.visitInnerClass(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, JAVA_LANG_INVOKE_METHOD_HANDLES, "Lookup",
395                     ACC_PRIVATE + ACC_FINAL + ACC_STATIC);
396
397             cw.visitField(ACC_PRIVATE, BEAN_FIELD, getAccessibleTypeReferenceName(entity), nullnull).visitEnd();
398
399             for (PersistentProperty<?> property : persistentProperties) {
400
401                 if (property.isImmutable()) {
402                     if (generateMethodHandle(entity, property.getWither())) {
403                         cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, witherName(property),
404                                 referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), nullnull).visitEnd();
405                     }
406                 } else {
407                     if (generateMethodHandle(entity, property.getSetter())) {
408                         cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, setterName(property),
409                                 referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), nullnull).visitEnd();
410                     }
411                 }
412
413                 if (generateMethodHandle(entity, property.getGetter())) {
414                     cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, getterName(property),
415                             referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), nullnull).visitEnd();
416                 }
417
418                 if (generateSetterMethodHandle(entity, property.getField())) {
419
420                     if (!property.isImmutable()) {
421                         cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldSetterName(property),
422                                 referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), nullnull).visitEnd();
423                     }
424
425                     cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldGetterName(property),
426                             referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), nullnull).visitEnd();
427                 }
428             }
429         }
430
431         /**
432          * Generates the default constructor.
433          *
434          * <pre class="code">
435          * public PersonWithId_Accessor_zd4wnl(PersonWithId bean) {
436          *     this.bean = bean;
437          * }
438          * </pre>
439          */

440         private static void visitDefaultConstructor(PersistentEntity<?, ?> entity, String internalClassName,
441                 ClassWriter cw) {
442
443             // public EntityAccessor(Entity bean) or EntityAccessor(Object bean)
444             MethodVisitor mv;
445
446             mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", getAccessibleTypeReferenceName(entity)), nullnull);
447
448             mv.visitCode();
449             Label l0 = new Label();
450             mv.visitLabel(l0);
451             mv.visitVarInsn(ALOAD, 0);
452             mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, INIT, "()V"false);
453
454             // Assert.notNull(bean)
455             mv.visitVarInsn(ALOAD, 1);
456             mv.visitLdcInsn("Bean must not be null!");
457             mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert""notNull",
458                     String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_STRING)), false);
459
460             // this.bean = bean
461             mv.visitVarInsn(ALOAD, 0);
462             mv.visitVarInsn(ALOAD, 1);
463
464             mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity));
465
466             mv.visitInsn(RETURN);
467             Label l3 = new Label();
468             mv.visitLabel(l3);
469             mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l3, 0);
470             mv.visitLocalVariable(BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, l0, l3, 1);
471
472             mv.visitMaxs(2, 2);
473         }
474
475         /**
476          * Generates the static initializer block.
477          *
478          * <pre class="code">
479          * static {
480          *     Method getter;
481          *     Method setter;
482          *     MethodHandles.Lookup lookup = MethodHandles.lookup();
483          *     Class class_1 = Class.forName("org.springframework.data.mapping.Person");
484          *     Class class_2 = Class.forName("org.springframework.data.mapping.PersonWithId");
485          *     Field field = class_2.getDeclaredField("id");
486          *     field.setAccessible(true);
487          *     $id_fieldGetter = lookup.unreflectGetter(field);
488          *     $id_fieldSetter = lookup.unreflectSetter(field);
489          *     // …
490          * }
491          * </pre>
492          */

493         private static void visitStaticInitializer(PersistentEntity<?, ?> entity,
494                 List<PersistentProperty<?>> persistentProperties, String internalClassName, ClassWriter cw) {
495
496             MethodVisitor mv = cw.visitMethod(ACC_STATIC, CLINIT, "()V"nullnull);
497             mv.visitCode();
498             Label l0 = new Label();
499             Label l1 = new Label();
500             mv.visitLabel(l0);
501
502             // lookup = MethodHandles.lookup()
503             mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_INVOKE_METHOD_HANDLES, "lookup",
504                     String.format("()%s", referenceName(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP)), false);
505             mv.visitVarInsn(ASTORE, 0);
506
507             List<Class<?>> entityClasses = getPropertyDeclaratingClasses(persistentProperties);
508
509             for (Class<?> entityClass : entityClasses) {
510
511                 mv.visitLdcInsn(entityClass.getName());
512                 mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_CLASS, "forName",
513                         String.format("(%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS)), false);
514                 mv.visitVarInsn(ASTORE, classVariableIndex5(entityClasses, entityClass));
515             }
516
517             for (PersistentProperty<?> property : persistentProperties) {
518
519                 if (property.usePropertyAccess()) {
520
521                     if (generateMethodHandle(entity, property.getGetter())) {
522                         visitPropertyGetterInitializer(property, mv, entityClasses, internalClassName);
523                     }
524
525                     if (generateMethodHandle(entity, property.getSetter())) {
526                         visitPropertySetterInitializer(property.getSetter(), property, mv, entityClasses, internalClassName,
527                                 PropertyAccessorClassGenerator::setterName, 2);
528                     }
529                 }
530
531                 if (property.isImmutable() && generateMethodHandle(entity, property.getWither())) {
532                     visitPropertySetterInitializer(property.getWither(), property, mv, entityClasses, internalClassName,
533                             PropertyAccessorClassGenerator::witherName, 4);
534                 }
535
536                 if (generateSetterMethodHandle(entity, property.getField())) {
537                     visitFieldGetterSetterInitializer(property, mv, entityClasses, internalClassName);
538                 }
539             }
540
541             mv.visitLabel(l1);
542             mv.visitInsn(RETURN);
543
544             mv.visitLocalVariable("lookup", referenceName(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP), null, l0, l1, 0);
545             mv.visitLocalVariable("field", referenceName(JAVA_LANG_REFLECT_FIELD), null, l0, l1, 1);
546             mv.visitLocalVariable("setter", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 2);
547             mv.visitLocalVariable("getter", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 3);
548             mv.visitLocalVariable("wither", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 4);
549
550             for (Class<?> entityClass : entityClasses) {
551
552                 int index = classVariableIndex5(entityClasses, entityClass);
553                 mv.visitLocalVariable(String.format("class_%d", index), referenceName(JAVA_LANG_CLASS), null, l0, l1, index);
554             }
555
556             mv.visitMaxs(0, 0);
557             mv.visitEnd();
558         }
559
560         /**
561          * Retrieve all classes which are involved in property/getter/setter declarations as these elements may be
562          * distributed across the type hierarchy.
563          */

564         @SuppressWarnings("null")
565         private static List<Class<?>> getPropertyDeclaratingClasses(List<PersistentProperty<?>> persistentProperties) {
566
567             return persistentProperties.stream().flatMap(property -> {
568                 return Optionals
569                         .toStream(Optional.ofNullable(property.getField()), Optional.ofNullable(property.getGetter()),
570                                 Optional.ofNullable(property.getSetter()))
571
572                         // keep it a lambda to infer the correct types, preventing
573                         // LambdaConversionException: Invalid receiver type class java.lang.reflect.AccessibleObject; not a subtype
574                         // of implementation type interface java.lang.reflect.Member
575                         .map(it -> it.getDeclaringClass());
576
577             }).collect(Collectors.collectingAndThen(Collectors.toSet(), it -> new ArrayList<>(it)));
578
579         }
580
581         /**
582          * Generate property getter initializer.
583          */

584         private static void visitPropertyGetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
585                 List<Class<?>> entityClasses, String internalClassName) {
586
587             // getter = <entity>.class.getDeclaredMethod()
588             Method getter = property.getGetter();
589
590             if (getter != null) {
591
592                 mv.visitVarInsn(ALOAD, classVariableIndex5(entityClasses, getter.getDeclaringClass()));
593                 mv.visitLdcInsn(getter.getName());
594                 mv.visitInsn(ICONST_0);
595                 mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_CLASS);
596
597                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredMethod", String.format("(%s[%s)%s",
598                         referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS), referenceName(JAVA_LANG_REFLECT_METHOD)),
599                         false);
600                 mv.visitVarInsn(ASTORE, 3);
601
602                 // getter.setAccessible(true)
603                 mv.visitVarInsn(ALOAD, 3);
604                 mv.visitInsn(ICONST_1);
605                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_METHOD, SET_ACCESSIBLE, "(Z)V"false);
606
607                 mv.visitVarInsn(ALOAD, 0);
608                 mv.visitVarInsn(ALOAD, 3);
609                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflect", String.format("(%s)%s",
610                         referenceName(JAVA_LANG_REFLECT_METHOD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
611             }
612
613             if (getter == null) {
614                 mv.visitInsn(ACONST_NULL);
615             }
616
617             mv.visitFieldInsn(PUTSTATIC, internalClassName, getterName(property),
618                     referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
619         }
620
621         /**
622          * Generate property setter/wither initializer.
623          */

624         private static void visitPropertySetterInitializer(@Nullable Method method, PersistentProperty<?> property,
625                 MethodVisitor mv, List<Class<?>> entityClasses, String internalClassName,
626                 Function<PersistentProperty<?>, String> setterNameFunction, int localVariableIndex) {
627
628             // method = <entity>.class.getDeclaredMethod()
629
630             if (method != null) {
631
632                 mv.visitVarInsn(ALOAD, classVariableIndex5(entityClasses, method.getDeclaringClass()));
633                 mv.visitLdcInsn(method.getName());
634
635                 mv.visitInsn(ICONST_1);
636                 mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_CLASS);
637                 mv.visitInsn(DUP);
638                 mv.visitInsn(ICONST_0);
639
640                 Class<?> parameterType = method.getParameterTypes()[0];
641
642                 if (parameterType.isPrimitive()) {
643                     mv.visitFieldInsn(GETSTATIC, Type.getInternalName(autoboxType(method.getParameterTypes()[0])), "TYPE",
644                             referenceName(JAVA_LANG_CLASS));
645                 } else {
646                     mv.visitLdcInsn(Type.getType(referenceName(parameterType)));
647                 }
648
649                 mv.visitInsn(AASTORE);
650
651                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredMethod", String.format("(%s[%s)%s",
652                         referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS), referenceName(JAVA_LANG_REFLECT_METHOD)),
653                         false);
654                 mv.visitVarInsn(ASTORE, localVariableIndex);
655
656                 // wither/setter.setAccessible(true)
657                 mv.visitVarInsn(ALOAD, localVariableIndex);
658                 mv.visitInsn(ICONST_1);
659                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_METHOD, SET_ACCESSIBLE, "(Z)V"false);
660
661                 mv.visitVarInsn(ALOAD, 0);
662                 mv.visitVarInsn(ALOAD, localVariableIndex);
663                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflect", String.format("(%s)%s",
664                         referenceName(JAVA_LANG_REFLECT_METHOD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
665             }
666
667             if (method == null) {
668                 mv.visitInsn(ACONST_NULL);
669             }
670
671             mv.visitFieldInsn(PUTSTATIC, internalClassName, setterNameFunction.apply(property),
672                     referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
673         }
674
675         /**
676          * Generate field getter and setter initializers.
677          */

678         private static void visitFieldGetterSetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
679                 List<Class<?>> entityClasses, String internalClassName) {
680
681             // field = <entity>.class.getDeclaredField()
682
683             Field field = property.getField();
684             if (field != null) {
685
686                 mv.visitVarInsn(ALOAD, classVariableIndex5(entityClasses, field.getDeclaringClass()));
687                 mv.visitLdcInsn(field.getName());
688                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredField",
689                         String.format("(%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_REFLECT_FIELD)), false);
690                 mv.visitVarInsn(ASTORE, 1);
691
692                 // field.setAccessible(true)
693                 mv.visitVarInsn(ALOAD, 1);
694                 mv.visitInsn(ICONST_1);
695                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_FIELD, SET_ACCESSIBLE, "(Z)V"false);
696
697                 // $fieldGetter = lookup.unreflectGetter(field)
698                 mv.visitVarInsn(ALOAD, 0);
699                 mv.visitVarInsn(ALOAD, 1);
700                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflectGetter", String.format(
701                         "(%s)%s", referenceName(JAVA_LANG_REFLECT_FIELD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
702                 mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldGetterName(property),
703                         referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
704
705                 if (!property.isImmutable()) {
706
707                     // $fieldSetter = lookup.unreflectSetter(field)
708                     mv.visitVarInsn(ALOAD, 0);
709                     mv.visitVarInsn(ALOAD, 1);
710                     mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflectSetter", String.format(
711                             "(%s)%s", referenceName(JAVA_LANG_REFLECT_FIELD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
712                     mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldSetterName(property),
713                             referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
714                 }
715             }
716         }
717
718         private static void visitBeanGetter(PersistentEntity<?, ?> entity, String internalClassName, ClassWriter cw) {
719
720             // public Object getBean()
721             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getBean", String.format("()%s", referenceName(JAVA_LANG_OBJECT)),
722                     nullnull);
723             mv.visitCode();
724             Label l0 = new Label();
725
726             // return this.bean
727             mv.visitLabel(l0);
728             mv.visitVarInsn(ALOAD, 0);
729
730             mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity));
731
732             mv.visitInsn(ARETURN);
733
734             Label l1 = new Label();
735             mv.visitLabel(l1);
736             mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l1, 0);
737             mv.visitMaxs(1, 1);
738             mv.visitEnd();
739         }
740
741         /**
742          * Generate {@link PersistentPropertyAccessor#getProperty(PersistentProperty)}.
743          *
744          * <pre class="code">
745          * public Object getProperty(PersistentProperty<?> property) {
746          *     Object bean = this.bean;
747          *     switch (property.getName().hashCode()) {
748          *         case 3355:
749          *             return id_fieldGetter.invoke(bean);
750          *         case 3356:
751          *             return bean.getField();
752          *         // …
753          *         case 3357:
754          *             return bean.field;
755          *         // …
756          *     }
757          *     throw new UnsupportedOperationException(
758          *             String.format("No MethodHandle to get property %s"new Object[] { property }));
759          * }
760          * </pre>
761          */

762         private static void visitGetProperty(PersistentEntity<?, ?> entity,
763                 List<PersistentProperty<?>> persistentProperties, String internalClassName, ClassWriter cw) {
764
765             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getProperty",
766                     "(Lorg/springframework/data/mapping/PersistentProperty;)Ljava/lang/Object;",
767                     "(Lorg/springframework/data/mapping/PersistentProperty<*>;)Ljava/lang/Object;"null);
768             mv.visitCode();
769
770             Label l0 = new Label();
771             Label l1 = new Label();
772             mv.visitLabel(l0);
773
774             // Assert.notNull(property)
775             visitAssertNotNull(mv);
776
777             mv.visitVarInsn(ALOAD, 0);
778
779             mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity));
780
781             mv.visitVarInsn(ASTORE, 2);
782
783             visitGetPropertySwitch(entity, persistentProperties, internalClassName, mv);
784
785             mv.visitLabel(l1);
786             visitThrowUnsupportedOperationException(mv, "No accessor to get property %s!");
787
788             mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l1, 0);
789             mv.visitLocalVariable("property", referenceName(PERSISTENT_PROPERTY),
790                     "Lorg/springframework/data/mapping/PersistentProperty<*>;", l0, l1, 1);
791
792             mv.visitLocalVariable(BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, l0, l1, 2);
793
794             mv.visitMaxs(0, 0);
795             mv.visitEnd();
796         }
797
798         /**
799          * Generate the {@code switch(hashcode) {label: }} block.
800          */

801         private static void visitGetPropertySwitch(PersistentEntity<?, ?> entity,
802                 List<PersistentProperty<?>> persistentProperties, String internalClassName, MethodVisitor mv) {
803
804             Map<String, PropertyStackAddress> propertyStackMap = createPropertyStackMap(persistentProperties);
805
806             int[] hashes = new int[propertyStackMap.size()];
807             Label[] switchJumpLabels = new Label[propertyStackMap.size()];
808             List<PropertyStackAddress> stackmap = new ArrayList<>(propertyStackMap.values());
809             Collections.sort(stackmap);
810
811             for (int i = 0; i < stackmap.size(); i++) {
812
813                 PropertyStackAddress propertyStackAddress = stackmap.get(i);
814                 hashes[i] = propertyStackAddress.hash;
815                 switchJumpLabels[i] = propertyStackAddress.label;
816             }
817
818             Label dfltLabel = new Label();
819
820             mv.visitVarInsn(ALOAD, 1);
821             mv.visitMethodInsn(INVOKEINTERFACE, PERSISTENT_PROPERTY, "getName",
822                     String.format("()%s", referenceName(JAVA_LANG_STRING)), true);
823             mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_STRING, "hashCode""()I"false);
824             mv.visitLookupSwitchInsn(dfltLabel, hashes, switchJumpLabels);
825
826             for (PersistentProperty<?> property : persistentProperties) {
827
828                 mv.visitLabel(propertyStackMap.get(property.getName()).label);
829                 mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
830
831                 if (property.getGetter() != null || property.getField() != null) {
832                     visitGetProperty0(entity, property, mv, internalClassName);
833                 } else {
834                     mv.visitJumpInsn(GOTO, dfltLabel);
835                 }
836             }
837
838             mv.visitLabel(dfltLabel);
839             mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
840         }
841
842         /**
843          * Generate property read access using a {@link java.lang.invoke.MethodHandle}.
844          * {@link java.lang.invoke.MethodHandle#invoke(Object...)} have a {@code @PolymorphicSignature} so {@code invoke} is
845          * called as if the method had the expected signature and not array/varargs.
846          */

847         private static void visitGetProperty0(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
848                 MethodVisitor mv, String internalClassName) {
849
850             Method getter = property.getGetter();
851             if (property.usePropertyAccess() && getter != null) {
852
853                 if (generateMethodHandle(entity, getter)) {
854                     // $getter.invoke(bean)
855                     mv.visitFieldInsn(GETSTATIC, internalClassName, getterName(property),
856                             referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
857                     mv.visitVarInsn(ALOAD, 2);
858                     mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
859                             String.format("(%s)%s", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
860                 } else {
861                     // bean.get…
862                     mv.visitVarInsn(ALOAD, 2);
863
864                     int invokeOpCode = INVOKEVIRTUAL;
865                     Class<?> declaringClass = getter.getDeclaringClass();
866                     boolean interfaceDefinition = declaringClass.isInterface();
867
868                     if (interfaceDefinition) {
869                         invokeOpCode = INVOKEINTERFACE;
870                     }
871
872                     mv.visitMethodInsn(invokeOpCode, Type.getInternalName(declaringClass), getter.getName(),
873                             String.format("()%s", signatureTypeName(getter.getReturnType())), interfaceDefinition);
874                     autoboxIfNeeded(getter.getReturnType(), autoboxType(getter.getReturnType()), mv);
875                 }
876             } else {
877
878                 Field field = property.getRequiredField();
879
880                 if (generateMethodHandle(entity, field)) {
881                     // $fieldGetter.invoke(bean)
882                     mv.visitFieldInsn(GETSTATIC, internalClassName, fieldGetterName(property),
883                             referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
884                     mv.visitVarInsn(ALOAD, 2);
885                     mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
886                             String.format("(%s)%s", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
887                 } else {
888                     // bean.field
889                     mv.visitVarInsn(ALOAD, 2);
890                     mv.visitFieldInsn(GETFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(),
891                             signatureTypeName(field.getType()));
892                     autoboxIfNeeded(field.getType(), autoboxType(field.getType()), mv);
893                 }
894             }
895
896             mv.visitInsn(ARETURN);
897         }
898
899         /**
900          * Generate the {@link PersistentPropertyAccessor#setProperty(PersistentProperty, Object)} method.
901          *
902          * <pre class="code">
903          * public void setProperty(PersistentProperty<?> property, Optional<? extends Object> value) {
904          *     Object bean = this.bean;
905          *     switch (property.getName().hashCode()) {
906          *         case 3355:
907          *             $id_fieldSetter.invoke(bean, value);
908          *             return;
909          *         case 3357:
910          *             this.bean = $id_fieldWither.invoke(bean, value);
911          *             return;
912          *         case 3358:
913          *             this.bean = bean.withId(value);
914          *             return;
915          *         case 3359:
916          *             this.bean = PersonWithId.copy$default(bean, value, 0, null); // Kotlin
917          *             return;
918          *         // …
919          *     }
920          *     throw new UnsupportedOperationException(
921          *             String.format("No accessor to set property %s!"new Object[] { property }));
922          * }
923          * </pre>
924          */

925         private static void visitSetProperty(PersistentEntity<?, ?> entity,
926                 List<PersistentProperty<?>> persistentProperties, String internalClassName, ClassWriter cw) {
927
928             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setProperty",
929                     "(Lorg/springframework/data/mapping/PersistentProperty;Ljava/lang/Object;)V",
930                     "(Lorg/springframework/data/mapping/PersistentProperty<*>;Ljava/lang/Object;)V"null);
931             mv.visitCode();
932
933             Label l0 = new Label();
934             mv.visitLabel(l0);
935
936             visitAssertNotNull(mv);
937
938             mv.visitVarInsn(ALOAD, 0);
939
940             mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity));
941
942             mv.visitVarInsn(ASTORE, 3);
943
944             visitSetPropertySwitch(entity, persistentProperties, internalClassName, mv);
945
946             Label l1 = new Label();
947             mv.visitLabel(l1);
948
949             visitThrowUnsupportedOperationException(mv, "No accessor to set property %s!");
950
951             mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l1, 0);
952             mv.visitLocalVariable("property""Lorg/springframework/data/mapping/PersistentProperty;",
953                     "Lorg/springframework/data/mapping/PersistentProperty<*>;", l0, l1, 1);
954             mv.visitLocalVariable("value", referenceName(JAVA_LANG_OBJECT), null, l0, l1, 2);
955
956             mv.visitLocalVariable(BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, l0, l1, 3);
957
958             mv.visitMaxs(0, 0);
959             mv.visitEnd();
960         }
961
962         /**
963          * Generate the {@code switch(hashcode) {label: }} block.
964          */

965         private static void visitSetPropertySwitch(PersistentEntity<?, ?> entity,
966                 List<PersistentProperty<?>> persistentProperties, String internalClassName, MethodVisitor mv) {
967
968             Map<String, PropertyStackAddress> propertyStackMap = createPropertyStackMap(persistentProperties);
969
970             int[] hashes = new int[propertyStackMap.size()];
971             Label[] switchJumpLabels = new Label[propertyStackMap.size()];
972             List<PropertyStackAddress> stackmap = new ArrayList<>(propertyStackMap.values());
973             Collections.sort(stackmap);
974
975             for (int i = 0; i < stackmap.size(); i++) {
976                 PropertyStackAddress propertyStackAddress = stackmap.get(i);
977                 hashes[i] = propertyStackAddress.hash;
978                 switchJumpLabels[i] = propertyStackAddress.label;
979             }
980
981             Label dfltLabel = new Label();
982
983             mv.visitVarInsn(ALOAD, 1);
984             mv.visitMethodInsn(INVOKEINTERFACE, PERSISTENT_PROPERTY, "getName",
985                     String.format("()%s", referenceName(JAVA_LANG_STRING)), true);
986             mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_STRING, "hashCode""()I"false);
987             mv.visitLookupSwitchInsn(dfltLabel, hashes, switchJumpLabels);
988
989             for (PersistentProperty<?> property : persistentProperties) {
990                 mv.visitLabel(propertyStackMap.get(property.getName()).label);
991                 mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
992
993                 if (supportsMutation(property)) {
994                     visitSetProperty0(entity, property, mv, internalClassName);
995                 } else {
996                     mv.visitJumpInsn(GOTO, dfltLabel);
997                 }
998             }
999
1000             mv.visitLabel(dfltLabel);
1001             mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
1002         }
1003
1004         /**
1005          * Generate property write access using a {@link java.lang.invoke.MethodHandle}. NOTE:
1006          * {@link java.lang.invoke.MethodHandle#invoke(Object...)} have a {@code @PolymorphicSignature} so {@code invoke} is
1007          * called as if the method had the expected signature and not array/varargs.
1008          */

1009         private static void visitSetProperty0(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
1010                 MethodVisitor mv, String internalClassName) {
1011
1012             Method setter = property.getSetter();
1013             Method wither = property.getWither();
1014
1015             if (property.isImmutable()) {
1016
1017                 if (wither != null) {
1018                     visitWithProperty(entity, property, mv, internalClassName, wither);
1019                 }
1020
1021                 if (hasKotlinCopyMethod(property)) {
1022                     visitKotlinCopy(entity, property, mv, internalClassName);
1023                 }
1024
1025             } else if (property.usePropertyAccess() && setter != null) {
1026                 visitSetProperty(entity, property, mv, internalClassName, setter);
1027             } else {
1028                 visitSetField(entity, property, mv, internalClassName);
1029             }
1030
1031             mv.visitInsn(RETURN);
1032         }
1033
1034         /**
1035          * Generates:
1036          *
1037          * <pre class="code">
1038          * this.bean = this.bean = bean.withId(value);
1039          * </pre>
1040          */

1041         private static void visitWithProperty(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
1042                 MethodVisitor mv, String internalClassName, Method wither) {
1043
1044             if (generateMethodHandle(entity, wither)) {
1045
1046                 // this. <- for later PUTFIELD
1047                 mv.visitVarInsn(ALOAD, 0);
1048
1049                 // $wither.invoke(bean)
1050                 mv.visitFieldInsn(GETSTATIC, internalClassName, witherName(property),
1051                         referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
1052                 mv.visitVarInsn(ALOAD, 3);
1053                 mv.visitVarInsn(ALOAD, 2);
1054                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke", String.format("(%s%s)%s",
1055                         referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT), getAccessibleTypeReferenceName(entity)),
1056                         false);
1057                 if (isAccessible(entity)) {
1058                     mv.visitTypeInsn(CHECKCAST, Type.getInternalName(entity.getType()));
1059                 }
1060             } else {
1061
1062                 // this. <- for later PUTFIELD
1063                 mv.visitVarInsn(ALOAD, 0);
1064
1065                 // bean.set...(object)
1066                 mv.visitVarInsn(ALOAD, 3);
1067                 mv.visitVarInsn(ALOAD, 2);
1068
1069                 visitInvokeMethodSingleArg(mv, wither);
1070             }
1071
1072             mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity));
1073         }
1074
1075         /**
1076          * Generates:
1077          *
1078          * <pre class="code">
1079          * this.bean = bean.copy(value);
1080          * </pre>
1081          *
1082          * or
1083          *
1084          * <pre class="code">
1085          * this.bean = bean.copy$default..(bean, object, MASK, null)
1086          * </pre>
1087          */

1088         private static void visitKotlinCopy(PersistentEntity<?, ?> entity, PersistentProperty<?> property, MethodVisitor mv,
1089                 String internalClassName) {
1090
1091             KotlinCopyMethod kotlinCopyMethod = KotlinCopyMethod.findCopyMethod(entity.getType())
1092                     .orElseThrow(() -> new IllegalStateException(
1093                             String.format("No usable .copy(…) method found in entity %s", entity.getType().getName())));
1094
1095             // this. <- for later PUTFIELD
1096             mv.visitVarInsn(ALOAD, 0);
1097
1098             if (kotlinCopyMethod.shouldUsePublicCopyMethod(entity)) {
1099
1100                 // PersonWithId.copy$(value)
1101                 mv.visitVarInsn(ALOAD, 3);
1102                 mv.visitVarInsn(ALOAD, 2);
1103
1104                 visitInvokeMethodSingleArg(mv, kotlinCopyMethod.getPublicCopyMethod());
1105             } else {
1106
1107                 Method copy = kotlinCopyMethod.getSyntheticCopyMethod();
1108                 Class<?>[] parameterTypes = copy.getParameterTypes();
1109
1110                 // PersonWithId.copy$default..(bean, object, MASK, null)
1111                 mv.visitVarInsn(ALOAD, 3);
1112
1113                 KotlinCopyByProperty copyByProperty = kotlinCopyMethod.forProperty(property)
1114                         .orElseThrow(() -> new IllegalStateException(
1115                                 String.format("No usable .copy(…) method found for property %s", property)));
1116
1117                 for (int i = 1; i < kotlinCopyMethod.getParameterCount(); i++) {
1118
1119                     if (copyByProperty.getParameterPosition() == i) {
1120
1121                         mv.visitVarInsn(ALOAD, 2);
1122
1123                         mv.visitTypeInsn(CHECKCAST, Type.getInternalName(autoboxType(parameterTypes[i])));
1124                         autoboxIfNeeded(autoboxType(parameterTypes[i]), parameterTypes[i], mv);
1125
1126                         continue;
1127                     }
1128
1129                     visitDefaultValue(parameterTypes[i], mv);
1130                 }
1131
1132                 copyByProperty.getDefaultMask().forEach(mv::visitLdcInsn);
1133
1134                 mv.visitInsn(Opcodes.ACONST_NULL);
1135
1136                 int invokeOpCode = getInvokeOp(copy, false);
1137
1138                 mv.visitMethodInsn(invokeOpCode, Type.getInternalName(copy.getDeclaringClass()), copy.getName(),
1139                         getArgumentSignature(copy), false);
1140             }
1141
1142             mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity));
1143         }
1144
1145         /**
1146          * Generate:
1147          *
1148          * <pre class="code">
1149          * $id_fieldSetter.invoke(bean, value);
1150          * </pre>
1151          *
1152          * or
1153          *
1154          * <pre class="code">
1155          * bean.setId(value);
1156          * </pre>
1157          */

1158         private static void visitSetProperty(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
1159                 MethodVisitor mv, String internalClassName, Method setter) {
1160
1161             if (generateMethodHandle(entity, setter)) {
1162
1163                 // $setter.invoke(bean)
1164                 mv.visitFieldInsn(GETSTATIC, internalClassName, setterName(property),
1165                         referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
1166                 mv.visitVarInsn(ALOAD, 3);
1167                 mv.visitVarInsn(ALOAD, 2);
1168                 mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
1169                         String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
1170             } else {
1171
1172                 // bean.set...(object)
1173                 mv.visitVarInsn(ALOAD, 3);
1174                 mv.visitVarInsn(ALOAD, 2);
1175
1176                 visitInvokeMethodSingleArg(mv, setter);
1177             }
1178         }
1179
1180         /**
1181          * Generate:
1182          *
1183          * <pre class="code">
1184          * $id_fieldSetter.invoke(bean, value);
1185          * </pre>
1186          *
1187          * or
1188          *
1189          * <pre class="code">
1190          * bean.id = value;
1191          * </pre>
1192          */

1193         private static void visitSetField(PersistentEntity<?, ?> entity, PersistentProperty<?> property, MethodVisitor mv,
1194                 String internalClassName) {
1195
1196             Field field = property.getField();
1197             if (field != null) {
1198                 if (generateSetterMethodHandle(entity, field)) {
1199                     // $fieldSetter.invoke(bean, object)
1200                     mv.visitFieldInsn(GETSTATIC, internalClassName, fieldSetterName(property),
1201                             referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
1202                     mv.visitVarInsn(ALOAD, 3);
1203                     mv.visitVarInsn(ALOAD, 2);
1204                     mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
1205                             String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
1206                 } else {
1207                     // bean.field
1208                     mv.visitVarInsn(ALOAD, 3);
1209                     mv.visitVarInsn(ALOAD, 2);
1210
1211                     Class<?> fieldType = field.getType();
1212
1213                     mv.visitTypeInsn(CHECKCAST, Type.getInternalName(autoboxType(fieldType)));
1214                     autoboxIfNeeded(autoboxType(fieldType), fieldType, mv);
1215                     mv.visitFieldInsn(PUTFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(),
1216                             signatureTypeName(fieldType));
1217                 }
1218             }
1219         }
1220
1221         /**
1222          * Creates the method signature containing parameter types (e.g. (Ljava/lang/Object)I for a method accepting
1223          * {@link Object} and returning a primitive {@code int}).
1224          *
1225          * @param method
1226          * @return
1227          * @since 2.1
1228          */

1229         private static String getArgumentSignature(Method method) {
1230
1231             StringBuilder result = new StringBuilder("(");
1232             List<String> argumentTypes = new ArrayList<>();
1233
1234             for (Class<?> parameterType : method.getParameterTypes()) {
1235
1236                 result.append("%s");
1237                 argumentTypes.add(signatureTypeName(parameterType));
1238             }
1239
1240             result.append(")%s");
1241             argumentTypes.add(signatureTypeName(method.getReturnType()));
1242
1243             return String.format(result.toString(), argumentTypes.toArray());
1244         }
1245
1246         private static void visitAssertNotNull(MethodVisitor mv) {
1247
1248             // Assert.notNull(property)
1249             mv.visitVarInsn(ALOAD, 1);
1250             mv.visitLdcInsn("Property must not be null!");
1251             mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert""notNull",
1252                     String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_STRING)), false);
1253         }
1254
1255         private static void visitThrowUnsupportedOperationException(MethodVisitor mv, String message) {
1256
1257             // throw new UnsupportedOperationException(msg)
1258             mv.visitTypeInsn(NEW, JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION);
1259             mv.visitInsn(DUP);
1260             mv.visitLdcInsn(message);
1261             mv.visitInsn(ICONST_1);
1262             mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT);
1263             mv.visitInsn(DUP);
1264             mv.visitInsn(ICONST_0);
1265             mv.visitVarInsn(ALOAD, 1);
1266             mv.visitInsn(AASTORE);
1267             mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_STRING, "format",
1268                     "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;"false);
1269             mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION, "<init>""(Ljava/lang/String;)V",
1270                     false);
1271             mv.visitInsn(ATHROW);
1272         }
1273
1274         private static String fieldSetterName(PersistentProperty<?> property) {
1275             return String.format("$%s_fieldSetter", property.getName());
1276         }
1277
1278         private static String fieldGetterName(PersistentProperty<?> property) {
1279             return String.format("$%s_fieldGetter", property.getName());
1280         }
1281
1282         private static String setterName(PersistentProperty<?> property) {
1283             return String.format("$%s_setter", property.getName());
1284         }
1285
1286         private static String witherName(PersistentProperty<?> property) {
1287             return String.format("$%s_wither", property.getName());
1288         }
1289
1290         private static String getterName(PersistentProperty<?> property) {
1291             return String.format("$%s_getter", property.getName());
1292         }
1293
1294         private static boolean isAccessible(PersistentEntity<?, ?> entity) {
1295             return BytecodeUtil.isAccessible(entity.getType());
1296         }
1297
1298         private static String getAccessibleTypeReferenceName(PersistentEntity<?, ?> entity) {
1299
1300             if (isAccessible(entity)) {
1301                 return referenceName(entity.getType());
1302             }
1303
1304             return referenceName(JAVA_LANG_OBJECT);
1305         }
1306
1307         private static boolean generateSetterMethodHandle(PersistentEntity<?, ?> entity, @Nullable Field field) {
1308
1309             if (field == null) {
1310                 return false;
1311             }
1312
1313             return generateMethodHandle(entity, field);
1314         }
1315
1316         /**
1317          * Check whether to generate {@link java.lang.invoke.MethodHandle} access. Checks visibility rules of the member and
1318          * its declaring class. Use also {@link java.lang.invoke.MethodHandle} if visibility is protected/package-default
1319          * and packages of the declaring types are different.
1320          */

1321         private static boolean generateMethodHandle(PersistentEntity<?, ?> entity, @Nullable Member member) {
1322
1323             if (member == null) {
1324                 return false;
1325             }
1326
1327             if (isAccessible(entity)) {
1328
1329                 if (Modifier.isProtected(member.getModifiers()) || isDefault(member.getModifiers())) {
1330                     Package declaringPackage = member.getDeclaringClass().getPackage();
1331
1332                     if (declaringPackage != null && !declaringPackage.equals(entity.getType().getPackage())) {
1333                         return true;
1334                     }
1335                 }
1336
1337                 if (BytecodeUtil.isAccessible(member.getDeclaringClass()) && BytecodeUtil.isAccessible(member.getModifiers())) {
1338                     return false;
1339                 }
1340             }
1341
1342             return true;
1343         }
1344
1345         /**
1346          * Retrieves the class variable index with an offset of {@code 4}.
1347          */

1348         private static int classVariableIndex5(List<Class<?>> list, Class<?> item) {
1349             return 5 + list.indexOf(item);
1350         }
1351
1352         private static String generateClassName(PersistentEntity<?, ?> entity) {
1353             return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36);
1354         }
1355     }
1356
1357     private static void visitInvokeMethodSingleArg(MethodVisitor mv, Method method) {
1358
1359         Class<?>[] parameterTypes = method.getParameterTypes();
1360         Class<?> parameterType = parameterTypes[0];
1361         Class<?> declaringClass = method.getDeclaringClass();
1362         boolean interfaceDefinition = declaringClass.isInterface();
1363
1364         mv.visitTypeInsn(CHECKCAST, Type.getInternalName(autoboxType(parameterType)));
1365         autoboxIfNeeded(autoboxType(parameterType), parameterType, mv);
1366
1367         int invokeOpCode = getInvokeOp(method, interfaceDefinition);
1368
1369         mv.visitMethodInsn(invokeOpCode, Type.getInternalName(method.getDeclaringClass()), method.getName(),
1370                 String.format("(%s)%s", signatureTypeName(parameterType), signatureTypeName(method.getReturnType())),
1371                 interfaceDefinition);
1372     }
1373
1374     private static int getInvokeOp(Method method, boolean interfaceDefinition) {
1375
1376         int invokeOpCode = Modifier.isStatic(method.getModifiers()) ? INVOKESTATIC : INVOKEVIRTUAL;
1377
1378         if (interfaceDefinition) {
1379             invokeOpCode = INVOKEINTERFACE;
1380         }
1381         return invokeOpCode;
1382     }
1383
1384     private static Map<String, PropertyStackAddress> createPropertyStackMap(
1385             List<PersistentProperty<?>> persistentProperties) {
1386
1387         Map<String, PropertyStackAddress> stackmap = new HashMap<>();
1388
1389         for (PersistentProperty<?> property : persistentProperties) {
1390             stackmap.put(property.getName(), new PropertyStackAddress(new Label(), property.getName().hashCode()));
1391         }
1392         return stackmap;
1393     }
1394
1395     /**
1396      * Stack map address for a particular property.
1397      *
1398      * @author Mark Paluch
1399      */

1400     @RequiredArgsConstructor
1401     static class PropertyStackAddress implements Comparable<PropertyStackAddress> {
1402
1403         private final @NonNull Label label;
1404         private final int hash;
1405
1406         /*
1407          * (non-Javadoc)
1408          * @see java.lang.Comparable#compareTo(java.lang.Object)
1409          */

1410         @Override
1411         public int compareTo(PropertyStackAddress o) {
1412             return Integer.compare(hash, o.hash);
1413         }
1414     }
1415
1416     /**
1417      * @param property
1418      * @return {@literal trueif object mutation is supported.
1419      */

1420     static boolean supportsMutation(PersistentProperty<?> property) {
1421
1422         if (property.isImmutable()) {
1423
1424             if (property.getWither() != null) {
1425                 return true;
1426             }
1427
1428             if (hasKotlinCopyMethod(property)) {
1429                 return true;
1430             }
1431         }
1432
1433         return (property.usePropertyAccess() && property.getSetter() != null)
1434                 || (property.getField() != null && !Modifier.isFinal(property.getField().getModifiers()));
1435     }
1436
1437     /**
1438      * Check whether the owning type of {@link PersistentProperty} declares a {@literal copy} method or {@literal copy}
1439      * method with parameter defaulting.
1440      *
1441      * @param type must not be {@literal null}.
1442      * @return
1443      */

1444     private static boolean hasKotlinCopyMethod(PersistentProperty<?> property) {
1445
1446         Class<?> type = property.getOwner().getType();
1447
1448         if (isAccessible(type) && KotlinDetector.isKotlinType(type)) {
1449             return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property)).isPresent();
1450         }
1451
1452         return false;
1453     }
1454 }
1455