Skip to content

Commit 47c04b3

Browse files
committed
Fixes #4584: add AnnotationIntrospector method for canonical Creator discovery
1 parent 475ebbf commit 47c04b3

File tree

4 files changed

+90
-9
lines changed

4 files changed

+90
-9
lines changed

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,6 +1397,38 @@ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated
13971397
return null;
13981398
}
13991399

1400+
/**
1401+
* Method called to check if introspector is able to detect so-called Canonical
1402+
* creator: primary Creator to use when no explicit annotation found
1403+
* (via {@link #findCreatorAnnotation}).
1404+
* This is the case for example for Java Record types (but for which handling
1405+
* is in-built); but is specifically true for various "Data" classes by frameworks like
1406+
* Lombok and JVM languages like Kotlin and Scala (case classes).
1407+
* If introspector can determine that one of given {@link PotentialCreator}s should
1408+
* be considered canonical, it should return it; if not, should return {@code null}.
1409+
*<p>
1410+
* NOTE: when returning chosen Creator, it may be necessary to mark its "mode"
1411+
* with {@link PotentialCreator#overrideMode} (especially for "delegating" creators).
1412+
*<p>
1413+
* NOTE: method is NOT called for Java Record types.
1414+
*
1415+
* @param config Configuration settings in effect (for deserialization)
1416+
* @param valueClass Class being instantiated and defines Creators passed
1417+
* @param declaredConstructors Constructors value class declares
1418+
* @param declaredFactories Factory methods value class declares
1419+
*
1420+
* @return The one Canonical Creator to use for {@code valueClass}, if it can be
1421+
* determined; {@code null} if not.
1422+
*
1423+
* @since 2.18
1424+
*/
1425+
public PotentialCreator findCanonicalCreator(MapperConfig<?> config,
1426+
AnnotatedClass valueClass,
1427+
List<PotentialCreator> declaredConstructors,
1428+
List<PotentialCreator> declaredFactories) {
1429+
return null;
1430+
}
1431+
14001432
/**
14011433
* Method for checking whether given annotated item (method, constructor)
14021434
* has an annotation

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,9 +735,23 @@ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated
735735
return (mode == null) ? _secondary.findCreatorAnnotation(config, a) : mode;
736736
}
737737

738+
@Override
739+
public PotentialCreator findCanonicalCreator(MapperConfig<?> config,
740+
AnnotatedClass valueClass,
741+
List<PotentialCreator> declaredConstructors,
742+
List<PotentialCreator> declaredFactories) {
743+
PotentialCreator canonical = _primary.findCanonicalCreator(config,
744+
valueClass, declaredConstructors, declaredFactories);
745+
if (canonical == null) {
746+
canonical = _secondary.findCanonicalCreator(config,
747+
valueClass, declaredConstructors, declaredFactories);
748+
}
749+
return canonical;
750+
}
751+
738752
/*
739753
/**********************************************************************
740-
/* Deserialization: other method annotations
754+
/* Deserialization: other property annotations
741755
/**********************************************************************
742756
*/
743757

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -655,15 +655,15 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
655655
if (_isRecordType) {
656656
canonical = JDK14Util.findCanonicalRecordConstructor(_config, _classDef, constructors);
657657
} else {
658-
// !!! TODO: fetch Canonical for Kotlin, Scala, via AnnotationIntrospector?
659-
canonical = null;
658+
canonical = _annotationIntrospector.findCanonicalCreator(_config, _classDef,
659+
constructors, factories);
660660
}
661661

662662
// Next: remove creators marked as explicitly disabled
663663
_removeDisabledCreators(constructors);
664664
_removeDisabledCreators(factories);
665665
// And then remove non-annotated static methods that do not look like factories
666-
_removeNonFactoryStaticMethods(factories);
666+
_removeNonFactoryStaticMethods(factories, canonical);
667667

668668
// and use annotations to find explicitly chosen Creators
669669
if (_useAnnotations) { // can't have explicit ones without Annotation introspection
@@ -682,12 +682,12 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
682682
}
683683

684684
// But if no annotation-based Creators found, find/use canonical Creator
685-
// (JDK 17 Record/Scala/Kotlin)
685+
// (Scala/Kotlin/Lombok?)
686686
if (!creators.hasPropertiesBased()) {
687-
// for Records:
688687
if (canonical != null) {
689688
// ... but only process if still included as a candidate
690-
if (constructors.remove(canonical)) {
689+
if (constructors.remove(canonical)
690+
|| factories.remove(canonical)) {
691691
// But wait! Could be delegating
692692
if (_isDelegatingConstructor(canonical)) {
693693
creators.addExplicitDelegating(canonical);
@@ -733,6 +733,16 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
733733
// looks like delegating one
734734
private boolean _isDelegatingConstructor(PotentialCreator ctor)
735735
{
736+
// First things first: could be
737+
switch (ctor.creatorMode()) {
738+
case DELEGATING:
739+
return true;
740+
case DISABLED:
741+
case PROPERTIES:
742+
return false;
743+
default:
744+
}
745+
736746
// Only consider single-arg case, for now
737747
if (ctor.paramCount() == 1) {
738748
// Main thing: @JsonValue makes it delegating:
@@ -779,7 +789,8 @@ private void _removeNonVisibleCreators(List<PotentialCreator> ctors)
779789
}
780790
}
781791

782-
private void _removeNonFactoryStaticMethods(List<PotentialCreator> ctors)
792+
private void _removeNonFactoryStaticMethods(List<PotentialCreator> ctors,
793+
PotentialCreator canonical)
783794
{
784795
final Class<?> rawType = _type.getRawClass();
785796
Iterator<PotentialCreator> it = ctors.iterator();
@@ -789,6 +800,10 @@ private void _removeNonFactoryStaticMethods(List<PotentialCreator> ctors)
789800
if (ctor.creatorMode() != null) {
790801
continue;
791802
}
803+
// Do not trim canonical either
804+
if (canonical == ctor) {
805+
continue;
806+
}
792807
// Copied from `BasicBeanDescription.isFactoryMethod()`
793808
AnnotatedWithParams factory = ctor.creator();
794809
if (rawType.isAssignableFrom(factory.getRawType())

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class PotentialCreator
1919

2020
private final AnnotatedWithParams creator;
2121

22-
private final JsonCreator.Mode creatorMode;
22+
private JsonCreator.Mode creatorMode;
2323

2424
private PropertyName[] implicitParamNames;
2525

@@ -35,9 +35,29 @@ public PotentialCreator(AnnotatedWithParams cr,
3535
JsonCreator.Mode cm)
3636
{
3737
creator = cr;
38+
if (cm == null) {
39+
cm = JsonCreator.Mode.DEFAULT;
40+
}
3841
creatorMode = cm;
3942
}
4043

44+
/**
45+
* Method that can be called to change the {@code creatorMode} this
46+
* Creator has: typically used to "mark" Creator as {@code JsonCreator.Mode.DELEGATING}
47+
* or {@code JsonCreator.Mode.PROPERTIES} when further information is gathered).
48+
*
49+
* @param mode Mode to set {@code creatorMode} to
50+
*
51+
* @return This creator instance
52+
*/
53+
public PotentialCreator overrideMode(JsonCreator.Mode mode) {
54+
if (mode == null) {
55+
mode = JsonCreator.Mode.DEFAULT;
56+
}
57+
creatorMode = mode;
58+
return this;
59+
}
60+
4161
/*
4262
/**********************************************************************
4363
/* Mutators

0 commit comments

Comments
 (0)