Skip to content

Commit e56bc0b

Browse files
authored
Refactor Bean property introspection in POJOPropertiesCollector (#4532)
First part of refactoring towards #4515.
1 parent e73a605 commit e56bc0b

File tree

9 files changed

+505
-32
lines changed

9 files changed

+505
-32
lines changed

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

+216-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import com.fasterxml.jackson.annotation.JsonFormat;
1010
import com.fasterxml.jackson.databind.*;
11+
import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
1112
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
1213
import com.fasterxml.jackson.databind.cfg.MapperConfig;
1314
import com.fasterxml.jackson.databind.jdk14.JDK14Util;
@@ -88,7 +89,7 @@ public class POJOPropertiesCollector
8889
*/
8990
protected LinkedHashMap<String, POJOPropertyBuilder> _properties;
9091

91-
protected LinkedList<POJOPropertyBuilder> _creatorProperties;
92+
protected List<POJOPropertyBuilder> _creatorProperties;
9293

9394
/**
9495
* A set of "field renamings" that have been discovered, indicating
@@ -614,10 +615,216 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
614615
}
615616
}
616617

618+
// Completely rewritten in 2.18
619+
protected void _addCreators(Map<String, POJOPropertyBuilder> props)
620+
{
621+
_creatorProperties = new ArrayList<>();
622+
623+
// First, resolve explicit annotations for all potential Creators
624+
// (but do NOT filter out DISABLED ones yet!)
625+
List<PotentialCreator> ctors = _collectCreators(_classDef.getConstructors());
626+
List<PotentialCreator> factories = _collectCreators(_classDef.getFactoryMethods());
627+
628+
final PotentialCreator canonical;
629+
630+
// Find and mark "canonical" constructor for Records.
631+
// Needs to be done early to get implicit names populated
632+
if (_isRecordType) {
633+
canonical = JDK14Util.findCanonicalRecordConstructor(_config, _classDef, ctors);
634+
} else {
635+
// !!! TODO: fetch Canonical for Kotlin, Scala, via AnnotationIntrospector?
636+
canonical = null;
637+
}
638+
639+
// Next: remove creators marked as explicitly disabled
640+
_removeDisabledCreators(ctors);
641+
_removeDisabledCreators(factories);
642+
643+
final PotentialCreators collector = new PotentialCreators(ctors, factories);
644+
// and use annotations to find explicitly chosen Creators
645+
if (_useAnnotations) { // can't have explicit ones without Annotation introspection
646+
// Start with Constructors as they have higher precedence:
647+
_addExplicitlyAnnotatedCreators(collector, collector.constructors, props, false);
648+
// followed by Factory methods (lower precedence)
649+
_addExplicitlyAnnotatedCreators(collector, collector.factories, props,
650+
collector.hasPropertiesBased());
651+
}
652+
653+
// If no Explicitly annotated creators found, look
654+
// for ones with explicitly-named ({@code @JsonProperty}) parameters
655+
if (!collector.hasPropertiesBased()) {
656+
// only discover constructor Creators?
657+
_addCreatorsWithAnnotatedNames(collector, collector.constructors);
658+
}
659+
660+
// But if no annotation-based Creators found, find/use canonical Creator
661+
// (JDK 17 Record/Scala/Kotlin)
662+
if (!collector.hasPropertiesBased()) {
663+
// for Records:
664+
if ((canonical != null) && ctors.contains(canonical)) {
665+
ctors.remove(canonical);
666+
collector.addPropertiesBased(_config, canonical, "canonical");
667+
}
668+
}
669+
670+
// And finally add logical properties:
671+
PotentialCreator primary = collector.propertiesBased;
672+
if (primary != null) {
673+
_addCreatorParams(props, primary);
674+
}
675+
}
676+
677+
private List<PotentialCreator> _collectCreators(List<? extends AnnotatedWithParams> ctors)
678+
{
679+
if (ctors.isEmpty()) {
680+
return Collections.emptyList();
681+
}
682+
List<PotentialCreator> result = new ArrayList<>();
683+
for (AnnotatedWithParams ctor : ctors) {
684+
JsonCreator.Mode creatorMode = _useAnnotations
685+
? _annotationIntrospector.findCreatorAnnotation(_config, ctor) : null;
686+
result.add(new PotentialCreator(ctor, creatorMode));
687+
}
688+
return (result == null) ? Collections.emptyList() : result;
689+
}
690+
691+
private void _removeDisabledCreators(List<PotentialCreator> ctors)
692+
{
693+
Iterator<PotentialCreator> it = ctors.iterator();
694+
while (it.hasNext()) {
695+
// explicitly prevented? Remove
696+
if (it.next().creatorMode == JsonCreator.Mode.DISABLED) {
697+
it.remove();
698+
}
699+
}
700+
}
701+
702+
private void _addExplicitlyAnnotatedCreators(PotentialCreators collector,
703+
List<PotentialCreator> ctors,
704+
Map<String, POJOPropertyBuilder> props,
705+
boolean skipPropsBased)
706+
{
707+
final ConstructorDetector ctorDetector = _config.getConstructorDetector();
708+
Iterator<PotentialCreator> it = ctors.iterator();
709+
while (it.hasNext()) {
710+
PotentialCreator ctor = it.next();
711+
712+
// If no explicit annotation, skip for now (may be discovered
713+
// at a later point)
714+
if (ctor.creatorMode == null) {
715+
continue;
716+
}
717+
it.remove();
718+
719+
Boolean propsBased = null;
720+
721+
switch (ctor.creatorMode) {
722+
case DELEGATING:
723+
propsBased = false;
724+
break;
725+
case PROPERTIES:
726+
propsBased = true;
727+
break;
728+
case DEFAULT:
729+
default:
730+
// First things first: if not single-arg Creator, must be Properties-based
731+
// !!! Or does it? What if there's @JacksonInject etc?
732+
if (ctor.paramCount() != 1) {
733+
propsBased = true;
734+
}
735+
}
736+
737+
// Must be 1-arg case:
738+
if (propsBased == null) {
739+
// Is ambiguity/heuristics allowed?
740+
if (ctorDetector.requireCtorAnnotation()) {
741+
throw new IllegalArgumentException(String.format(
742+
"Ambiguous 1-argument Creator; `ConstructorDetector` requires specifying `mode`: %s",
743+
ctor));
744+
}
745+
746+
// First things first: if explicit names found, is Properties-based
747+
ctor.introspectParamNames(_config);
748+
propsBased = ctor.hasExplicitNames()
749+
|| ctorDetector.singleArgCreatorDefaultsToProperties();
750+
// One more possibility: implicit name that maps to implied
751+
// property based on Field/Getter/Setter
752+
if (!propsBased) {
753+
String implName = ctor.implicitNameSimple(0);
754+
propsBased = (implName != null) && props.containsKey(implName);
755+
}
756+
}
757+
758+
if (propsBased) {
759+
// Skipping done if we already got higher-precedence Creator
760+
if (!skipPropsBased) {
761+
collector.addPropertiesBased(_config, ctor, "explicit");
762+
}
763+
} else {
764+
collector.addDelegating(ctor);
765+
}
766+
}
767+
}
768+
769+
private void _addCreatorsWithAnnotatedNames(PotentialCreators collector,
770+
List<PotentialCreator> ctors)
771+
{
772+
Iterator<PotentialCreator> it = ctors.iterator();
773+
while (it.hasNext()) {
774+
PotentialCreator ctor = it.next();
775+
776+
// Ok: existence of explicit (annotated) names infers properties-based:
777+
ctor.introspectParamNames(_config);
778+
if (!ctor.hasExplicitNames()) {
779+
continue;
780+
}
781+
it.remove();
782+
783+
collector.addPropertiesBased(_config, ctor, "implicit");
784+
}
785+
}
786+
787+
private void _addCreatorParams(Map<String, POJOPropertyBuilder> props,
788+
PotentialCreator ctor)
789+
{
790+
for (int i = 0, len = ctor.paramCount(); i < len; ++i) {
791+
final AnnotatedParameter param = ctor.param(i);
792+
final PropertyName explName = ctor.explicitName(i);
793+
PropertyName implName = ctor.implicitName(i);
794+
final boolean hasExplicit = (explName != null);
795+
796+
if (!hasExplicit) {
797+
if (implName == null) {
798+
// Important: if neither implicit nor explicit name, cannot make use of
799+
// this creator parameter -- may or may not be a problem, verified at a later point.
800+
continue;
801+
}
802+
}
803+
804+
// 27-Dec-2019, tatu: [databind#2527] may need to rename according to field
805+
if (implName != null) {
806+
String n = _checkRenameByField(implName.getSimpleName());
807+
implName = PropertyName.construct(n);
808+
}
809+
810+
POJOPropertyBuilder prop = (implName == null)
811+
? _property(props, explName) : _property(props, implName);
812+
prop.addCtor(param, hasExplicit ? explName : implName, hasExplicit, true, false);
813+
_creatorProperties.add(prop);
814+
}
815+
}
816+
817+
/*
818+
/**********************************************************************
819+
/* Deprecated (in 2.18) creator detection
820+
/**********************************************************************
821+
*/
822+
617823
/**
618824
* Method for collecting basic information on constructor(s) found
619825
*/
620-
protected void _addCreators(Map<String, POJOPropertyBuilder> props)
826+
@Deprecated
827+
protected void _addCreatorsOLD(Map<String, POJOPropertyBuilder> props)
621828
{
622829
// can be null if annotation processing is disabled...
623830
if (_useAnnotations) {
@@ -734,8 +941,14 @@ private void _addCreatorParam(Map<String, POJOPropertyBuilder> props,
734941
_creatorProperties.add(prop);
735942
}
736943

944+
/*
945+
/**********************************************************************
946+
/* Method (getter, setter etc) introspection
947+
/**********************************************************************
948+
*/
949+
737950
/**
738-
* Method for collecting basic information on all fields found
951+
* Method for collecting basic information on all accessor methods found
739952
*/
740953
protected void _addMethods(Map<String, POJOPropertyBuilder> props)
741954
{

0 commit comments

Comments
 (0)