DefaultCreatorDetection4584Test.java
package com.fasterxml.jackson.databind.introspect;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
// Tests for [databind#4584]: extension point for discovering "Default"
// Creator (primary Creator, usually constructor, used in case no creator
// explicitly annotated)
//
// @since 2.18
public class DefaultCreatorDetection4584Test extends DatabindTestUtil
{
static class POJO4584 {
final String value;
POJO4584(@ImplicitName("v") String v, @ImplicitName("bogus") int bogus) {
value = v;
}
public POJO4584(@ImplicitName("list") List<Object> list) {
value = "List["+((list == null) ? -1 : list.size())+"]";
}
public POJO4584(@ImplicitName("array") Object[] array) {
value = "Array["+((array == null) ? -1 : array.length)+"]";
}
public static POJO4584 factoryInt(@ImplicitName("i") int i) {
return new POJO4584("int["+i+"]", 0);
}
public static POJO4584 factoryString(@ImplicitName("v") String v) {
return new POJO4584(v, 0);
}
@Override
public boolean equals(Object o) {
return (o instanceof POJO4584) && Objects.equals(((POJO4584) o).value, value);
}
@Override
public String toString() {
return "'"+value+"'";
}
}
// Let's also ensure that explicit annotation trumps Primary
static class POJO4584Annotated {
String value;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
POJO4584Annotated(@ImplicitName("v") String v, @ImplicitName("bogus") int bogus) {
value = v;
}
POJO4584Annotated(@ImplicitName("i") int i, @ImplicitName("foobar") String f) {
throw new Error("Should NOT get called!");
}
public static POJO4584Annotated wrongInt(@ImplicitName("i") int i) {
throw new Error("Should NOT get called!");
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static POJO4584Annotated factoryString(String v) {
return new POJO4584Annotated(v, 0);
}
@Override
public boolean equals(Object o) {
return (o instanceof POJO4584Annotated) && Objects.equals(((POJO4584Annotated) o).value, value);
}
@Override
public String toString() {
return "'"+value+"'";
}
}
static class PrimaryCreatorFindingIntrospector extends ImplicitNameIntrospector
{
private static final long serialVersionUID = 1L;
private final Class<?>[] _argTypes;
private JsonCreator.Mode _mode;
private final String _factoryName;
public PrimaryCreatorFindingIntrospector(JsonCreator.Mode mode,
Class<?>... argTypes) {
_mode = mode;
_factoryName = null;
_argTypes = argTypes;
}
public PrimaryCreatorFindingIntrospector(JsonCreator.Mode mode,
String factoryName) {
_mode = mode;
_factoryName = factoryName;
_argTypes = new Class<?>[0];
}
@Override
public PotentialCreator findDefaultCreator(MapperConfig<?> config,
AnnotatedClass valueClass,
List<PotentialCreator> declaredConstructors,
List<PotentialCreator> declaredFactories)
{
// Apply to all test POJOs here but nothing else
if (!valueClass.getRawType().toString().contains("4584")) {
return null;
}
if (_factoryName != null) {
for (PotentialCreator ctor : declaredFactories) {
if (ctor.creator().getName().equals(_factoryName)) {
return ctor;
}
}
return null;
}
List<PotentialCreator> combo = new ArrayList<>(declaredConstructors);
combo.addAll(declaredFactories);
final int argCount = _argTypes.length;
for (PotentialCreator ctor : combo) {
if (ctor.paramCount() == argCount) {
int i = 0;
for (; i < argCount; ++i) {
if (_argTypes[i] != ctor.param(i).getRawType()) {
break;
}
}
if (i == argCount) {
ctor.overrideMode(_mode);
return ctor;
}
}
}
return null;
}
}
/*
/**********************************************************************
/* Test methods; simple properties-based Creators
/**********************************************************************
*/
@Test
public void testCanonicalConstructor1ArgPropertiesCreator() throws Exception
{
// Instead of delegating, try denoting List-taking 1-arg one:
assertEquals(POJO4584.factoryString("List[2]"),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.PROPERTIES,
List.class))
.readValue(a2q("{'list':[ 1, 2]}")));
// ok to map from empty Object too
assertEquals(POJO4584.factoryString("List[-1]"),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.PROPERTIES,
List.class))
.readValue(a2q("{}")));
}
@Test
public void testCanonicalConstructor2ArgPropertiesCreator() throws Exception
{
// Mark the "true" canonical
assertEquals(POJO4584.factoryString("abc"),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.PROPERTIES,
String.class, Integer.TYPE))
.readValue(a2q("{'bogus':12, 'v':'abc' }")));
// ok to map from empty Object too
assertEquals(POJO4584.factoryString(null),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.PROPERTIES,
String.class, Integer.TYPE))
.without(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
.readValue(a2q("{}")));
}
/*
/**********************************************************************
/* Test methods; simple delegation-based Creators
/**********************************************************************
*/
@Test
public void testCanonicalConstructorDelegatingIntCreator() throws Exception
{
assertEquals(POJO4584.factoryString("int[42]"),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.DELEGATING,
Integer.TYPE))
.readValue(a2q("42")));
}
@Test
public void testCanonicalConstructorDelegatingListCreator() throws Exception
{
assertEquals(POJO4584.factoryString("List[3]"),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.DELEGATING,
List.class))
.readValue(a2q("[1, 2, 3]")));
}
@Test
public void testCanonicalConstructorDelegatingArrayCreator() throws Exception
{
assertEquals(POJO4584.factoryString("Array[1]"),
readerWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.DELEGATING,
Object[].class))
.readValue(a2q("[true]")));
}
/*
/**********************************************************************
/* Test methods; deal with explicitly annotated types
/**********************************************************************
*/
// Here we test to ensure that
@Test
public void testDelegatingVsExplicit() throws Exception
{
assertEquals(POJO4584Annotated.factoryString("abc"),
mapperWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.DELEGATING,
"wrongInt"))
.readerFor(POJO4584Annotated.class)
.readValue(a2q("{'v':'abc','bogus':3}")));
}
@Test
public void testPropertiesBasedVsExplicit() throws Exception
{
assertEquals(POJO4584Annotated.factoryString("abc"),
mapperWith(new PrimaryCreatorFindingIntrospector(JsonCreator.Mode.PROPERTIES,
Integer.TYPE, String.class))
.readerFor(POJO4584Annotated.class)
.readValue(a2q("{'v':'abc','bogus':3}")));
}
/*
/**********************************************************************
/* Helper methods
/**********************************************************************
*/
private ObjectReader readerWith(AnnotationIntrospector intr) {
return mapperWith(intr).readerFor(POJO4584.class);
}
private ObjectMapper mapperWith(AnnotationIntrospector intr) {
return JsonMapper.builder()
.annotationIntrospector(intr)
.build();
}
}