1
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
73 public class ClassGeneratingPropertyAccessorFactory implements PersistentPropertyAccessorFactory {
74
75
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
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
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
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
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
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
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
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), null, null).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), null, null).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), null, null).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), null, null).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), null, null).visitEnd();
423 }
424
425 cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldGetterName(property),
426 referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd();
427 }
428 }
429 }
430
431
440 private static void visitDefaultConstructor(PersistentEntity<?, ?> entity, String internalClassName,
441 ClassWriter cw) {
442
443
444 MethodVisitor mv;
445
446 mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", getAccessibleTypeReferenceName(entity)), null, null);
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
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
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
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", null, null);
497 mv.visitCode();
498 Label l0 = new Label();
499 Label l1 = new Label();
500 mv.visitLabel(l0);
501
502
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
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
573
574
575 .map(it -> it.getDeclaringClass());
576
577 }).collect(Collectors.collectingAndThen(Collectors.toSet(), it -> new ArrayList<>(it)));
578
579 }
580
581
584 private static void visitPropertyGetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
585 List<Class<?>> entityClasses, String internalClassName) {
586
587
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
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
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
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
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
678 private static void visitFieldGetterSetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
679 List<Class<?>> entityClasses, String internalClassName) {
680
681
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
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
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
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
721 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getBean", String.format("()%s", referenceName(JAVA_LANG_OBJECT)),
722 null, null);
723 mv.visitCode();
724 Label l0 = new Label();
725
726
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
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
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
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
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
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
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
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
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
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
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
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
1041 private static void visitWithProperty(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
1042 MethodVisitor mv, String internalClassName, Method wither) {
1043
1044 if (generateMethodHandle(entity, wither)) {
1045
1046
1047 mv.visitVarInsn(ALOAD, 0);
1048
1049
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
1063 mv.visitVarInsn(ALOAD, 0);
1064
1065
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
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
1096 mv.visitVarInsn(ALOAD, 0);
1097
1098 if (kotlinCopyMethod.shouldUsePublicCopyMethod(entity)) {
1099
1100
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
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
1158 private static void visitSetProperty(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
1159 MethodVisitor mv, String internalClassName, Method setter) {
1160
1161 if (generateMethodHandle(entity, setter)) {
1162
1163
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
1173 mv.visitVarInsn(ALOAD, 3);
1174 mv.visitVarInsn(ALOAD, 2);
1175
1176 visitInvokeMethodSingleArg(mv, setter);
1177 }
1178 }
1179
1180
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
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
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
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
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
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
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
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
1400 @RequiredArgsConstructor
1401 static class PropertyStackAddress implements Comparable<PropertyStackAddress> {
1402
1403 private final @NonNull Label label;
1404 private final int hash;
1405
1406
1410 @Override
1411 public int compareTo(PropertyStackAddress o) {
1412 return Integer.compare(hash, o.hash);
1413 }
1414 }
1415
1416
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
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