diff --git a/src/main/java/org/springframework/data/mapping/PersistentProperty.java b/src/main/java/org/springframework/data/mapping/PersistentProperty.java index 2814bf36b..b975228ac 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/PersistentProperty.java @@ -113,6 +113,27 @@ public interface PersistentProperty

> { return setter; } + /** + * Returns the wither method to set a property value on a new object instance. Might return {@literal null} in case + * there is no wither available. + * + * @return the wither method to set a property value on a new object instance if available, otherwise {@literal null}. + * @since 2.1 + */ + @Nullable + Method getWither(); + + default Method getRequiredWither() { + + Method wither = getWither(); + + if (wither == null) { + throw new IllegalArgumentException("No wither available for this persistent property!"); + } + + return wither; + } + @Nullable Field getField(); @@ -221,6 +242,15 @@ public interface PersistentProperty

> { */ boolean isWritable(); + /** + * Returns whether the current property is immutable, i.e. if there is no setter or the backing {@link Field} is + * {@code final}. + * + * @return + * @since 2.1 + */ + boolean isImmutable(); + /** * Returns whether the property is an {@link Association}. * @@ -321,7 +351,7 @@ public interface PersistentProperty

> { /** * Returns whether the actual type of the property carries the given annotation. - * + * * @param annotationType must not be {@literal null}. * @return * @since 2.1 diff --git a/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java b/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java index 85ed388c9..42e5e88e7 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java +++ b/src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java @@ -23,8 +23,12 @@ import org.springframework.util.Assert; * Domain service to allow accessing and setting {@link PersistentProperty}s of an entity. Usually obtained through * {@link PersistentEntity#getPropertyAccessor(Object)}. In case type conversion shall be applied on property access, * use a {@link ConvertingPropertyAccessor}. + *

+ * This service supports mutation for immutable classes by creating new object instances. These are managed as state of + * {@link PersistentPropertyAccessor} and must be obtained from {@link #getBean()} after processing all updates. * * @author Oliver Gierke + * @author Mark Paluch * @since 1.10 * @see PersistentEntity#getPropertyAccessor(Object) * @see ConvertingPropertyAccessor @@ -44,7 +48,7 @@ public interface PersistentPropertyAccessor { /** * Sets the given value for the {@link PersistentProperty} pointed to by the given {@link PersistentPropertyPath}. The * lookup of intermediate values must not yield {@literal null}. - * + * * @param path must not be {@literal null} or empty. * @param value can be {@literal null}. * @since 2.1 @@ -84,7 +88,7 @@ public interface PersistentPropertyAccessor { /** * Return the value pointed to by the given {@link PersistentPropertyPath}. If the given path is empty, the wrapped * bean is returned. - * + * * @param path must not be {@literal null}. * @return * @since 2.1 @@ -119,7 +123,7 @@ public interface PersistentPropertyAccessor { } /** - * Returns the underlying bean. + * Returns the underlying bean. May change between {@link #setProperty(PersistentProperty, Object)} calls. * * @return will never be {@literal null}. */ diff --git a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java index aa6610f9c..c2fdf4814 100644 --- a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -20,6 +20,7 @@ import lombok.Getter; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -65,6 +66,8 @@ public abstract class AbstractPersistentProperty

private final @Getter(onMethod = @__(@Nullable)) Method getter; private final @Getter(onMethod = @__(@Nullable)) Method setter; private final @Getter(onMethod = @__(@Nullable)) Field field; + private final @Getter(onMethod = @__(@Nullable)) Method wither; + private final boolean immutable; public AbstractPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { @@ -90,6 +93,13 @@ public abstract class AbstractPersistentProperty

this.getter = property.getGetter().orElse(null); this.setter = property.getSetter().orElse(null); this.field = property.getField().orElse(null); + this.wither = property.getWither().orElse(null); + + if (setter == null && (field == null || Modifier.isFinal(field.getModifiers()))) { + this.immutable = true; + } else { + this.immutable = false; + } } protected abstract Association

createAssociation(); @@ -130,7 +140,7 @@ public abstract class AbstractPersistentProperty

return information; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getPersistentEntityTypes() */ @@ -174,6 +184,15 @@ public abstract class AbstractPersistentProperty

return !isTransient(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentProperty#isImmutable() + */ + @Override + public boolean isImmutable() { + return immutable; + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#isAssociation() diff --git a/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java b/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java index 1e74a5d75..3a0aad5bd 100644 --- a/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java +++ b/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java @@ -33,7 +33,7 @@ import org.springframework.util.ReflectionUtils; */ class BeanWrapper implements PersistentPropertyAccessor { - private final T bean; + private T bean; /** * Creates a new {@link BeanWrapper} for the given bean. @@ -50,12 +50,27 @@ class BeanWrapper implements PersistentPropertyAccessor { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.util.Optional) */ + @SuppressWarnings("unchecked") public void setProperty(PersistentProperty property, @Nullable Object value) { Assert.notNull(property, "PersistentProperty must not be null!"); try { + if (property.isImmutable()) { + + Method wither = property.getWither(); + if (wither != null) { + + ReflectionUtils.makeAccessible(wither); + this.bean = (T) ReflectionUtils.invokeMethod(wither, bean, value); + return; + } + + throw new UnsupportedOperationException( + String.format("Cannot set immutable property %s.%s!", property.getOwner().getName(), property.getName())); + } + if (!property.usePropertyAccess()) { Field field = property.getRequiredField(); diff --git a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java index 2caae95b2..eb1125583 100644 --- a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java +++ b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactory.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.asm.ClassWriter; @@ -246,6 +247,11 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert * case 3355: * $id_fieldSetter.invoke(bean, value); * return; + * case 3357: + * this.bean = $id_fieldWither.invoke(bean, value); + * case 3358: + * this.bean = bean.withId(value); + * return; * // ... * } * throw new UnsupportedOperationException( @@ -364,20 +370,20 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert cw.visitInnerClass(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, JAVA_LANG_INVOKE_METHOD_HANDLES, "Lookup", ACC_PRIVATE + ACC_FINAL + ACC_STATIC); - boolean accessibleType = isAccessible(entity); - - if (accessibleType) { - cw.visitField(ACC_PRIVATE + ACC_FINAL, BEAN_FIELD, referenceName(Type.getInternalName(entity.getType())), null, - null).visitEnd(); - } else { - cw.visitField(ACC_PRIVATE + ACC_FINAL, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, null).visitEnd(); - } + cw.visitField(ACC_PRIVATE, BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, null).visitEnd(); for (PersistentProperty property : persistentProperties) { - if (generateMethodHandle(entity, property.getSetter())) { - cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, setterName(property), - referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd(); + if (property.isImmutable()) { + if (generateMethodHandle(entity, property.getWither())) { + cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, witherName(property), + referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd(); + } + } else { + if (generateMethodHandle(entity, property.getSetter())) { + cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, setterName(property), + referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd(); + } } if (generateMethodHandle(entity, property.getGetter())) { @@ -387,8 +393,11 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert if (generateSetterMethodHandle(entity, property.getField())) { - cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldSetterName(property), - referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd(); + if (!property.isImmutable()) { + cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldSetterName(property), + referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd(); + } + cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldGetterName(property), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd(); } @@ -412,13 +421,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert // public EntityAccessor(Entity bean) or EntityAccessor(Object bean) MethodVisitor mv; - boolean accessibleType = isAccessible(entity); - if (accessibleType) { - mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", referenceName(entity.getType())), null, null); - } else { - mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", referenceName(JAVA_LANG_OBJECT)), null, null); - } + mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", getAccessibleTypeReferenceName(entity)), null, null); mv.visitCode(); Label l0 = new Label(); @@ -436,22 +440,13 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); - if (accessibleType) { - mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType())); - } else { - mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT)); - } + mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity)); mv.visitInsn(RETURN); Label l3 = new Label(); mv.visitLabel(l3); mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l3, 0); - - if (accessibleType) { - mv.visitLocalVariable(BEAN_FIELD, referenceName(Type.getInternalName(entity.getType())), null, l0, l3, 1); - } else { - mv.visitLocalVariable(BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, l0, l3, 1); - } + mv.visitLocalVariable(BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, l0, l3, 1); mv.visitMaxs(2, 2); } @@ -496,7 +491,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitLdcInsn(entityClass.getName()); mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_CLASS, "forName", String.format("(%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS)), false); - mv.visitVarInsn(ASTORE, classVariableIndex4(entityClasses, entityClass)); + mv.visitVarInsn(ASTORE, classVariableIndex5(entityClasses, entityClass)); } for (PersistentProperty property : persistentProperties) { @@ -508,7 +503,17 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert } if (generateMethodHandle(entity, property.getSetter())) { - visitPropertySetterInitializer(property, mv, entityClasses, internalClassName); + visitPropertySetterInitializer(property.getSetter(), property, mv, entityClasses, internalClassName, + PropertyAccessorClassGenerator::setterName, 2); + } + } + + if (property.isImmutable()) { + if (generateMethodHandle(entity, property.getWither())) { + if (generateMethodHandle(entity, property.getWither())) { + visitPropertySetterInitializer(property.getWither(), property, mv, entityClasses, internalClassName, + PropertyAccessorClassGenerator::witherName, 4); + } } } @@ -524,10 +529,11 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitLocalVariable("field", referenceName(JAVA_LANG_REFLECT_FIELD), null, l0, l1, 1); mv.visitLocalVariable("setter", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 2); mv.visitLocalVariable("getter", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 3); + mv.visitLocalVariable("wither", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 4); for (Class entityClass : entityClasses) { - int index = classVariableIndex4(entityClasses, entityClass); + int index = classVariableIndex5(entityClasses, entityClass); mv.visitLocalVariable(String.format("class_%d", index), referenceName(JAVA_LANG_CLASS), null, l0, l1, index); } @@ -567,7 +573,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert if (getter != null) { - mv.visitVarInsn(ALOAD, classVariableIndex4(entityClasses, getter.getDeclaringClass())); + mv.visitVarInsn(ALOAD, classVariableIndex5(entityClasses, getter.getDeclaringClass())); mv.visitLdcInsn(getter.getName()); mv.visitInsn(ICONST_0); mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_CLASS); @@ -597,28 +603,28 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert } /** - * Generate property setter initializer. + * Generate property setter/wither initializer. */ - private static void visitPropertySetterInitializer(PersistentProperty property, MethodVisitor mv, - List> entityClasses, String internalClassName) { + private static void visitPropertySetterInitializer(Method method, PersistentProperty property, MethodVisitor mv, + List> entityClasses, String internalClassName, + Function, String> setterNameFunction, int localVariableIndex) { - // setter = .class.getDeclaredMethod() - Method setter = property.getSetter(); + // method = .class.getDeclaredMethod() - if (setter != null) { + if (method != null) { - mv.visitVarInsn(ALOAD, classVariableIndex4(entityClasses, setter.getDeclaringClass())); - mv.visitLdcInsn(setter.getName()); + mv.visitVarInsn(ALOAD, classVariableIndex5(entityClasses, method.getDeclaringClass())); + mv.visitLdcInsn(method.getName()); mv.visitInsn(ICONST_1); mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_CLASS); mv.visitInsn(DUP); mv.visitInsn(ICONST_0); - Class parameterType = setter.getParameterTypes()[0]; + Class parameterType = method.getParameterTypes()[0]; if (parameterType.isPrimitive()) { - mv.visitFieldInsn(GETSTATIC, Type.getInternalName(autoboxType(setter.getParameterTypes()[0])), "TYPE", + mv.visitFieldInsn(GETSTATIC, Type.getInternalName(autoboxType(method.getParameterTypes()[0])), "TYPE", referenceName(JAVA_LANG_CLASS)); } else { mv.visitLdcInsn(Type.getType(referenceName(parameterType))); @@ -629,24 +635,24 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredMethod", String.format("(%s[%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS), referenceName(JAVA_LANG_REFLECT_METHOD)), false); - mv.visitVarInsn(ASTORE, 2); + mv.visitVarInsn(ASTORE, localVariableIndex); - // setter.setAccessible(true) - mv.visitVarInsn(ALOAD, 2); + // wither/setter.setAccessible(true) + mv.visitVarInsn(ALOAD, localVariableIndex); mv.visitInsn(ICONST_1); mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_METHOD, SET_ACCESSIBLE, "(Z)V", false); mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 2); + mv.visitVarInsn(ALOAD, localVariableIndex); mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflect", String.format("(%s)%s", referenceName(JAVA_LANG_REFLECT_METHOD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false); } - if (setter == null) { + if (method == null) { mv.visitInsn(ACONST_NULL); } - mv.visitFieldInsn(PUTSTATIC, internalClassName, setterName(property), + mv.visitFieldInsn(PUTSTATIC, internalClassName, setterNameFunction.apply(property), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)); } @@ -661,7 +667,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert Field field = property.getField(); if (field != null) { - mv.visitVarInsn(ALOAD, classVariableIndex4(entityClasses, field.getDeclaringClass())); + mv.visitVarInsn(ALOAD, classVariableIndex5(entityClasses, field.getDeclaringClass())); mv.visitLdcInsn(field.getName()); mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredField", String.format("(%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_REFLECT_FIELD)), false); @@ -680,13 +686,16 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldGetterName(property), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)); - // $fieldSetter = lookup.unreflectSetter(field) - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflectSetter", String.format( - "(%s)%s", referenceName(JAVA_LANG_REFLECT_FIELD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false); - mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldSetterName(property), - referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)); + if (!property.isImmutable()) { + + // $fieldSetter = lookup.unreflectSetter(field) + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflectSetter", String.format( + "(%s)%s", referenceName(JAVA_LANG_REFLECT_FIELD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false); + mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldSetterName(property), + referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)); + } } } @@ -702,11 +711,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitLabel(l0); mv.visitVarInsn(ALOAD, 0); - if (isAccessible(entity)) { - mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType())); - } else { - mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT)); - } + mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity)); mv.visitInsn(ARETURN); @@ -758,11 +763,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitVarInsn(ALOAD, 0); - if (isAccessible(entity)) { - mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType())); - } else { - mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT)); - } + mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity)); + mv.visitVarInsn(ASTORE, 2); visitGetPropertySwitch(entity, persistentProperties, internalClassName, mv); @@ -774,11 +776,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitLocalVariable("property", referenceName(PERSISTENT_PROPERTY), "Lorg/springframework/data/mapping/PersistentProperty<*>;", l0, l1, 1); - if (isAccessible(entity)) { - mv.visitLocalVariable(BEAN_FIELD, referenceName(entity.getType()), null, l0, l1, 2); - } else { - mv.visitLocalVariable(BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, l0, l1, 2); - } + mv.visitLocalVariable(BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, l0, l1, 2); mv.visitMaxs(0, 0); mv.visitEnd(); @@ -897,6 +895,10 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert * case 3355: * $id_fieldSetter.invoke(bean, value); * return; + * case 3357: + * this.bean = $id_fieldWither.invoke(bean, value); + * case 3358: + * this.bean = bean.withId(value); * // ... * } * throw new UnsupportedOperationException( @@ -920,11 +922,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitVarInsn(ALOAD, 0); - if (isAccessible(entity)) { - mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType())); - } else { - mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT)); - } + mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity)); mv.visitVarInsn(ASTORE, 3); @@ -940,11 +938,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert "Lorg/springframework/data/mapping/PersistentProperty<*>;", l0, l1, 1); mv.visitLocalVariable("value", referenceName(JAVA_LANG_OBJECT), null, l0, l1, 2); - if (isAccessible(entity)) { - mv.visitLocalVariable(BEAN_FIELD, referenceName(entity.getType()), null, l0, l1, 3); - } else { - mv.visitLocalVariable(BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, l0, l1, 3); - } + mv.visitLocalVariable(BEAN_FIELD, getAccessibleTypeReferenceName(entity), null, l0, l1, 3); mv.visitMaxs(0, 0); mv.visitEnd(); @@ -981,7 +975,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitLabel(propertyStackMap.get(property.getName()).label); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - if (property.getSetter() != null || property.getField() != null) { + if ((property.isImmutable() && property.getWither() != null) + || (!property.isImmutable() && (property.getSetter() != null || property.getField() != null))) { visitSetProperty0(entity, property, mv, internalClassName); } else { mv.visitJumpInsn(GOTO, dfltLabel); @@ -1000,10 +995,59 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert private static void visitSetProperty0(PersistentEntity entity, PersistentProperty property, MethodVisitor mv, String internalClassName) { + Method setter = property.getSetter(); - if (property.usePropertyAccess() && setter != null) { + Method wither = property.getWither(); + + if (property.isImmutable() && wither != null) { + + if (generateMethodHandle(entity, wither)) { + + // this. <- for later PUTFIELD + mv.visitVarInsn(ALOAD, 0); + + // $wither.invoke(bean) + mv.visitFieldInsn(GETSTATIC, internalClassName, witherName(property), + referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)); + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke", String.format("(%s%s)%s", + referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT), getAccessibleTypeReferenceName(entity)), + false); + + mv.visitTypeInsn(CHECKCAST, getAccessibleTypeReferenceName(entity)); + + } else { + + // this. <- for later PUTFIELD + mv.visitVarInsn(ALOAD, 0); + + // bean.set...(object) + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ALOAD, 2); + + Class parameterType = wither.getParameterTypes()[0]; + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(autoboxType(parameterType))); + autoboxIfNeeded(autoboxType(parameterType), parameterType, mv); + + int invokeOpCode = INVOKEVIRTUAL; + Class declaringClass = wither.getDeclaringClass(); + boolean interfaceDefinition = declaringClass.isInterface(); + + if (interfaceDefinition) { + invokeOpCode = INVOKEINTERFACE; + } + + mv.visitMethodInsn(invokeOpCode, Type.getInternalName(wither.getDeclaringClass()), wither.getName(), + String.format("(%s)%s", signatureTypeName(parameterType), referenceName(wither.getReturnType())), + interfaceDefinition); + } + + mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, getAccessibleTypeReferenceName(entity)); + } else if (property.usePropertyAccess() && setter != null) { if (generateMethodHandle(entity, setter)) { + // $setter.invoke(bean) mv.visitFieldInsn(GETSTATIC, internalClassName, setterName(property), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)); @@ -1012,6 +1056,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke", String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false); } else { + // bean.set...(object) mv.visitVarInsn(ALOAD, 3); mv.visitVarInsn(ALOAD, 2); @@ -1101,6 +1146,10 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert return String.format("$%s_setter", property.getName()); } + private static String witherName(PersistentProperty property) { + return String.format("$%s_wither", property.getName()); + } + private static String getterName(PersistentProperty property) { return String.format("$%s_getter", property.getName()); } @@ -1122,13 +1171,21 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert return !(Modifier.isPrivate(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPublic(modifiers)); } + private static String getAccessibleTypeReferenceName(PersistentEntity entity) { + if (isAccessible(entity)) { + return referenceName(entity.getType()); + } + + return referenceName(JAVA_LANG_OBJECT); + } + private static boolean generateSetterMethodHandle(PersistentEntity entity, @Nullable Field field) { if (field == null) { return false; } - return generateMethodHandle(entity, field) || Modifier.isFinal(field.getModifiers()); + return generateMethodHandle(entity, field); } /** @@ -1161,8 +1218,8 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert /** * Retrieves the class variable index with an offset of {@code 4}. */ - private static int classVariableIndex4(List> list, Class item) { - return 4 + list.indexOf(item); + private static int classVariableIndex5(List> list, Class item) { + return 5 + list.indexOf(item); } private static String generateClassName(PersistentEntity entity) { diff --git a/src/main/java/org/springframework/data/mapping/model/Property.java b/src/main/java/org/springframework/data/mapping/model/Property.java index ee8adc25c..926f429f4 100644 --- a/src/main/java/org/springframework/data/mapping/model/Property.java +++ b/src/main/java/org/springframework/data/mapping/model/Property.java @@ -29,6 +29,8 @@ import org.springframework.data.util.Optionals; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** * Value object to abstract the concept of a property backed by a {@link Field} and / or a {@link PropertyDescriptor}. @@ -49,6 +51,7 @@ public class Property { private final Lazy name; private final Lazy toString; + private final Lazy> wither; private Property(TypeInformation type, Optional field, Optional descriptor) { @@ -73,6 +76,20 @@ public class Property { this.setter = descriptor.map(PropertyDescriptor::getWriteMethod)// .filter(it -> getType() != null)// .filter(it -> type.getParameterTypes(it).get(0).getType().isAssignableFrom(getType())); + + this.wither = Lazy.of(() -> findWither(type, getName(), getType())); + + } + + private static Optional findWither(TypeInformation owner, String propertyName, Class rawType) { + + Method method = ReflectionUtils.findMethod(owner.getType(), "with" + StringUtils.capitalize(propertyName), rawType); + + if (method != null && owner.isAssignableFrom(owner.getReturnType(method))) { + return Optional.of(method); + } + + return Optional.empty(); } /** @@ -162,6 +179,15 @@ public class Property { return setter; } + /** + * Returns the wither of the property if available and if its first (only) parameter matches the type of the property. + * + * @return will never be {@literal null}. + */ + public Optional getWither() { + return wither.get(); + } + /** * Returns whether the property exposes a getter or a setter. * diff --git a/src/test/java/org/springframework/data/mapping/PersistentPropertyAccessorUnitTests.java b/src/test/java/org/springframework/data/mapping/PersistentPropertyAccessorUnitTests.java index 271f47cd1..18a2aff49 100644 --- a/src/test/java/org/springframework/data/mapping/PersistentPropertyAccessorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/PersistentPropertyAccessorUnitTests.java @@ -17,6 +17,8 @@ package org.springframework.data.mapping; import static org.assertj.core.api.Assertions.*; +import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Value; import org.junit.Test; @@ -93,7 +95,8 @@ public class PersistentPropertyAccessorUnitTests { Customer customer; } - @Value + @Data + @AllArgsConstructor static class Customer { String firstname; } diff --git a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java index bf745aa69..220928ff4 100755 --- a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java @@ -46,8 +46,8 @@ import org.springframework.util.StringUtils; @RunWith(Parameterized.class) public class ClassGeneratingPropertyAccessorFactoryTests { - private final ClassGeneratingPropertyAccessorFactory factory = new ClassGeneratingPropertyAccessorFactory(); - private final SampleMappingContext mappingContext = new SampleMappingContext(); + private final static ClassGeneratingPropertyAccessorFactory factory = new ClassGeneratingPropertyAccessorFactory(); + private final static SampleMappingContext mappingContext = new SampleMappingContext(); private final Object bean; private final String propertyName; @@ -67,7 +67,8 @@ public class ClassGeneratingPropertyAccessorFactoryTests { List parameters = new ArrayList<>(); List propertyNames = Arrays.asList("privateField", "packageDefaultField", "protectedField", "publicField", - "privateProperty", "packageDefaultProperty", "protectedProperty", "publicProperty", "syntheticProperty"); + "privateProperty", "packageDefaultProperty", "protectedProperty", "publicProperty", "syntheticProperty", + "immutable", "wither"); parameters.addAll(parameters(new InnerPrivateType(), propertyNames, Object.class)); parameters @@ -106,7 +107,7 @@ public class ClassGeneratingPropertyAccessorFactoryTests { assertThat(factory.isSupported(mappingContext.getRequiredPersistentEntity(bean.getClass()))).isTrue(); } - @Test // DATACMNS-809 + @Test // DATACMNS-809, // DATACMNS-1322 public void shouldSetAndGetProperty() throws Exception { assumeTrue(StringUtils.hasText(propertyName)); @@ -114,9 +115,15 @@ public class ClassGeneratingPropertyAccessorFactoryTests { assertThat(getProperty(bean, propertyName)).satisfies(property -> { PersistentPropertyAccessor persistentPropertyAccessor = getPersistentPropertyAccessor(bean); + if (property.isImmutable() && property.getWither() == null) { - persistentPropertyAccessor.setProperty(property, "value"); - assertThat(persistentPropertyAccessor.getProperty(property)).isEqualTo("value"); + assertThatThrownBy(() -> persistentPropertyAccessor.setProperty(property, "value")) + .isInstanceOf(UnsupportedOperationException.class); + } else { + + persistentPropertyAccessor.setProperty(property, "value"); + assertThat(persistentPropertyAccessor.getProperty(property)).isEqualTo("value"); + } }); } @@ -183,6 +190,8 @@ public class ClassGeneratingPropertyAccessorFactoryTests { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; @AccessType(Type.PROPERTY) private String privateProperty; @@ -192,6 +201,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { @AccessType(Type.PROPERTY) private String publicProperty; + private InnerPrivateType() { + this.wither = ""; + } + + private InnerPrivateType(String wither) { + this.wither = wither; + } + private String getPrivateProperty() { return privateProperty; } @@ -232,6 +249,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { public void setSyntheticProperty(String syntheticProperty) { backing = syntheticProperty; } + + public String getWither() { + return wither; + } + + public InnerPrivateType withWither(String wither) { + return new InnerPrivateType(wither); + } } // DATACMNS-809 @@ -248,6 +273,8 @@ public class ClassGeneratingPropertyAccessorFactoryTests { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; @AccessType(Type.PROPERTY) private String privateProperty; @@ -257,6 +284,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { @AccessType(Type.PROPERTY) private String publicProperty; + InnerPackageDefaultType() { + this.wither = ""; + } + + private InnerPackageDefaultType(String wither) { + this.wither = wither; + } + private String getPrivateProperty() { return privateProperty; } @@ -297,6 +332,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { public void setSyntheticProperty(String syntheticProperty) { backing = syntheticProperty; } + + public String getWither() { + return wither; + } + + public InnerPrivateType withWither(String wither) { + return new InnerPrivateType(wither); + } } // DATACMNS-809 @@ -308,6 +351,8 @@ public class ClassGeneratingPropertyAccessorFactoryTests { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; @AccessType(Type.PROPERTY) private String privateProperty; @@ -317,6 +362,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { @AccessType(Type.PROPERTY) private String publicProperty; + InnerProtectedType() { + this.wither = ""; + } + + private InnerProtectedType(String wither) { + this.wither = wither; + } + private String getPrivateProperty() { return privateProperty; } @@ -357,6 +410,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { public void setSyntheticProperty(String syntheticProperty) { backing = syntheticProperty; } + + public String getWither() { + return wither; + } + + public InnerPrivateType withWither(String wither) { + return new InnerPrivateType(wither); + } } // DATACMNS-809 @@ -368,6 +429,8 @@ public class ClassGeneratingPropertyAccessorFactoryTests { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; @AccessType(Type.PROPERTY) private String privateProperty; @@ -377,6 +440,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { @AccessType(Type.PROPERTY) private String publicProperty; + InnerPublicType() { + this.wither = ""; + } + + private InnerPublicType(String wither) { + this.wither = wither; + } + private String getPrivateProperty() { return privateProperty; } @@ -417,6 +488,14 @@ public class ClassGeneratingPropertyAccessorFactoryTests { public void setSyntheticProperty(String syntheticProperty) { backing = syntheticProperty; } + + public String getWither() { + return wither; + } + + public InnerPrivateType withWither(String wither) { + return new InnerPrivateType(wither); + } } public static class SubtypeOfTypeInOtherPackage extends TypeInOtherPackage {} diff --git a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPackageDefaultType.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPackageDefaultType.java index 349b9bacd..d4578d623 100644 --- a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPackageDefaultType.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPackageDefaultType.java @@ -30,6 +30,8 @@ class ClassGeneratingPropertyAccessorPackageDefaultType { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; @AccessType(Type.PROPERTY) private String privateProperty; @@ -39,6 +41,14 @@ class ClassGeneratingPropertyAccessorPackageDefaultType { @AccessType(Type.PROPERTY) private String publicProperty; + public ClassGeneratingPropertyAccessorPackageDefaultType() { + this.wither = ""; + } + + private ClassGeneratingPropertyAccessorPackageDefaultType(String wither) { + this.wither = wither; + } + private String getPrivateProperty() { return privateProperty; } @@ -79,4 +89,12 @@ class ClassGeneratingPropertyAccessorPackageDefaultType { public void setSyntheticProperty(String syntheticProperty) { backing = syntheticProperty; } + + public String getWither() { + return wither; + } + + public ClassGeneratingPropertyAccessorPackageDefaultType withWither(String wither) { + return new ClassGeneratingPropertyAccessorPackageDefaultType(wither); + } } diff --git a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPublicType.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPublicType.java index 5dd739d49..3a7ecb7ca 100644 --- a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPublicType.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorPublicType.java @@ -30,6 +30,8 @@ public class ClassGeneratingPropertyAccessorPublicType { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; private Integer aa; private int bb; @@ -41,6 +43,14 @@ public class ClassGeneratingPropertyAccessorPublicType { @AccessType(Type.PROPERTY) private String publicProperty; + public ClassGeneratingPropertyAccessorPublicType() { + this.wither = ""; + } + + private ClassGeneratingPropertyAccessorPublicType(String wither) { + this.wither = wither; + } + private String getPrivateProperty() { return privateProperty; } @@ -82,6 +92,14 @@ public class ClassGeneratingPropertyAccessorPublicType { backing = syntheticProperty; } + public String getWither() { + return wither; + } + + public ClassGeneratingPropertyAccessorPublicType withWither(String wither) { + return new ClassGeneratingPropertyAccessorPublicType(wither); + } + public Object set(Object e) { aa = (Integer) e; diff --git a/src/test/java/org/springframework/data/mapping/model/PersistentPropertyAccessorTests.java b/src/test/java/org/springframework/data/mapping/model/PersistentPropertyAccessorTests.java new file mode 100644 index 000000000..3dd5c4591 --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/model/PersistentPropertyAccessorTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; +import lombok.Value; +import lombok.experimental.Wither; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.context.SampleMappingContext; +import org.springframework.data.mapping.context.SamplePersistentProperty; + +/** + * Unit tests for {@link PersistentPropertyAccessor} through {@link BeanWrapper} and + * {@link ClassGeneratingPropertyAccessorFactory}. + * + * @author Mark Paluch + */ +@RunWith(Parameterized.class) +public class PersistentPropertyAccessorTests { + + private final static SampleMappingContext mappingContext = new SampleMappingContext(); + + private final Function propertyAccessorFunction; + + public PersistentPropertyAccessorTests(Function propertyAccessor, + String displayName) { + + this.propertyAccessorFunction = propertyAccessor; + } + + @Parameters(name = "{1}") + @SuppressWarnings("unchecked") + public static List parameters() { + + List parameters = new ArrayList<>(); + + ClassGeneratingPropertyAccessorFactory factory = new ClassGeneratingPropertyAccessorFactory(); + + Function beanWrapper = BeanWrapper::new; + Function classGenerating = it -> factory + .getPropertyAccessor(mappingContext.getRequiredPersistentEntity(it.getClass()), it); + + parameters.add(new Object[] { beanWrapper, "BeanWrapper" }); + parameters.add(new Object[] { classGenerating, "ClassGeneratingPropertyAccessorFactory" }); + + return parameters; + } + + @Test // DATACMNS-1322 + public void shouldSetProperty() { + + DataClass bean = new DataClass(); + PersistentPropertyAccessor accessor = propertyAccessorFunction.apply(bean); + SamplePersistentProperty property = getProperty(bean, "id"); + + accessor.setProperty(property, "value"); + + assertThat(accessor.getBean()).hasFieldOrPropertyWithValue("id", "value"); + assertThat(accessor.getBean()).isSameAs(bean); + assertThat(accessor.getProperty(property)).isEqualTo("value"); + } + + @Test // DATACMNS-1322 + public void shouldWitherProperty() { + + ValueClass bean = new ValueClass("foo", "bar"); + PersistentPropertyAccessor accessor = propertyAccessorFunction.apply(bean); + SamplePersistentProperty property = getProperty(bean, "id"); + + accessor.setProperty(property, "value"); + + assertThat(accessor.getBean()).hasFieldOrPropertyWithValue("id", "value"); + assertThat(accessor.getBean()).isNotSameAs(bean); + assertThat(accessor.getProperty(property)).isEqualTo("value"); + } + + @Test // DATACMNS-1322 + public void shouldRejectImmutablePropertyUpdate() { + + ValueClass bean = new ValueClass("foo", "bar"); + PersistentPropertyAccessor accessor = propertyAccessorFunction.apply(bean); + SamplePersistentProperty property = getProperty(bean, "immutable"); + + assertThatThrownBy(() -> accessor.setProperty(property, "value")).isInstanceOf(UnsupportedOperationException.class); + } + + private static SamplePersistentProperty getProperty(Object bean, String propertyName) { + + return mappingContext.getRequiredPersistentEntity(bean.getClass()).getRequiredPersistentProperty(propertyName); + } + + @Data + static class DataClass { + String id; + } + + @Value + + static class ValueClass { + @Wither String id; + String immutable; + } + +} diff --git a/src/test/java/org/springframework/data/mapping/model/PropertyUnitTests.java b/src/test/java/org/springframework/data/mapping/model/PropertyUnitTests.java new file mode 100644 index 000000000..00c3723cb --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/model/PropertyUnitTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import static org.assertj.core.api.Assertions.*; + +import lombok.Value; +import lombok.experimental.Wither; + +import org.junit.Test; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.util.ReflectionUtils; + +/** + * Unit tests for {@link Property}. + * + * @author Mark Paluch + */ +public class PropertyUnitTests { + + @Test // DATACMNS-1322 + public void shouldNotFindWitherMethod() { + + assertThat(Property + .of(ClassTypeInformation.from(ImmutableType.class), ReflectionUtils.findField(ImmutableType.class, "id")) + .getWither()).isEmpty(); + assertThat(Property + .of(ClassTypeInformation.from(ImmutableType.class), ReflectionUtils.findField(ImmutableType.class, "name")) + .getWither()).isEmpty(); + } + + @Test // DATACMNS-1322 + public void shouldDiscoverWitherMethod() { + + Property property = Property.of(ClassTypeInformation.from(WitherType.class), + ReflectionUtils.findField(WitherType.class, "id")); + + assertThat(property.getWither()).isPresent().hasValueSatisfying(actual -> { + assertThat(actual.getName()).isEqualTo("withId"); + assertThat(actual.getReturnType()).isEqualTo(WitherType.class); + }); + } + + @Value + static class ImmutableType { + String id; + String name; + + ImmutableType withId(Long id) { + return null; + } + + ImmutableType withName(Object id) { + return null; + } + + Object withName(String id) { + return null; + } + } + + @Value + @Wither + static class WitherType { + String id; + String name; + } +} diff --git a/src/test/java/org/springframework/data/mapping/model/subpackage/TypeInOtherPackage.java b/src/test/java/org/springframework/data/mapping/model/subpackage/TypeInOtherPackage.java index 29480c08a..57ae50430 100644 --- a/src/test/java/org/springframework/data/mapping/model/subpackage/TypeInOtherPackage.java +++ b/src/test/java/org/springframework/data/mapping/model/subpackage/TypeInOtherPackage.java @@ -29,6 +29,8 @@ public class TypeInOtherPackage { protected String protectedField; public String publicField; private String backing; + private final String immutable = ""; + private final String wither; @AccessType(Type.PROPERTY) private String privateProperty; @@ -38,6 +40,14 @@ public class TypeInOtherPackage { @AccessType(Type.PROPERTY) private String publicProperty; + public TypeInOtherPackage() { + this.wither = ""; + } + + private TypeInOtherPackage(String wither) { + this.wither = wither; + } + @SuppressWarnings("unused") private String getPrivateProperty() { return privateProperty; @@ -80,4 +90,12 @@ public class TypeInOtherPackage { public void setSyntheticProperty(String syntheticProperty) { backing = syntheticProperty; } + + public String getWither() { + return wither; + } + + public TypeInOtherPackage withWither(String wither) { + return new TypeInOtherPackage(wither); + } }