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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user