Skip to content

Commit 5df1a6c

Browse files
committed
Fix #1044
1 parent f2b0ac5 commit 5df1a6c

File tree

10 files changed

+145
-33
lines changed

10 files changed

+145
-33
lines changed

release-notes/VERSION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Project: jackson-databind
5252
(suggested by David B)
5353
#1043: @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) does not work on fields
5454
(reported by fabiolaa@github)
55+
#1044: Add `AnnotationIntrospector.resolveSetterConflict(...)` to allow custom setter conflict resolution
56+
(suggested by clydebarrow@github)
5557
- Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external
5658
value (as per JSON Schema spec)
5759

src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
* so that different sets of annotations can be supported, and support
3131
* plugged-in dynamically.
3232
*<p>
33+
* Although default implementations are based on using annotations as the only
34+
* (or at least main) information source, custom implementations are not limited
35+
* in such a way, and in fact there is no expectation they should be. So the name
36+
* is bit of misnomer; this is a general configuration introspection facility.
37+
*<p>
3338
* NOTE: due to rapid addition of new methods (and changes to existing methods),
3439
* it is <b>strongly</b> recommended that custom implementations should not directly
3540
* extend this class, but rather extend {@link NopAnnotationIntrospector}.
@@ -559,6 +564,19 @@ public TypeResolverBuilder<?> findPropertyContentTypeResolver(MapperConfig<?> co
559564
*/
560565
public JsonProperty.Access findPropertyAccess(Annotated ann) { return null; }
561566

567+
/**
568+
* Method called in cases where a class has two methods eligible to be used
569+
* for the same logical property, and default logic is not enough to figure
570+
* out clear precedence. Introspector may try to choose one to use; or, if
571+
* unable, return `null` to indicate it can not resolve the problem.
572+
*
573+
* @since 2.7
574+
*/
575+
public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
576+
AnnotatedMethod setter1, AnnotatedMethod setter2) {
577+
return null;
578+
}
579+
562580
/*
563581
/**********************************************************
564582
/* Serialization: general annotations

src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
/**
1212
* Checked exception used to signal fatal problems with mapping of
13-
* content.
13+
* content, distinct from low-level I/O problems (signaled using
14+
* simple {@link java.io.IOException}s) or data encoding/decoding
15+
* problems (signaled with {@link com.fasterxml.jackson.core.JsonParseException},
16+
* {@link com.fasterxml.jackson.core.JsonGenerationException}).
1417
*<p>
1518
* One additional feature is the ability to denote relevant path
1619
* of references (during serialization/deserialization) to help in

src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,20 @@ public JsonProperty.Access findPropertyAccess(Annotated ann) {
467467
return JsonProperty.Access.AUTO;
468468
}
469469

470+
@Override // since 2.7
471+
public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
472+
AnnotatedMethod setter1, AnnotatedMethod setter2)
473+
{
474+
AnnotatedMethod res = _primary.resolveSetterConflict(config, setter1, setter2);
475+
if (res == null) {
476+
res = _secondary.resolveSetterConflict(config, setter1, setter2);
477+
}
478+
return res;
479+
}
480+
470481
// // // Serialization: type refinements
471482

472-
// since 2.7
473-
@Override
483+
@Override // since 2.7
474484
public JavaType refineSerializationType(MapperConfig<?> config,
475485
Annotated a, JavaType baseType) throws JsonMappingException
476486
{

src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,34 @@ public Class<?>[] findViews(Annotated a)
414414
return (ann == null) ? null : ann.value();
415415
}
416416

417+
@Override // since 2.7
418+
public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
419+
AnnotatedMethod setter1, AnnotatedMethod setter2)
420+
{
421+
Class<?> cls1 = setter1.getRawParameterType(0);
422+
Class<?> cls2 = setter2.getRawParameterType(0);
423+
424+
// First: prefer primitives over non-primitives
425+
// 11-Dec-2015, tatu: TODO, perhaps consider wrappers for primitives too?
426+
if (cls1.isPrimitive()) {
427+
if (!cls2.isPrimitive()) {
428+
return setter1;
429+
}
430+
} else if (cls2.isPrimitive()) {
431+
return setter2;
432+
}
433+
434+
if (cls1 == String.class) {
435+
if (cls2 != String.class) {
436+
return setter1;
437+
}
438+
} else if (cls2 == String.class) {
439+
return setter2;
440+
}
441+
442+
return null;
443+
}
444+
417445
/*
418446
/**********************************************************
419447
/* Annotations for Polymorphic Type handling

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -784,9 +784,8 @@ protected void _renameUsing(Map<String, POJOPropertyBuilder> propMap,
784784
for (POJOPropertyBuilder prop : props) {
785785
PropertyName fullName = prop.getFullName();
786786
String rename = null;
787-
// As per [#428](https://github.com/FasterXML/jackson-databind/issues/428) need
788-
// to skip renaming if property has explicitly defined name, unless feature
789-
// is enabled
787+
// As per [databind#428] need to skip renaming if property has
788+
// explicitly defined name, unless feature is enabled
790789
if (!prop.isExplicitlyNamed() || _config.isEnabled(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING)) {
791790
if (_forSerialization) {
792791
if (prop.hasGetter()) {
@@ -983,8 +982,8 @@ protected POJOPropertyBuilder _property(Map<String, POJOPropertyBuilder> props,
983982
{
984983
POJOPropertyBuilder prop = props.get(implName);
985984
if (prop == null) {
986-
prop = new POJOPropertyBuilder(PropertyName.construct(implName),
987-
_annotationIntrospector, _forSerialization);
985+
prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization,
986+
PropertyName.construct(implName));
988987
props.put(implName, prop);
989988
}
990989
return prop;

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import com.fasterxml.jackson.annotation.JsonInclude;
66
import com.fasterxml.jackson.annotation.JsonProperty;
7-
87
import com.fasterxml.jackson.databind.*;
8+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
99
import com.fasterxml.jackson.databind.util.ClassUtil;
1010

1111
/**
@@ -22,6 +22,8 @@ public class POJOPropertyBuilder
2222
*/
2323
protected final boolean _forSerialization;
2424

25+
protected final MapperConfig<?> _config;
26+
2527
protected final AnnotationIntrospector _annotationIntrospector;
2628

2729
/**
@@ -45,25 +47,27 @@ public class POJOPropertyBuilder
4547

4648
protected Linked<AnnotatedMethod> _setters;
4749

48-
public POJOPropertyBuilder(PropertyName internalName, AnnotationIntrospector ai,
49-
boolean forSerialization) {
50-
this(internalName, internalName, ai, forSerialization);
50+
public POJOPropertyBuilder(MapperConfig<?> config, AnnotationIntrospector ai,
51+
boolean forSerialization, PropertyName internalName) {
52+
this(config, ai, forSerialization, internalName, internalName);
5153
}
5254

53-
protected POJOPropertyBuilder(PropertyName internalName, PropertyName name,
54-
AnnotationIntrospector annotationIntrospector, boolean forSerialization)
55+
protected POJOPropertyBuilder(MapperConfig<?> config, AnnotationIntrospector ai,
56+
boolean forSerialization, PropertyName internalName, PropertyName name)
5557
{
58+
_config = config;
59+
_annotationIntrospector = ai;
5660
_internalName = internalName;
5761
_name = name;
58-
_annotationIntrospector = annotationIntrospector;
5962
_forSerialization = forSerialization;
6063
}
6164

6265
public POJOPropertyBuilder(POJOPropertyBuilder src, PropertyName newName)
6366
{
67+
_config = src._config;
68+
_annotationIntrospector = src._annotationIntrospector;
6469
_internalName = src._internalName;
6570
_name = newName;
66-
_annotationIntrospector = src._annotationIntrospector;
6771
_fields = src._fields;
6872
_ctorParameters = src._ctorParameters;
6973
_getters = src._getters;
@@ -269,9 +273,7 @@ public AnnotatedMethod getSetter()
269273
}
270274
// But if multiple, verify that they do not conflict...
271275
for (; next != null; next = next.next) {
272-
/* [JACKSON-255] Allow masking, i.e. do not report exception if one
273-
* is in super-class from the other
274-
*/
276+
// Allow masking, i.e. do not fail if one is in super-class from the other
275277
Class<?> currClass = curr.value.getDeclaringClass();
276278
Class<?> nextClass = next.value.getDeclaringClass();
277279
if (currClass != nextClass) {
@@ -283,20 +285,38 @@ public AnnotatedMethod getSetter()
283285
continue;
284286
}
285287
}
288+
AnnotatedMethod nextM = next.value;
289+
AnnotatedMethod currM = curr.value;
290+
286291
/* 30-May-2014, tatu: Two levels of precedence:
287292
*
288293
* 1. Regular setters ("setX(...)")
289294
* 2. Implicit, possible setters ("x(...)")
290295
*/
291-
int priNext = _setterPriority(next.value);
292-
int priCurr = _setterPriority(curr.value);
296+
int priNext = _setterPriority(nextM);
297+
int priCurr = _setterPriority(currM);
293298

294299
if (priNext != priCurr) {
295300
if (priNext < priCurr) {
296301
curr = next;
297302
}
298303
continue;
299304
}
305+
// 11-Dec-2015, tatu: As per [databind#1033] allow pluggable conflict resolution
306+
if (_annotationIntrospector != null) {
307+
AnnotatedMethod pref = _annotationIntrospector.resolveSetterConflict(_config,
308+
currM, nextM);
309+
310+
// note: should be one of nextM/currM; but no need to check
311+
if (pref == currM) {
312+
continue;
313+
}
314+
if (pref == nextM) {
315+
curr = next;
316+
continue;
317+
}
318+
}
319+
300320
throw new IllegalArgumentException("Conflicting setter definitions for property \""+getName()+"\": "
301321
+curr.value.getFullName()+" vs "+next.value.getFullName());
302322
}
@@ -408,7 +428,7 @@ public AnnotatedMember getPrimaryMember() {
408428
protected int _getterPriority(AnnotatedMethod m)
409429
{
410430
final String name = m.getName();
411-
// [#238]: Also, regular getters have precedence over "is-getters"
431+
// [databind#238]: Also, regular getters have precedence over "is-getters"
412432
if (name.startsWith("get") && name.length() > 3) {
413433
// should we check capitalization?
414434
return 1;
@@ -428,7 +448,7 @@ protected int _setterPriority(AnnotatedMethod m)
428448
}
429449
return 2;
430450
}
431-
451+
432452
/*
433453
/**********************************************************
434454
/* Implementations of refinement accessors
@@ -885,7 +905,7 @@ private void _explode(Collection<PropertyName> newNames,
885905
for (Linked<?> node = accessors; node != null; node = node.next) {
886906
PropertyName name = node.name;
887907
if (!node.isNameExplicit || name == null) { // no explicit name -- problem!
888-
// [Issue#541] ... but only as long as it's visible
908+
// [databind#541] ... but only as long as it's visible
889909
if (!node.isVisible) {
890910
continue;
891911
}
@@ -896,7 +916,8 @@ private void _explode(Collection<PropertyName> newNames,
896916
}
897917
POJOPropertyBuilder prop = props.get(name);
898918
if (prop == null) {
899-
prop = new POJOPropertyBuilder(_internalName, name, _annotationIntrospector, _forSerialization);
919+
prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization,
920+
_internalName, name);
900921
props.put(name, prop);
901922
}
902923
// ultra-clumsy, part 2 -- lambdas would be nice here
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.fasterxml.jackson.databind.introspect;
2+
3+
import com.fasterxml.jackson.databind.*;
4+
5+
// mostly for [databind#1033]
6+
public class SetterConflictTest extends BaseMapTest
7+
{
8+
// Should prefer primitives over Strings, more complex types, by default
9+
static class Issue1033Bean {
10+
public int value;
11+
12+
public void setValue(int v) { value = v; }
13+
public void setValue(Issue1033Bean foo) {
14+
throw new Error("Should not get called");
15+
}
16+
}
17+
18+
/*
19+
/**********************************************************
20+
/* Test methods
21+
/**********************************************************
22+
*/
23+
24+
private final ObjectMapper MAPPER = objectMapper();
25+
26+
public void testSetterPriority() throws Exception
27+
{
28+
Issue1033Bean bean = MAPPER.readValue(aposToQuotes("{'value':42}"),
29+
Issue1033Bean.class);
30+
assertEquals(42, bean.value);
31+
}
32+
}

src/test/java/com/fasterxml/jackson/databind/introspect/TestPropertyConflicts.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
*/
1313
public class TestPropertyConflicts extends BaseMapTest
1414
{
15-
// For [JACKSON-694]: error message for conflicting getters sub-optimal
15+
// error message for conflicting getters sub-optimal
1616
static class BeanWithConflict
1717
{
1818
public int getX() { return 3; }
1919
public boolean getx() { return false; }
2020
}
2121

22-
// [Issue#238]
22+
// [databind#238]
2323
protected static class Getters1A
2424
{
2525
@JsonProperty
@@ -67,7 +67,7 @@ public void _stuff(String value) {
6767
}
6868
}
6969

70-
// For [Issue#541]
70+
// For [databind#541]
7171
static class Bean541 {
7272
protected String str;
7373

@@ -81,14 +81,13 @@ public String getStr() {
8181
return str;
8282
}
8383
}
84-
84+
8585
/*
8686
/**********************************************************
8787
/* Test methods
8888
/**********************************************************
8989
*/
90-
91-
// for [JACKSON-694]
90+
9291
public void testFailWithDupProps() throws Exception
9392
{
9493
BeanWithConflict bean = new BeanWithConflict();
@@ -100,7 +99,7 @@ public void testFailWithDupProps() throws Exception
10099
}
101100
}
102101

103-
// [Issue#238]: ok to have getter, "isGetter"
102+
// [databind#238]: ok to have getter, "isGetter"
104103
public void testRegularAndIsGetter() throws Exception
105104
{
106105
final ObjectWriter writer = objectWriter();

src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
157157
{
158158
JavaType strType = config.constructType(String.class);
159159
// we need a valid BeanPropertyDefinition; this will do (just need name to match)
160-
POJOPropertyBuilder prop = new POJOPropertyBuilder(new PropertyName("bogus"), null, true);
160+
POJOPropertyBuilder prop = new POJOPropertyBuilder(config, null, true, new PropertyName("bogus"));
161161
try {
162162
AnnotatedField f = new AnnotatedField(null, EmptyBean.class.getDeclaredField("name"), null);
163163
beanProperties.add(new BeanPropertyWriter(prop, f, null,

0 commit comments

Comments
 (0)