Introduce PersistentProperty.isReadable.

isReadable reports whether a property can be read through PersistentPropertyAccessor, by either using property access through setters, a wither, Kotlin Copy method or by accessing the field directly.

Closes #2915
Original pull request: #2916
This commit is contained in:
Mark Paluch
2023-08-28 11:38:05 +02:00
parent 6f981143ce
commit df543cd50e
6 changed files with 100 additions and 45 deletions

View File

@@ -262,6 +262,16 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
*/
boolean isWritable();
/**
* Returns whether the current property is readable through {@link PersistentPropertyAccessor}, i.e. if it is not
* {@link #isTransient()}, if the value can be set on the current instance or read to create a new instance as per
* {@link #getWither()} or via Kotlin Copy methods.
*
* @return
* @since 3.2
*/
boolean isReadable();
/**
* Returns whether the current property is immutable, i.e. if there is no setter or the backing {@link Field} is
* {@code final}.

View File

@@ -27,6 +27,7 @@ import java.util.stream.Collectors;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.data.util.TypeInformation;
@@ -70,6 +71,7 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
private final Method setter;
private final Field field;
private final Method wither;
private final Lazy<Boolean> readable;
private final boolean immutable;
public AbstractPersistentProperty(Property property, PersistentEntity<?, P> owner,
@@ -103,11 +105,27 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
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;
}
this.immutable = setter == null && (field == null || Modifier.isFinal(field.getModifiers()));
this.readable = Lazy.of(() -> {
if (setter != null) {
return true;
}
if (wither != null) {
return true;
}
if (KotlinReflectionUtils.isDataClass(owner.getType()) && KotlinCopyMethod.hasKotlinCopyMethodFor(this)) {
return true;
}
if (field != null && !Modifier.isFinal(field.getModifiers())) {
return true;
}
return false;
});
}
protected abstract Association<P> createAssociation();
@@ -170,6 +188,7 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
}
@Nullable
@Override
public Field getField() {
return this.field;
}
@@ -190,6 +209,11 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
return !isTransient();
}
@Override
public boolean isReadable() {
return !isTransient() && readable.get();
}
@Override
public boolean isImmutable() {
return immutable;
@@ -313,10 +337,8 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
Set<TypeInformation<?>> result = detectEntityTypes(typeToStartWith);
return result.stream()
.filter(it -> !simpleTypes.isSimpleType(it.getType()))
.filter(it -> !it.getType().equals(ASSOCIATION_TYPE))
.collect(Collectors.toSet());
return result.stream().filter(it -> !simpleTypes.isSimpleType(it.getType()))
.filter(it -> !it.getType().equals(ASSOCIATION_TYPE)).collect(Collectors.toSet());
}
private Set<TypeInformation<?>> detectEntityTypes(@Nullable TypeInformation<?> source) {

View File

@@ -1029,7 +1029,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
visitWithProperty(entity, property, mv, internalClassName, wither);
}
if (hasKotlinCopyMethod(property)) {
if (KotlinDetector.isKotlinType(entity.getType()) && KotlinCopyMethod.hasKotlinCopyMethodFor(property)) {
visitKotlinCopy(entity, property, mv, internalClassName);
}
@@ -1429,38 +1429,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
* @return {@literal true} if object mutation is supported.
*/
static boolean supportsMutation(PersistentProperty<?> property) {
if (property.isImmutable()) {
if (property.getWither() != null) {
return true;
}
if (hasKotlinCopyMethod(property)) {
return true;
}
}
return (property.usePropertyAccess() && property.getSetter() != null)
|| (property.getField() != null && !Modifier.isFinal(property.getField().getModifiers()));
}
/**
* Check whether the owning type of {@link PersistentProperty} declares a {@literal copy} method or {@literal copy}
* method with parameter defaulting.
*
* @param property must not be {@literal null}.
* @return
*/
private static boolean hasKotlinCopyMethod(PersistentProperty<?> property) {
Class<?> type = property.getOwner().getType();
if (isAccessible(type) && KotlinDetector.isKotlinType(type)) {
return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property)).isPresent();
}
return false;
return (property.usePropertyAccess() && property.getSetter() != null) || property.isReadable();
}
/**

View File

@@ -17,7 +17,6 @@ package org.springframework.data.mapping.model;
import java.util.function.Function;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
@@ -76,7 +75,7 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
PersistentEntity<?, ? extends PersistentProperty<?>> owner = property.getOwner();
PersistentPropertyAccessor<T> delegate = delegateFunction.apply(this.bean);
if (!property.isImmutable() || property.getWither() != null || KotlinDetector.isKotlinType(owner.getType())) {
if (property.isReadable()) {
delegate.setProperty(property, value);
this.bean = delegate.getBean();

View File

@@ -97,6 +97,20 @@ class KotlinCopyMethod {
});
}
/**
* Check whether the owning type of {@link PersistentProperty} declares a {@literal copy} method or {@literal copy}
* method with parameter defaulting.
*
* @param property must not be {@literal null}.
* @return
*/
public static boolean hasKotlinCopyMethodFor(PersistentProperty<?> property) {
Class<?> type = property.getOwner().getType();
return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property)).isPresent();
}
public Method getPublicCopyMethod() {
return this.publicCopyMethod;
}