@@ -662,6 +662,8 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
662
662
// Next: remove creators marked as explicitly disabled
663
663
_removeDisabledCreators (constructors );
664
664
_removeDisabledCreators (factories );
665
+ // And then remove non-annotated static methods that do not look like factories
666
+ _removeNonFactoryStaticMethods (factories );
665
667
666
668
// and use annotations to find explicitly chosen Creators
667
669
if (_useAnnotations ) { // can't have explicit ones without Annotation introspection
@@ -683,9 +685,30 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
683
685
// (JDK 17 Record/Scala/Kotlin)
684
686
if (!creators .hasPropertiesBased ()) {
685
687
// for Records:
686
- if ((canonical != null ) && constructors .contains (canonical )) {
687
- constructors .remove (canonical );
688
- creators .setPropertiesBased (_config , canonical , "canonical" );
688
+ if (canonical != null ) {
689
+ // ... but only process if still included as a candidate
690
+ if (constructors .remove (canonical )) {
691
+ // But wait! Could be delegating
692
+ if (_isDelegatingConstructor (canonical )) {
693
+ creators .addExplicitDelegating (canonical );
694
+ } else {
695
+ creators .setPropertiesBased (_config , canonical , "canonical" );
696
+ }
697
+ }
698
+ }
699
+ }
700
+
701
+ // One more thing: if neither explicit (constructor or factory) nor
702
+ // canonical (constructor?), consider implicit Constructor with
703
+ // all named.
704
+ final ConstructorDetector ctorDetector = _config .getConstructorDetector ();
705
+ if (!creators .hasPropertiesBasedOrDelegating ()
706
+ && !ctorDetector .requireCtorAnnotation ()) {
707
+ // But only if no default constructor available OR if we are configured
708
+ // to prefer properties-based Creators
709
+ if ((_classDef .getDefaultConstructor () == null )
710
+ || ctorDetector .singleArgCreatorDefaultsToProperties ()) {
711
+ _addImplicitConstructor (creators , constructors , props );
689
712
}
690
713
}
691
714
@@ -703,6 +726,20 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
703
726
}
704
727
}
705
728
729
+ // Method to determine if given non-explictly-annotated constructor
730
+ // looks like delegating one
731
+ private boolean _isDelegatingConstructor (PotentialCreator ctor )
732
+ {
733
+ // Only consider single-arg case, for now
734
+ if (ctor .paramCount () == 1 ) {
735
+ // Main thing: @JsonValue makes it delegating:
736
+ if ((_jsonValueAccessors != null ) && !_jsonValueAccessors .isEmpty ()) {
737
+ return true ;
738
+ }
739
+ }
740
+ return false ;
741
+ }
742
+
706
743
private List <PotentialCreator > _collectCreators (List <? extends AnnotatedWithParams > ctors )
707
744
{
708
745
if (ctors .isEmpty ()) {
@@ -728,6 +765,35 @@ private void _removeDisabledCreators(List<PotentialCreator> ctors)
728
765
}
729
766
}
730
767
768
+ private void _removeNonFactoryStaticMethods (List <PotentialCreator > ctors )
769
+ {
770
+ final Class <?> rawType = _type .getRawClass ();
771
+ Iterator <PotentialCreator > it = ctors .iterator ();
772
+ while (it .hasNext ()) {
773
+ // explicit mode? Retain (for now)
774
+ PotentialCreator ctor = it .next ();
775
+ if (ctor .creatorMode () != null ) {
776
+ continue ;
777
+ }
778
+ // Copied from `BasicBeanDescription.isFactoryMethod()`
779
+ AnnotatedWithParams factory = ctor .creator ();
780
+ if (rawType .isAssignableFrom (factory .getRawType ())
781
+ && ctor .paramCount () == 1 ) {
782
+ String name = factory .getName ();
783
+
784
+ if ("valueOf" .equals (name )) {
785
+ continue ;
786
+ } else if ("fromString" .equals (name )) {
787
+ Class <?> cls = factory .getRawParameterType (0 );
788
+ if (cls == String .class || CharSequence .class .isAssignableFrom (cls )) {
789
+ continue ;
790
+ }
791
+ }
792
+ }
793
+ it .remove ();
794
+ }
795
+ }
796
+
731
797
private void _addExplicitlyAnnotatedCreators (PotentialCreators collector ,
732
798
List <PotentialCreator > ctors ,
733
799
Map <String , POJOPropertyBuilder > props ,
@@ -743,48 +809,25 @@ private void _addExplicitlyAnnotatedCreators(PotentialCreators collector,
743
809
if (ctor .creatorMode () == null ) {
744
810
continue ;
745
811
}
812
+
746
813
it .remove ();
747
814
748
- Boolean propsBased = null ;
749
-
815
+ boolean isPropsBased ;
816
+
750
817
switch (ctor .creatorMode ()) {
751
818
case DELEGATING :
752
- propsBased = false ;
819
+ isPropsBased = false ;
753
820
break ;
754
821
case PROPERTIES :
755
- propsBased = true ;
822
+ isPropsBased = true ;
756
823
break ;
757
824
case DEFAULT :
758
825
default :
759
- // First things first: if not single-arg Creator, must be Properties-based
760
- // !!! Or does it? What if there's @JacksonInject etc?
761
- if (ctor .paramCount () != 1 ) {
762
- propsBased = true ;
763
- }
764
- }
765
-
766
- // Must be 1-arg case:
767
- if (propsBased == null ) {
768
- // Is ambiguity/heuristics allowed?
769
- if (ctorDetector .requireCtorAnnotation ()) {
770
- throw new IllegalArgumentException (String .format (
771
- "Ambiguous 1-argument Creator; `ConstructorDetector` requires specifying `mode`: %s" ,
772
- ctor ));
773
- }
774
-
775
- // First things first: if explicit names found, is Properties-based
776
- ctor .introspectParamNames (_config );
777
- propsBased = ctor .hasExplicitNames ()
778
- || ctorDetector .singleArgCreatorDefaultsToProperties ();
779
- // One more possibility: implicit name that maps to implied
780
- // property based on Field/Getter/Setter
781
- if (!propsBased ) {
782
- String implName = ctor .implicitNameSimple (0 );
783
- propsBased = (implName != null ) && props .containsKey (implName );
784
- }
826
+ isPropsBased = _isExplicitlyAnnotatedCreatorPropsBased (ctor ,
827
+ props , ctorDetector );
785
828
}
786
829
787
- if (propsBased ) {
830
+ if (isPropsBased ) {
788
831
// Skipping done if we already got higher-precedence Creator
789
832
if (!skipPropsBased ) {
790
833
collector .setPropertiesBased (_config , ctor , "explicit" );
@@ -795,6 +838,53 @@ private void _addExplicitlyAnnotatedCreators(PotentialCreators collector,
795
838
}
796
839
}
797
840
841
+ private boolean _isExplicitlyAnnotatedCreatorPropsBased (PotentialCreator ctor ,
842
+ Map <String , POJOPropertyBuilder > props , ConstructorDetector ctorDetector )
843
+ {
844
+ if (ctor .paramCount () == 1 ) {
845
+ // Is ambiguity/heuristics allowed?
846
+ switch (ctorDetector .singleArgMode ()) {
847
+ case DELEGATING :
848
+ return false ;
849
+ case PROPERTIES :
850
+ return true ;
851
+ case REQUIRE_MODE :
852
+ throw new IllegalArgumentException (String .format (
853
+ "Single-argument constructor (%s) is annotated but no 'mode' defined; `ConstructorDetector`"
854
+ + "configured with `SingleArgConstructor.REQUIRE_MODE`" ,
855
+ ctor .creator ()));
856
+ case HEURISTIC :
857
+ default :
858
+ }
859
+ }
860
+
861
+ // First: if explicit names found, is Properties-based
862
+ ctor .introspectParamNames (_config );
863
+ if (ctor .hasExplicitNames ()) {
864
+ return true ;
865
+ }
866
+ // Second: [databind#3180] @JsonValue indicates delegating
867
+ if ((_jsonValueAccessors != null ) && !_jsonValueAccessors .isEmpty ()) {
868
+ return false ;
869
+ }
870
+ if (ctor .paramCount () == 1 ) {
871
+ // One more possibility: implicit name that maps to implied
872
+ // property based on Field/Getter/Setter
873
+ String implName = ctor .implicitNameSimple (0 );
874
+ if ((implName != null ) && props .containsKey (implName )) {
875
+ return true ;
876
+ }
877
+ // Second: injectable also suffices
878
+ if ((_annotationIntrospector != null )
879
+ && _annotationIntrospector .findInjectableValue (ctor .param (0 )) != null ) {
880
+ return true ;
881
+ }
882
+ return false ;
883
+ }
884
+ // Trickiest case: rely on existence of implicit names and/or injectables
885
+ return ctor .hasNameOrInjectForAllParams (_config );
886
+ }
887
+
798
888
private void _addCreatorsWithAnnotatedNames (PotentialCreators collector ,
799
889
List <PotentialCreator > ctors )
800
890
{
@@ -813,6 +903,50 @@ private void _addCreatorsWithAnnotatedNames(PotentialCreators collector,
813
903
}
814
904
}
815
905
906
+ private boolean _addImplicitConstructor (PotentialCreators collector ,
907
+ List <PotentialCreator > ctors , Map <String , POJOPropertyBuilder > props )
908
+ {
909
+ // Must have one and only one candidate
910
+ if (ctors .size () != 1 ) {
911
+ return false ;
912
+ }
913
+ final PotentialCreator ctor = ctors .get (0 );
914
+ // which needs to be visible
915
+ if (!_visibilityChecker .isCreatorVisible (ctor .creator ())) {
916
+ return false ;
917
+ }
918
+ ctor .introspectParamNames (_config );
919
+
920
+ // As usual, 1-param case is distinct
921
+ if (ctor .paramCount () != 1 ) {
922
+ if (!ctor .hasNameOrInjectForAllParams (_config )) {
923
+ return false ;
924
+ }
925
+ } else {
926
+ // First things first: if only param has Injectable, must be Props-based
927
+ if ((_annotationIntrospector != null )
928
+ && _annotationIntrospector .findInjectableValue (ctor .param (0 )) != null ) {
929
+ // props-based, continue
930
+ } else {
931
+ // may have explicit preference
932
+ final ConstructorDetector ctorDetector = _config .getConstructorDetector ();
933
+ if (ctorDetector .singleArgCreatorDefaultsToDelegating ()) {
934
+ return false ;
935
+ }
936
+ // if not, prefer Properties-based if explicit preference OR
937
+ // property with same name
938
+ if (!ctorDetector .singleArgCreatorDefaultsToProperties ()
939
+ && !props .containsKey (ctor .implicitNameSimple (0 ))) {
940
+ return false ;
941
+ }
942
+ }
943
+ }
944
+
945
+ ctors .remove (0 );
946
+ collector .setPropertiesBased (_config , ctor , "implicit" );
947
+ return true ;
948
+ }
949
+
816
950
private void _addCreatorParams (Map <String , POJOPropertyBuilder > props ,
817
951
PotentialCreator ctor , List <POJOPropertyBuilder > creatorProps )
818
952
{
0 commit comments