Skip to content

Commit

Permalink
fix: SettableBeanPropertyDelegate implements all methods from Settabl…
Browse files Browse the repository at this point in the history
…eBeanProperty

Includes tests to ensure all methods are implemented in future
Jackson versions too.

Signed-off-by: Marc Nuri <[email protected]>
  • Loading branch information
manusa committed Sep 20, 2024
1 parent 783c5c2 commit 43e4181
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableAnyProperty;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.function.BooleanSupplier;

/**
Expand Down Expand Up @@ -66,6 +74,95 @@ public boolean isIgnorable() {
return delegate.isIgnorable();
}

/**
* {@inheritDoc}
*/
@Override
public void setViews(Class<?>[] views) {
delegate.setViews(views);
}

/**
* {@inheritDoc}
*/
@Override
public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
return delegate.getContextAnnotation(acls);
}

/**
* {@inheritDoc}
*/
@Override
public PropertyName getWrapperName() {
return delegate.getWrapperName();
}

/**
* {@inheritDoc}
*/
@Override
public NullValueProvider getNullValueProvider() {
return delegate.getNullValueProvider();
}

/**
* {@inheritDoc}
*/
@Override
public void depositSchemaProperty(JsonObjectFormatVisitor objectVisitor, SerializerProvider provider)
throws JsonMappingException {
delegate.depositSchemaProperty(objectVisitor, provider);
}

/**
* {@inheritDoc}
*/
@Override
public JavaType getType() {
return delegate.getType();
}

/**
* {@inheritDoc}
*/
@Override
public PropertyName getFullName() {
return delegate.getFullName();
}

/**
* {@inheritDoc}
*/
@Override
public void setManagedReferenceName(String n) {
delegate.setManagedReferenceName(n);
}

/**
* {@inheritDoc}
*/
@Override
public SettableBeanProperty withSimpleName(String simpleName) {
return _with(delegate.withSimpleName(simpleName));
}

/**
* {@inheritDoc}
*/
@Override
public void setObjectIdInfo(ObjectIdInfo objectIdInfo) {
delegate.setObjectIdInfo(objectIdInfo);
}

/**
* {@inheritDoc}
*/
@Override
public String toString() {
return delegate.toString();
}

/**
* Method called to deserialize appropriate value, given parser (and context), and set it using appropriate mechanism.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
Expand All @@ -30,14 +31,19 @@
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.CreatorProperty;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableAnyProperty;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.impl.FieldProperty;
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
Expand All @@ -50,10 +56,13 @@
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
Expand Down Expand Up @@ -168,7 +177,7 @@ void getCreatorIndex() {
// When
final int result = intFieldPropertyDelegating.getCreatorIndex();
// Then
assertThat(result).isEqualTo(0);
assertThat(result).isZero();
}

@Test
Expand Down Expand Up @@ -223,6 +232,146 @@ void isIgnorable() {
.isEqualTo(intFieldProperty.isIgnorable());
}

@Test
@DisplayName("setViews, should invoke setViews in delegate")
void setViews() {
// Given
assertThat(intFieldProperty.visibleInView(String.class)).isTrue();
// When
intFieldPropertyDelegating.setViews(new Class<?>[] { Integer.class });
// Then
assertThat(intFieldProperty.visibleInView(String.class)).isFalse();
}

@Test
@DisplayName("getContextAnnotation, should return getContextAnnotation result in delegate")
void getContextAnnotation() {
// When
final JsonIgnoreProperties result = intFieldPropertyDelegating
.getContextAnnotation(JsonIgnoreProperties.class);
// Then
assertThat(result)
.isSameAs(intFieldProperty.getContextAnnotation(JsonIgnoreProperties.class))
.extracting(JsonIgnoreProperties::ignoreUnknown)
.isEqualTo(true);
}

@Test
@DisplayName("getWrapperName, should return getWrapperName result in delegate")
void getWrapperName() {
// Given
final DeserializationConfig config = deserializationContext.getConfig()
.withAppendedAnnotationIntrospector(new JacksonAnnotationIntrospector() {
@Override
public PropertyName findWrapperName(Annotated ann) {
return PropertyName.construct("WrapperNameForTest");
}
});
final JavaType testBeanJavaType = objectMapper.constructType(TestBean.class);
final BasicBeanDescription testBeanDescription = (BasicBeanDescription) config
.introspect(testBeanJavaType);
final BeanPropertyDefinition testPropertyFieldDefinition = (testBeanDescription)
.findProperty(PropertyName.construct("intField"));
final SettableBeanProperty fieldProperty = new FieldProperty(testPropertyFieldDefinition, testBeanJavaType, null,
testBeanDescription.getClassAnnotations(), testPropertyFieldDefinition.getField());
final SettableBeanProperty fieldPropertyDelegating = new SettableBeanPropertyDelegate(fieldProperty, anySetter,
useAnySetter::get);
// When
final PropertyName result = fieldPropertyDelegating.getWrapperName();
// Then
assertThat(result)
.isSameAs(fieldProperty.getWrapperName())
.hasFieldOrPropertyWithValue("simpleName", "WrapperNameForTest");
}

@Test
@DisplayName("getNullValueProvider, should return getNullValueProvider result in delegate")
void getNullValueProvider() {
// When
final NullValueProvider result = intFieldPropertyDelegating.getNullValueProvider();
// Then
assertThat(result)
.isSameAs(intFieldProperty.getNullValueProvider());
}

@Test
@DisplayName("depositSchemaProperty, should invoke depositSchemaProperty in delegate")
void depositSchemaProperty() throws Exception {
// Given
final JsonObjectFormatVisitor visitor = new JsonObjectFormatVisitor.Base() {
@Override
public void optionalProperty(BeanProperty prop) {
((CreatorProperty) prop).setManagedReferenceName("visited");
}
};
// When
intFieldPropertyDelegating.depositSchemaProperty(visitor, objectMapper.getSerializerProvider());
// Then
assertThat(intFieldProperty.getManagedReferenceName())
.isEqualTo("visited");
}

@Test
@DisplayName("getFullName, should return getNullValueProvider result in delegate")
void getFullName() {
// When
final PropertyName result = intFieldPropertyDelegating.getFullName();
// Then
assertThat(result)
.isSameAs(intFieldProperty.getFullName())
.hasFieldOrPropertyWithValue("simpleName", "intField");
}

@Test
@DisplayName("setManagedReferenceName, should invoke setManagedReferenceName in delegate")
void setManagedReferenceName() {
// When
intFieldPropertyDelegating.setManagedReferenceName("the-managed-reference-name");
// Then
assertThat(intFieldPropertyDelegating.getManagedReferenceName())
.isEqualTo(intFieldProperty.getManagedReferenceName())
.isEqualTo("the-managed-reference-name");
}

@Test
@DisplayName("setObjectIdInfo, should invoke setObjectIdInfo in delegate")
void setObjectIdInfo() {
// When
intFieldPropertyDelegating.setObjectIdInfo(
new ObjectIdInfo(PropertyName.construct("objectId"), null, null, null));
// Then
assertThat(intFieldProperty.getObjectIdInfo())
.extracting(ObjectIdInfo::getPropertyName)
.hasFieldOrPropertyWithValue("simpleName", "objectId");
}

@Test
@DisplayName("withSimpleName, should invoke withSimpleName in delegate")
void withSimpleName() {
// When
final SettableBeanProperty result = intFieldPropertyDelegating
.withSimpleName("overridden-simple-name");
// Then
assertThat(result)
.isNotSameAs(intFieldPropertyDelegating)
.returns("overridden-simple-name", SettableBeanProperty::getName)
.extracting("delegate")
.asInstanceOf(InstanceOfAssertFactories.type(CreatorProperty.class))
.isNotSameAs(intFieldProperty)
.returns("overridden-simple-name", SettableBeanProperty::getName);
}

@Test
@DisplayName("toString, should return toString result in delegate")
void toStringTest() {
// When
final String result = intFieldPropertyDelegating.toString();
// Then
assertThat(result)
.isEqualTo(intFieldProperty.toString())
.isNotBlank();
}

@Test
@DisplayName("set, should set in delegate")
void set() throws IOException {
Expand Down Expand Up @@ -322,6 +471,31 @@ void deserializeSetAndReturnWithExceptionAndNullAnySetter() throws IOException {
}
}

@Nested
class ReflectionTest {

@Test
@DisplayName("all methods from superclass (SettableBeanProperty) are implemented by delegating class (SettableBeanPropertyDelegate)")
void allMethodsFromSuperclassAreImplementedByDelegatingClass() {
final Map<MethodSignature, Boolean> superclassMethods = Stream.of(SettableBeanProperty.class.getDeclaredMethods())
.filter(m -> !Modifier.isFinal(m.getModifiers()))
.filter(m -> !Modifier.isPrivate(m.getModifiers()))
.filter(m -> !Modifier.isAbstract(m.getModifiers()))
.filter(m -> !m.getName().startsWith("_"))
.map(MethodSignature::from)
.collect(Collectors.toMap(ms -> ms, ms -> false));
Stream.concat(
Stream.of(SettableBeanProperty.Delegating.class.getDeclaredMethods()),
Stream.of(SettableBeanPropertyDelegate.class.getDeclaredMethods()))
.map(MethodSignature::from)
.forEach(ms -> superclassMethods.computeIfPresent(ms, (k, v) -> true));
assertThat(superclassMethods)
.values()
.containsOnly(true);
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
private static final class TestBean {

@JsonProperty("intField")
Expand Down

0 comments on commit 43e4181

Please sign in to comment.