package com.fasterxml.jackson.databind.introspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
public class TestPOJOPropertiesCollector
extends BaseMapTest
{
static class Simple {
public int value;
@JsonProperty("value")
public void valueSetter(int v) { value = v; }
@JsonProperty("value")
public int getFoobar() { return value; }
}
static class SimpleFieldDeser
{
@JsonDeserialize String[] values;
}
static class SimpleGetterVisibility {
public int getA() { return 0; }
protected int getB() { return 1; }
@SuppressWarnings("unused")
private int getC() { return 2; }
}
// Class for testing 'shared ignore'
static class Empty {
public int value;
public void setValue(int v) { value = v; }
@JsonIgnore
public int getValue() { return value; }
}
static class IgnoredSetter {
@JsonProperty
public int value;
@JsonIgnore
public void setValue(int v) { value = v; }
public int getValue() { return value; }
}
static class ImplicitIgnores {
@JsonIgnore public int a;
@JsonIgnore public void setB(int b) { }
public int c;
}
// Should find just one setter for "y", due to partial ignore
static class IgnoredRenamedSetter {
@JsonIgnore public void setY(int value) { }
@JsonProperty("y") void foobar(int value) { }
}
// should produce a single property, "x"
static class RenamedProperties {
@JsonProperty("x")
public int value;
public void setValue(int v) { value = v; }
public int getX() { return value; }
}
static class RenamedProperties2
{
@JsonProperty("renamed")
public int getValue() { return 1; }
public void setValue(int x) { }
}
// Testing that we can "merge" properties with renaming
static class MergedProperties {
public int x;
@JsonProperty("x")
public void setFoobar(int v) { x = v; }
}
// Testing that property order is obeyed, even for deserialization purposes
@JsonPropertyOrder({"a", "b", "c", "d"})
static class SortedProperties
{
public int b;
public int c;
public void setD(int value) { }
public void setA(int value) { }
}
// [JACKSON-700]: test property type detection, selection
static class TypeTestBean
{
protected Long value;
@JsonCreator
public TypeTestBean(@JsonProperty("value") String value) { }
// If you remove this method, the test will pass
public Integer getValue() { return 0; }
}
static class Jackson703
{
private List<FoodOrgLocation> location = new ArrayList<FoodOrgLocation>();
{
location.add(new FoodOrgLocation());
}
public List<FoodOrgLocation> getLocation() { return location; }
}
static class FoodOrgLocation
{
protected Long id;
public String name;
protected Location location;
public FoodOrgLocation() {
location = new Location();
}
public FoodOrgLocation(final Location foodOrg) { }
public FoodOrgLocation(final Long id, final String name, final Location location) { }
public Location getLocation() { return location; }
}
static class Location {
public BigDecimal lattitude;
public BigDecimal longitude;
public Location() { }
public Location(final BigDecimal lattitude, final BigDecimal longitude) { }
}
class Issue701Bean { // important: non-static!
private int i;
// annotation does not matter -- just need one on the last argument
public Issue701Bean(@JsonProperty int i) { this.i = i; }
public int getX() { return i; }
}
static class Issue744Bean
{
protected Map<String,Object> additionalProperties;
@JsonAnySetter
public void addAdditionalProperty(String key, Object value) {
if (additionalProperties == null) additionalProperties = new HashMap<String, Object>();
additionalProperties.put(key,value);
}
public void setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
}
@JsonAnyGetter
public Map<String,Object> getAdditionalProperties() { return additionalProperties; }
@JsonIgnore
public String getName() {
return (String) additionalProperties.get("name");
}
}
static class PropDescBean
{
public final static String A_DESC = "That's A!";
public final static int B_INDEX = 3;
@JsonPropertyDescription(A_DESC)
public String a;
protected int b;
public String getA() { return a; }
public void setA(String a) { this.a = a; }
@JsonProperty(required=true, index=B_INDEX, defaultValue="13")
public int getB() { return b; }
}
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
@interface A {}
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
@interface B {}
static class DuplicateGetterBean
{
@A
public boolean isBloop() { return true; }
@B
public boolean getBloop() { return true; }
}
static class DuplicateGetterCreatorBean
{
public DuplicateGetterCreatorBean(@JsonProperty("bloop") @A boolean bloop) {}
public boolean isBloop() { return true; }
public boolean getBloop() { return true; }
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
private final ObjectMapper MAPPER = objectMapper();
public void testSimple()
{
POJOPropertiesCollector coll = collector(MAPPER,
Simple.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("value");
assertNotNull(prop);
assertTrue(prop.hasSetter());
assertTrue(prop.hasGetter());
assertTrue(prop.hasField());
}
public void testSimpleFieldVisibility()
{
// false -> deserialization
POJOPropertiesCollector coll = collector(MAPPER,
SimpleFieldDeser.class, false);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("values");
assertNotNull(prop);
assertFalse(prop.hasSetter());
assertFalse(prop.hasGetter());
assertTrue(prop.hasField());
}
public void testSimpleGetterVisibility()
{
POJOPropertiesCollector coll = collector(MAPPER,
SimpleGetterVisibility.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("a");
assertNotNull(prop);
assertFalse(prop.hasSetter());
assertTrue(prop.hasGetter());
assertFalse(prop.hasField());
}
// Unit test for verifying that a single @JsonIgnore can remove the
// whole property, unless explicit property marker exists
public void testEmpty()
{
POJOPropertiesCollector coll = collector(MAPPER,
Empty.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(0, props.size());
}
// Unit test for verifying handling of 'partial' @JsonIgnore; that is,
// if there is at least one explicit annotation to indicate property,
// only parts that are ignored are, well, ignored
public void testPartialIgnore()
{
POJOPropertiesCollector coll = collector(MAPPER,
IgnoredSetter.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("value");
assertNotNull(prop);
assertFalse(prop.hasSetter());
assertTrue(prop.hasGetter());
assertTrue(prop.hasField());
}
public void testSimpleRenamed()
{
POJOPropertiesCollector coll = collector(MAPPER,
RenamedProperties.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("x");
assertNotNull(prop);
assertTrue(prop.hasSetter());
assertTrue(prop.hasGetter());
assertTrue(prop.hasField());
}
public void testSimpleRenamed2()
{
POJOPropertiesCollector coll = collector(MAPPER,
RenamedProperties2.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("renamed");
assertNotNull(prop);
assertTrue(prop.hasSetter());
assertTrue(prop.hasGetter());
assertFalse(prop.hasField());
}
public void testMergeWithRename()
{
POJOPropertiesCollector coll = collector(MAPPER,
MergedProperties.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("x");
assertNotNull(prop);
assertTrue(prop.hasSetter());
assertFalse(prop.hasGetter());
assertTrue(prop.hasField());
}
public void testSimpleIgnoreAndRename()
{
POJOPropertiesCollector coll = collector(MAPPER,
IgnoredRenamedSetter.class, true);
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
POJOPropertyBuilder prop = props.get("y");
assertNotNull(prop);
assertTrue(prop.hasSetter());
assertFalse(prop.hasGetter());
assertFalse(prop.hasField());
}
public void testGlobalVisibilityForGetters()
{
ObjectMapper m = new ObjectMapper();
m.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
POJOPropertiesCollector coll = collector(m, SimpleGetterVisibility.class, true);
// should be 1, expect that we disabled getter auto-detection, so
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(0, props.size());
}
public void testCollectionOfIgnored()
{
POJOPropertiesCollector coll = collector(MAPPER, ImplicitIgnores.class, false);
// should be 1, due to ignorals
Map<String, POJOPropertyBuilder> props = coll.getPropertyMap();
assertEquals(1, props.size());
// but also have 2 ignored properties
Collection<String> ign = coll.getIgnoredPropertyNames();
assertEquals(2, ign.size());
assertTrue(ign.contains("a"));
assertTrue(ign.contains("b"));
}
public void testSimpleOrderingForDeserialization()
{
POJOPropertiesCollector coll = collector(MAPPER, SortedProperties.class, false);
List<BeanPropertyDefinition> props = coll.getProperties();
assertEquals(4, props.size());
assertEquals("a", props.get(0).getName());
assertEquals("b", props.get(1).getName());
assertEquals("c", props.get(2).getName());
assertEquals("d", props.get(3).getName());
}
public void testSimpleWithType()
{
// first for serialization; should base choice on getter
POJOPropertiesCollector coll = collector(MAPPER, TypeTestBean.class, true);
List<BeanPropertyDefinition> props = coll.getProperties();
assertEquals(1, props.size());
assertEquals("value", props.get(0).getName());
AnnotatedMember m = props.get(0).getAccessor();
assertTrue(m instanceof AnnotatedMethod);
assertEquals(Integer.class, m.getRawType());
// then for deserialization; prefer ctor param
coll = collector(MAPPER, TypeTestBean.class, false);
props = coll.getProperties();
assertEquals(1, props.size());
assertEquals("value", props.get(0).getName());
m = props.get(0).getMutator();
assertEquals(AnnotatedParameter.class, m.getClass());
assertEquals(String.class, m.getRawType());
}
public void testInnerClassWithAnnotationsInCreator() throws Exception
{
BasicBeanDescription beanDesc;
// first with serialization
beanDesc = MAPPER.getSerializationConfig().introspect(MAPPER.constructType(Issue701Bean.class));
assertNotNull(beanDesc);
// then with deserialization
beanDesc = MAPPER.getDeserializationConfig().introspect(MAPPER.constructType(Issue701Bean.class));
assertNotNull(beanDesc);
}
public void testUseAnnotationsFalse() throws Exception
{
// note: need a separate mapper, need to reconfigure
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.USE_ANNOTATIONS, false);
BasicBeanDescription beanDesc = mapper.getSerializationConfig().introspect(mapper.constructType(Jackson703.class));
assertNotNull(beanDesc);
Jackson703 bean = new Jackson703();
String json = mapper.writeValueAsString(bean);
assertNotNull(json);
}
public void testJackson744() throws Exception
{
BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect
(MAPPER.constructType(Issue744Bean.class));
assertNotNull(beanDesc);
AnnotatedMember setter = beanDesc.findAnySetterAccessor();
assertNotNull(setter);
assertEquals("addAdditionalProperty", setter.getName());
assertTrue(setter instanceof AnnotatedMethod);
}
// [databind#269]: Support new @JsonPropertyDescription
public void testPropertyDesc() throws Exception
{
// start via deser
BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect(MAPPER.constructType(PropDescBean.class));
_verifyProperty(beanDesc, true, false, "13");
// and then via ser:
beanDesc = MAPPER.getSerializationConfig().introspect(MAPPER.constructType(PropDescBean.class));
_verifyProperty(beanDesc, true, false, "13");
}
// [databind#438]: Support @JsonProperty.index
public void testPropertyIndex() throws Exception
{
BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect(MAPPER.constructType(PropDescBean.class));
_verifyProperty(beanDesc, false, true, "13");
beanDesc = MAPPER.getSerializationConfig().introspect(MAPPER.constructType(PropDescBean.class));
_verifyProperty(beanDesc, false, true, "13");
}
public void testDuplicateGetters() throws Exception
{
POJOPropertiesCollector coll = collector(MAPPER, DuplicateGetterBean.class, true);
List<BeanPropertyDefinition> props = coll.getProperties();
assertEquals(1, props.size());
BeanPropertyDefinition prop = props.get(0);
assertEquals("bloop", prop.getName());
assertTrue(prop.getGetter().hasAnnotation(A.class));
assertTrue(prop.getGetter().hasAnnotation(B.class));
}
public void testDuplicateGettersCreator() throws Exception
{
POJOPropertiesCollector coll = collector(MAPPER, DuplicateGetterCreatorBean.class, true);
List<BeanPropertyDefinition> props = coll.getProperties();
assertEquals(1, props.size());
POJOPropertyBuilder prop = (POJOPropertyBuilder) props.get(0);
assertEquals("bloop", prop.getName());
// Can't call getGetter or the duplicate will be removed
assertTrue(prop._getters.value.hasAnnotation(A.class));
assertNotNull(prop._getters.next);
assertTrue(prop._getters.next.value.hasAnnotation(A.class));
}
private void _verifyProperty(BeanDescription beanDesc,
boolean verifyDesc, boolean verifyIndex, String expDefaultValue)
{
assertNotNull(beanDesc);
List<BeanPropertyDefinition> props = beanDesc.findProperties();
assertEquals(2, props.size());
for (BeanPropertyDefinition prop : props) {
String name = prop.getName();
final PropertyMetadata md = prop.getMetadata();
if ("a".equals(name)) {
assertFalse(md.isRequired());
assertNull(md.getRequired());
if (verifyDesc) {
assertEquals(PropDescBean.A_DESC, md.getDescription());
}
if (verifyIndex) {
assertNull(md.getIndex());
}
} else if ("b".equals(name)) {
assertTrue(md.isRequired());
assertEquals(Boolean.TRUE, md.getRequired());
if (verifyDesc) {
assertNull(md.getDescription());
}
if (verifyIndex) {
assertEquals(Integer.valueOf(PropDescBean.B_INDEX), md.getIndex());
}
if (expDefaultValue != null) {
assertEquals(expDefaultValue, md.getDefaultValue());
}
} else {
fail("Unrecognized property '"+name+"'");
}
}
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
protected POJOPropertiesCollector collector(ObjectMapper m0,
Class<?> cls, boolean forSerialization)
{
BasicClassIntrospector bci = new BasicClassIntrospector();
// no real difference between serialization, deserialization, at least here
if (forSerialization) {
return bci.collectProperties(m0.getSerializationConfig(),
m0.constructType(cls), null, true, "set");
}
return bci.collectProperties(m0.getDeserializationConfig(),
m0.constructType(cls), null, false, "set");
}
}