DATACMNS-1322 - Detect wither methods.
We now detect withher methods (withId(…)) that create a new object instance that contains the new property value. We support those wither methods to create new object instances from property updates. Classes following a wither pattern declare a withXXX(…) method that accepts the property value and return a new, instance of its own type associated with the property value. Along with this change we removed the ability to update final fields that worked by accident using reflection.
class ValueClass {
@Wither String id;
}
class ValueClass {
final String id;
ValueClass withId(String id) {
return new ValueClass(id);
}
}
This commit is contained in:
committed by
Oliver Gierke
parent
6951d8b711
commit
1ea65f3130
@@ -113,6 +113,27 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
|
||||
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<P extends PersistentProperty<P>> {
|
||||
*/
|
||||
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<P extends PersistentProperty<P>> {
|
||||
|
||||
/**
|
||||
* Returns whether the actual type of the property carries the given annotation.
|
||||
*
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
|
||||
@@ -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}.
|
||||
* <p />
|
||||
* 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}.
|
||||
*/
|
||||
|
||||
@@ -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<P extends PersistentProperty<P>
|
||||
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<?, P> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
@@ -90,6 +93,13 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
|
||||
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<P> createAssociation();
|
||||
@@ -130,7 +140,7 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
|
||||
return information;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.PersistentProperty#getPersistentEntityTypes()
|
||||
*/
|
||||
@@ -174,6 +184,15 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
|
||||
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()
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
class BeanWrapper<T> implements PersistentPropertyAccessor {
|
||||
|
||||
private final T bean;
|
||||
private T bean;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BeanWrapper} for the given bean.
|
||||
@@ -50,12 +50,27 @@ class BeanWrapper<T> 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();
|
||||
|
||||
@@ -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<Class<?>> entityClasses, String internalClassName) {
|
||||
private static void visitPropertySetterInitializer(Method method, PersistentProperty<?> property, MethodVisitor mv,
|
||||
List<Class<?>> entityClasses, String internalClassName,
|
||||
Function<PersistentProperty<?>, String> setterNameFunction, int localVariableIndex) {
|
||||
|
||||
// setter = <entity>.class.getDeclaredMethod()
|
||||
Method setter = property.getSetter();
|
||||
// method = <entity>.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<Class<?>> list, Class<?> item) {
|
||||
return 4 + list.indexOf(item);
|
||||
private static int classVariableIndex5(List<Class<?>> list, Class<?> item) {
|
||||
return 5 + list.indexOf(item);
|
||||
}
|
||||
|
||||
private static String generateClassName(PersistentEntity<?, ?> entity) {
|
||||
|
||||
@@ -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<String> name;
|
||||
private final Lazy<String> toString;
|
||||
private final Lazy<Optional<Method>> wither;
|
||||
|
||||
private Property(TypeInformation<?> type, Optional<Field> field, Optional<PropertyDescriptor> 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<Method> 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<Method> getWither() {
|
||||
return wither.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the property exposes a getter or a setter.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Object[]> parameters = new ArrayList<>();
|
||||
List<String> 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 {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Object, PersistentPropertyAccessor> propertyAccessorFunction;
|
||||
|
||||
public PersistentPropertyAccessorTests(Function<Object, PersistentPropertyAccessor> propertyAccessor,
|
||||
String displayName) {
|
||||
|
||||
this.propertyAccessorFunction = propertyAccessor;
|
||||
}
|
||||
|
||||
@Parameters(name = "{1}")
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Object[]> parameters() {
|
||||
|
||||
List<Object[]> parameters = new ArrayList<>();
|
||||
|
||||
ClassGeneratingPropertyAccessorFactory factory = new ClassGeneratingPropertyAccessorFactory();
|
||||
|
||||
Function<Object, PersistentPropertyAccessor> beanWrapper = BeanWrapper::new;
|
||||
Function<Object, PersistentPropertyAccessor> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user