Skip to content

Commit d77b5cd

Browse files
committed
Merge branch '2.11'
2 parents 25bc76e + e1de974 commit d77b5cd

File tree

6 files changed

+181
-34
lines changed

6 files changed

+181
-34
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,40 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
503503
return null;
504504
}
505505

506+
/**
507+
* Method called on fields that are eligible candidates for properties
508+
* (that is, non-static member fields), but not necessarily selected (may
509+
* or may not be visible), to let fields affect name linking.
510+
* Call will be made after finding implicit name (which by default is just
511+
* name of the field, but may be overridden by introspector), but before
512+
* discovering other accessors.
513+
* If non-null name returned, it is to be used to find other accessors (getters,
514+
* setters, creator parameters) and replace their implicit names with that
515+
* of field's implicit name (assuming they differ).
516+
*<p>
517+
* Specific example (and initial use case is for support Kotlin's "is getter"
518+
* matching (see
519+
* <a href="https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html">Kotling Interop</a>
520+
* for details), in which field like '{@code isOpen}' would have implicit name of
521+
* "isOpen", match getter {@code getOpen()} and setter {@code setOpen(boolean)},
522+
* but use logical external name of "isOpen" (and not implicit name of getter/setter, "open"!).
523+
* To achieve this, field implicit name needs to remain "isOpen" but this method needs
524+
* to return name {@code PropertyName.construct("open")}: doing so will "pull in" getter
525+
* and/or setter, and rename them as "isOpen".
526+
*
527+
* @param config Effective mapper configuration in use
528+
* @param f Field to check
529+
* @param implName Implicit name of the field; usually name of field itself but not always,
530+
* used as the target name for accessors to rename.
531+
*
532+
* @return Name used to find other accessors to rename, if any; {@code null} to indicate
533+
* no renaming
534+
*/
535+
public PropertyName findRenameByField(MapperConfig<?> config,
536+
AnnotatedField f, PropertyName implName) {
537+
return null;
538+
}
539+
506540
/*
507541
/**********************************************************************
508542
/* Serialization: general annotations

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,16 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
474474
return res;
475475
}
476476

477+
@Override // since 2.11
478+
public PropertyName findRenameByField(MapperConfig<?> config,
479+
AnnotatedField f, PropertyName implName) {
480+
PropertyName n = _secondary.findRenameByField(config, f, implName);
481+
if (n == null) {
482+
n = _primary.findRenameByField(config, f, implName);
483+
}
484+
return n;
485+
}
486+
477487
// // // Serialization: type refinements
478488

479489
@Override

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,13 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
536536
return null;
537537
}
538538

539+
@Override // since 2.11
540+
public PropertyName findRenameByField(MapperConfig<?> config,
541+
AnnotatedField f, PropertyName implName) {
542+
// Nothing to report, only used by modules. But define just as documentation
543+
return null;
544+
}
545+
539546
/*
540547
/**********************************************************************
541548
/* Annotations for Polymorphic Type handling

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

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
public class POJOPropertiesCollector
2222
{
2323
/*
24-
/**********************************************************
24+
/**********************************************************************
2525
/* Configuration
26-
/**********************************************************
26+
/**********************************************************************
2727
*/
2828

2929
/**
@@ -58,11 +58,11 @@ public class POJOPropertiesCollector
5858
* but differs for builder objects ("with" by default).
5959
*/
6060
protected final String _mutatorPrefix;
61-
61+
6262
/*
63-
/**********************************************************
63+
/**********************************************************************
6464
/* Collected property information
65-
/**********************************************************
65+
/**********************************************************************
6666
*/
6767

6868
/**
@@ -106,11 +106,11 @@ public class POJOPropertiesCollector
106106
* value injection.
107107
*/
108108
protected LinkedHashMap<Object, AnnotatedMember> _injectables;
109-
109+
110110
/*
111-
/**********************************************************
111+
/**********************************************************************
112112
/* Life-cycle
113-
/**********************************************************
113+
/**********************************************************************
114114
*/
115115

116116
protected POJOPropertiesCollector(MapperConfig<?> config, boolean forSerialization,
@@ -133,9 +133,9 @@ protected POJOPropertiesCollector(MapperConfig<?> config, boolean forSerializati
133133
}
134134

135135
/*
136-
/**********************************************************
136+
/**********************************************************************
137137
/* Public API
138-
/**********************************************************
138+
/**********************************************************************
139139
*/
140140

141141
public MapperConfig<?> getConfig() {
@@ -267,9 +267,9 @@ protected Map<String, POJOPropertyBuilder> getPropertyMap() {
267267
}
268268

269269
/*
270-
/**********************************************************
270+
/**********************************************************************
271271
/* Public API: main-level collection
272-
/**********************************************************
272+
/**********************************************************************
273273
*/
274274

275275
/**
@@ -289,6 +289,9 @@ protected void collectAll()
289289
}
290290
_addInjectables(props);
291291

292+
// 27-Dec-2019, tatu: [databind#2527] initial re-linking by Field needs to
293+
// be applied before other processing
294+
292295
// Remove ignored properties, first; this MUST precede annotation merging
293296
// since logic relies on knowing exactly which accessor has which annotation
294297
_removeUnwantedProperties(props);
@@ -326,9 +329,9 @@ protected void collectAll()
326329
}
327330

328331
/*
329-
/**********************************************************
332+
/**********************************************************************
330333
/* Overridable internal methods, adding members
331-
/**********************************************************
334+
/**********************************************************************
332335
*/
333336

334337
/**
@@ -343,7 +346,7 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
343346
*/
344347
final boolean pruneFinalFields = !_forSerialization && !_config.isEnabled(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS);
345348
final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
346-
349+
347350
for (AnnotatedField f : _classDef.fields()) {
348351
// @JsonValue?
349352
if (Boolean.TRUE.equals(ai.hasAsValue(_config, f))) {
@@ -356,7 +359,7 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
356359
// @JsonAnySetter?
357360
if (Boolean.TRUE.equals(ai.hasAnySetter(_config, f))) {
358361
if (_anySetterField == null) {
359-
_anySetterField = new LinkedList<AnnotatedMember>();
362+
_anySetterField = new LinkedList<>();
360363
}
361364
_anySetterField.add(f);
362365
continue;
@@ -365,14 +368,22 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
365368
if (implName == null) {
366369
implName = f.getName();
367370
}
371+
372+
// [databind#2527: Field-based renaming can be applied early (here),
373+
// or at a later point, but probably must be done before pruning
374+
// final fields. So let's do it early here
375+
final PropertyName rename = ai.findRenameByField(_config, f, _propNameFromSimple(implName));
376+
if (rename != null) {
377+
// todo
378+
}
379+
368380
PropertyName pn;
369381

370382
if (_forSerialization) {
371-
/* 18-Aug-2011, tatu: As per existing unit tests, we should only
372-
* use serialization annotation (@JsonSerialize) when serializing
373-
* fields, and similarly for deserialize-only annotations... so
374-
* no fallbacks in this particular case.
375-
*/
383+
// 18-Aug-2011, tatu: As per existing unit tests, we should only
384+
// use serialization annotation (@JsonSerialize) when serializing
385+
// fields, and similarly for deserialize-only annotations... so
386+
// no fallbacks in this particular case.
376387
pn = ai.findNameForSerialization(_config, f);
377388
} else {
378389
pn = ai.findNameForDeserialization(_config, f);
@@ -405,7 +416,7 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
405416
}
406417
/* [databind#190]: this is the place to prune final fields, if they are not
407418
* to be used as mutators. Must verify they are not explicitly included.
408-
* Also: if 'ignored' is set, need to included until a later point, to
419+
* Also: if 'ignored' is set, need to include until a later point, to
409420
* avoid losing ignoral information.
410421
*/
411422
if (pruneFinalFields && (pn == null) && !ignored
@@ -427,15 +438,15 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
427438
}
428439
for (AnnotatedConstructor ctor : _classDef.getConstructors()) {
429440
if (_creatorProperties == null) {
430-
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
441+
_creatorProperties = new LinkedList<>();
431442
}
432443
for (int i = 0, len = ctor.getParameterCount(); i < len; ++i) {
433444
_addCreatorParam(props, ctor.getParameter(i));
434445
}
435446
}
436447
for (AnnotatedMethod factory : _classDef.getFactoryMethods()) {
437448
if (_creatorProperties == null) {
438-
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
449+
_creatorProperties = new LinkedList<>();
439450
}
440451
for (int i = 0, len = factory.getParameterCount(); i < len; ++i) {
441452
_addCreatorParam(props, factory.getParameter(i));
@@ -654,11 +665,11 @@ protected void _doAddInjectable(JacksonInject.Value injectable, AnnotatedMember
654665
private PropertyName _propNameFromSimple(String simpleName) {
655666
return PropertyName.construct(simpleName, null);
656667
}
657-
668+
658669
/*
659-
/**********************************************************
670+
/**********************************************************************
660671
/* Internal methods; removing ignored properties
661-
/**********************************************************
672+
/**********************************************************************
662673
*/
663674

664675
/**
@@ -729,9 +740,9 @@ private void _collectIgnorals(String name)
729740
}
730741

731742
/*
732-
/**********************************************************
743+
/**********************************************************************
733744
/* Internal methods; renaming properties
734-
/**********************************************************
745+
/**********************************************************************
735746
*/
736747

737748
protected void _renameProperties(Map<String, POJOPropertyBuilder> props)
@@ -893,9 +904,9 @@ protected void _renameWithWrappers(Map<String, POJOPropertyBuilder> props)
893904
}
894905

895906
/*
896-
/**********************************************************
907+
/**********************************************************************
897908
/* Overridable internal methods, sorting, other stuff
898-
/**********************************************************
909+
/**********************************************************************
899910
*/
900911

901912
// First, order by(explicit ordering and/or alphabetic),
@@ -1014,9 +1025,9 @@ private boolean _anyIndexed(Collection<POJOPropertyBuilder> props) {
10141025
}
10151026

10161027
/*
1017-
/**********************************************************
1028+
/**********************************************************************
10181029
/* Internal methods; helpers
1019-
/**********************************************************
1030+
/**********************************************************************
10201031
*/
10211032

10221033
protected void reportProblem(String msg, Object... args) {

src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ protected static boolean isGroovyMetaClassGetter(AnnotatedMember am) {
174174
// 24-Sep-2017, tatu: note that "std" here refers to earlier (1.x, 2.x) distinction
175175
// between "legacy" (slightly non-conforming) and "std" (fully conforming): with 3.x
176176
// only latter exists.
177-
protected static String stdManglePropertyName(final String basename, final int offset)
177+
public static String stdManglePropertyName(final String basename, final int offset)
178178
{
179179
final int end = basename.length();
180180
if (end == offset) { // empty name, nope
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.fasterxml.jackson.failing;
2+
3+
import java.util.Collections;
4+
import java.util.Map;
5+
6+
import com.fasterxml.jackson.databind.*;
7+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
8+
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
9+
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
10+
import com.fasterxml.jackson.databind.util.BeanUtil;
11+
12+
// [databind#2527] Support Kotlin-style "is" properties
13+
public class IsGetterRenaming2527Test extends BaseMapTest
14+
{
15+
static class POJO2527 {
16+
private boolean isEnabled;
17+
18+
protected POJO2527() { }
19+
public POJO2527(boolean b) {
20+
isEnabled = b;
21+
}
22+
23+
public boolean getEnabled() { return isEnabled; }
24+
public void setEnabled(boolean b) { isEnabled = b; }
25+
}
26+
27+
static class POJO2527b {
28+
public boolean isEnabled;
29+
30+
protected POJO2527b() { }
31+
public POJO2527b(boolean b) {
32+
isEnabled = b;
33+
}
34+
35+
public boolean getEnabled() { return isEnabled; }
36+
public void setEnabled(boolean b) { isEnabled = b; }
37+
}
38+
39+
@SuppressWarnings("serial")
40+
static class MyIntrospector extends JacksonAnnotationIntrospector
41+
{
42+
@Override
43+
public PropertyName findRenameByField(MapperConfig<?> config,
44+
AnnotatedField f, PropertyName implName)
45+
{
46+
final String origSimple = implName.getSimpleName();
47+
if (origSimple.startsWith("is")) {
48+
String mangledName = BeanUtil.stdManglePropertyName(origSimple, 2);
49+
// Needs to be valid ("is" -> null), and different from original
50+
if ((mangledName != null) && !mangledName.equals(origSimple)) {
51+
return PropertyName.construct(mangledName);
52+
}
53+
}
54+
return null;
55+
}
56+
}
57+
58+
private final ObjectMapper MAPPER = newJsonMapper();
59+
60+
public void testIsPropertiesStdKotlin() throws Exception
61+
{
62+
POJO2527 input = new POJO2527(true);
63+
final String json = MAPPER.writeValueAsString(input);
64+
65+
Map<?, ?> props = MAPPER.readValue(json, Map.class);
66+
assertEquals(Collections.singletonMap("isEnabled", Boolean.TRUE),
67+
props);
68+
69+
POJO2527 output = MAPPER.readValue(json, POJO2527.class);
70+
assertEquals(input.isEnabled, output.isEnabled);
71+
}
72+
73+
public void testIsPropertiesAlt() throws Exception
74+
{
75+
POJO2527b input = new POJO2527b(true);
76+
final String json = MAPPER.writeValueAsString(input);
77+
78+
Map<?, ?> props = MAPPER.readValue(json, Map.class);
79+
assertEquals(Collections.singletonMap("isEnabled", Boolean.TRUE),
80+
props);
81+
82+
POJO2527b output = MAPPER.readValue(json, POJO2527b.class);
83+
assertEquals(input.isEnabled, output.isEnabled);
84+
}
85+
}

0 commit comments

Comments
 (0)