diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 4d608245e7..2a26d24822 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -24,6 +24,7 @@ Project: jackson-databind (contributed by @pjfanning) #4483: Remove `final` on method BeanSerializer.serialize() (contributed by Matthew L) +#4515: Rewrite Bean Property Introspection logic in Jackson 2.x 2.17.1 (04-May-2024) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index 98f74c7828..95c7c73f18 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -249,39 +249,27 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo { final MapperConfig config = ctxt.getConfig(); final PotentialCreators potentialCreators = beanDesc.getPotentialCreators(); - final CreatorCollectionState ccState; - final ConstructorDetector ctorDetector; + final ConstructorDetector ctorDetector = config.getConstructorDetector(); + // need to construct suitable visibility checker: + final VisibilityChecker vchecker = config.getDefaultVisibilityChecker(beanDesc.getBeanClass(), + beanDesc.getClassInfo()); - { - // need to construct suitable visibility checker: - final VisibilityChecker vchecker = config.getDefaultVisibilityChecker(beanDesc.getBeanClass(), - beanDesc.getClassInfo()); - ctorDetector = config.getConstructorDetector(); - - // 24-Sep-2014, tatu: Tricky part first; need to merge resolved property information - // (which has creator parameters sprinkled around) with actual creator - // declarations (which are needed to access creator annotation, amongst other things). - // Easiest to combine that info first, then pass it to remaining processing. - - // 15-Mar-2015, tatu: Alas, this won't help with constructors that only have implicit - // names. Those will need to be resolved later on. - final CreatorCollector creators = new CreatorCollector(beanDesc, config); - - // 21-May-2024, tatu: [databind#4515] Rewritten to use PotentialCreators - Map creatorDefs; - if (potentialCreators.hasPropertiesBased()) { - PotentialCreator primaryPropsBased = potentialCreators.propertiesBased; - creatorDefs = Collections.singletonMap(primaryPropsBased.creator(), - primaryPropsBased.propertyDefs()); - } else { - creatorDefs = Collections.emptyMap(); - } - ccState = new CreatorCollectionState(config, beanDesc, vchecker, - creators, creatorDefs); + final CreatorCollector creators = new CreatorCollector(beanDesc, config); + + // 21-May-2024, tatu: [databind#4515] Rewritten to use PotentialCreators + if (potentialCreators.hasPropertiesBased()) { + PotentialCreator primaryPropsBased = potentialCreators.propertiesBased; + + // Start by assigning the primary (and only) properties-based creator + _addExplicitPropertyCreator(ctxt, beanDesc, creators, + CreatorCandidate.construct(config.getAnnotationIntrospector(), + primaryPropsBased.creator(), primaryPropsBased.propertyDefs())); } - // Start with explicitly annotated factory methods - _addExplicitFactoryCreators(ctxt, ccState, !ctorDetector.requireCtorAnnotation()); + // Continue with explicitly annotated delegating Creators + boolean hasExplicitDelegating = _addExplicitDelegatingCreators(ctxt, + beanDesc, creators, + potentialCreators.getExplicitDelegating()); // constructors only usable on concrete types: if (beanDesc.getType().isConcrete()) { @@ -297,32 +285,28 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo // in list of constructors, so needs to be handled separately. AnnotatedConstructor defaultCtor = beanDesc.findDefaultConstructor(); if (defaultCtor != null) { - if (!ccState.creators.hasDefaultCreator() || _hasCreatorAnnotation(config, defaultCtor)) { - ccState.creators.setDefaultCreator(defaultCtor); + if (!creators.hasDefaultCreator() || _hasCreatorAnnotation(config, defaultCtor)) { + creators.setDefaultCreator(defaultCtor); } } // 18-Sep-2020, tatu: Although by default implicit introspection is allowed, 2.12 // has settings to prevent that either generally, or at least for JDK types final boolean findImplicit = ctorDetector.shouldIntrospectorImplicitConstructors(beanDesc.getBeanClass()); - _addExplicitConstructorCreators(ctxt, ccState, findImplicit); - if (ccState.hasImplicitConstructorCandidates() - // 05-Dec-2020, tatu: [databind#2962] explicit annotation of - // a factory should not block implicit constructor, for backwards - // compatibility (minor regression in 2.12.0) - //&& !ccState.hasExplicitFactories() - // ... explicit constructor should prevent, however - && !ccState.hasExplicitConstructors()) { - _addImplicitConstructorCreators(ctxt, ccState, ccState.implicitConstructorCandidates()); + if (findImplicit) { + _addImplicitDelegatingConstructors(ctxt, beanDesc, vchecker, + creators, + potentialCreators.getImplicitDelegatingConstructors()); } } } - // and finally, implicitly found factory methods if nothing explicit found - if (ccState.hasImplicitFactoryCandidates() - && !ccState.hasExplicitFactories() && !ccState.hasExplicitConstructors()) { - _addImplicitFactoryCreators(ctxt, ccState, ccState.implicitFactoryCandidates()); + if (!hasExplicitDelegating) { + _addImplicitDelegatingFactories(ctxt, vchecker, + creators, + potentialCreators.getImplicitDelegatingFactories()); } - return ccState.creators.constructValueInstantiator(ctxt); + + return creators.constructValueInstantiator(ctxt); } public ValueInstantiator _valueInstantiatorInstance(DeserializationConfig config, @@ -368,107 +352,38 @@ public ValueInstantiator _valueInstantiatorInstance(DeserializationConfig config /********************************************************************** */ - /* - /********************************************************************** - /* OLD Creator introspection: constructor creator introspection - /********************************************************************** - */ - - protected void _addExplicitConstructorCreators(DeserializationContext ctxt, - CreatorCollectionState ccState, boolean findImplicit) + private boolean _addExplicitDelegatingCreators(DeserializationContext ctxt, + BeanDescription beanDesc, + CreatorCollector creators, + List potentials) throws JsonMappingException { - final MapperConfig config = ccState.config; - final BeanDescription beanDesc = ccState.beanDesc; - final CreatorCollector creators = ccState.creators; - final AnnotationIntrospector intr = ccState.annotationIntrospector(); - final VisibilityChecker vchecker = ccState.vchecker; - final Map creatorParams = ccState.creatorParams; - - // 21-Sep-2017, tatu: First let's handle explicitly annotated ones - for (AnnotatedConstructor ctor : beanDesc.getConstructors()) { - JsonCreator.Mode creatorMode = intr.findCreatorAnnotation(config, ctor); - if (JsonCreator.Mode.DISABLED == creatorMode) { - continue; - } - if (creatorMode == null) { - // let's check Visibility here, to avoid further processing for non-visible? - if (findImplicit && vchecker.isCreatorVisible(ctor)) { - ccState.addImplicitConstructorCandidate(CreatorCandidate.construct(intr, - ctor, creatorParams.get(ctor))); - } - continue; - } + final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); + boolean added = false; - switch (creatorMode) { - case DELEGATING: - _addExplicitDelegatingCreator(ctxt, beanDesc, creators, - CreatorCandidate.construct(intr, ctor, null)); - break; - case PROPERTIES: - _addExplicitPropertyCreator(ctxt, beanDesc, creators, - CreatorCandidate.construct(intr, ctor, creatorParams.get(ctor))); - break; - default: - _addExplicitAnyCreator(ctxt, beanDesc, creators, - CreatorCandidate.construct(intr, ctor, creatorParams.get(ctor)), - ctxt.getConfig().getConstructorDetector()); - break; - } - ccState.increaseExplicitConstructorCount(); + for (PotentialCreator ctor : potentials) { + added |= _addExplicitDelegatingCreator(ctxt, beanDesc, creators, + CreatorCandidate.construct(intr, ctor.creator(), null)); } + return added; } - protected void _addImplicitConstructorCreators(DeserializationContext ctxt, - CreatorCollectionState ccState, List ctorCandidates) + private void _addImplicitDelegatingConstructors(DeserializationContext ctxt, + BeanDescription beanDesc, VisibilityChecker vchecker, + CreatorCollector creators, + List potentials) throws JsonMappingException { - final MapperConfig config = ccState.config; - final BeanDescription beanDesc = ccState.beanDesc; - final CreatorCollector creators = ccState.creators; - final AnnotationIntrospector intr = ccState.annotationIntrospector(); - final VisibilityChecker vchecker = ccState.vchecker; - List implicitCtors = null; - final boolean preferPropsBased = config.getConstructorDetector().singleArgCreatorDefaultsToProperties() - // [databind#3968]: Only Record's canonical constructor is allowed - // to be considered for properties-based creator to avoid failure - && !beanDesc.isRecordType(); - - for (CreatorCandidate candidate : ctorCandidates) { + final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); + + for (PotentialCreator candidate : potentials) { final int argCount = candidate.paramCount(); final AnnotatedWithParams ctor = candidate.creator(); // some single-arg factory methods (String, number) are auto-detected if (argCount == 1) { - final BeanPropertyDefinition propDef = candidate.propertyDef(0); - final boolean useProps = preferPropsBased - || _checkIfCreatorPropertyBased(beanDesc, intr, ctor, propDef); - - if (useProps) { - SettableBeanProperty[] properties = new SettableBeanProperty[1]; - final JacksonInject.Value injection = candidate.injection(0); - - // 18-Sep-2020, tatu: [databind#1498] looks like implicit name not linked - // unless annotation found, so try again from here - PropertyName name = candidate.paramName(0); - if (name == null) { - name = candidate.findImplicitParamName(0); - if ((name == null) && (injection == null)) { - continue; - } - } - properties[0] = constructCreatorProperty(ctxt, beanDesc, name, 0, - candidate.parameter(0), injection); - creators.addPropertyCreator(ctor, false, properties); - } else { - /*boolean added = */ _handleSingleArgumentCreator(creators, - ctor, false, - vchecker.isCreatorVisible(ctor)); - // one more thing: sever link to creator property, to avoid possible later - // problems with "unresolved" constructor property - if (propDef != null) { - ((POJOPropertyBuilder) propDef).removeConstructors(); - } - } + /*boolean added = */ _handleSingleArgumentCreator(creators, + ctor, false, + vchecker.isCreatorVisible(ctor)); // regardless, fully handled continue; } @@ -477,28 +392,16 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt, // 14-Mar-2015, tatu (2.6): Or, as per [#725], implicit names will also // do, with some constraints. But that will require bit post processing... - int nonAnnotatedParamIndex = -1; SettableBeanProperty[] properties = new SettableBeanProperty[argCount]; - int explicitNameCount = 0; - int implicitWithCreatorCount = 0; int injectCount = 0; for (int i = 0; i < argCount; ++i) { final AnnotatedParameter param = ctor.getParameter(i); - BeanPropertyDefinition propDef = candidate.propertyDef(i); JacksonInject.Value injectable = intr.findInjectableValue(param); - final PropertyName name = (propDef == null) ? null : propDef.getFullName(); - if ((propDef != null) - // [databind#3724]: Record canonical constructor will have implicitly named propDef - && (propDef.isExplicitlyNamed() || beanDesc.isRecordType())) { - ++explicitNameCount; - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable); - continue; - } if (injectable != null) { ++injectCount; - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable); + properties[i] = constructCreatorProperty(ctxt, beanDesc, null, i, param, injectable); continue; } NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param); @@ -510,205 +413,43 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt, */ continue; } - // One more thing: implicit names are ok iff ctor has creator annotation - /* - if (isCreator && (name != null && !name.isEmpty())) { - ++implicitWithCreatorCount; - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId); - continue; - } - */ - if (nonAnnotatedParamIndex < 0) { - nonAnnotatedParamIndex = i; - } } - final int namedCount = explicitNameCount + implicitWithCreatorCount; - // Ok: if named or injectable, we have more work to do - if ((explicitNameCount > 0) || (injectCount > 0)) { - // simple case; everything covered: - if ((namedCount + injectCount) == argCount) { - creators.addPropertyCreator(ctor, false, properties); - continue; - } - if ((explicitNameCount == 0) && ((injectCount + 1) == argCount)) { - // Secondary: all but one injectable, one un-annotated (un-named) - creators.addDelegatingCreator(ctor, false, properties, 0); - continue; - } - // otherwise, epic fail? - // 16-Mar-2015, tatu: due to [#725], need to be more permissive. For now let's - // only report problem if there's no implicit name - PropertyName impl = candidate.findImplicitParamName(nonAnnotatedParamIndex); - if (impl == null || impl.isEmpty()) { - // Let's consider non-static inner class as a special case... - // 25-Jan-2017, tatu: Non-static inner classes skipped altogether, now - /* - if ((nonAnnotatedParamIndex == 0) && isNonStaticInnerClass) { - throw new IllegalArgumentException("Non-static inner classes like " - +ctor.getDeclaringClass().getName()+" cannot use @JsonCreator for constructors"); - } - */ - ctxt.reportBadTypeDefinition(beanDesc, -"Argument #%d of constructor %s has no property name annotation; must have name when multiple-parameter constructor annotated as Creator", -nonAnnotatedParamIndex, ctor); - } - } - // [#725]: as a fallback, all-implicit names may work as well - if (!creators.hasDefaultCreator()) { - if (implicitCtors == null) { - implicitCtors = new LinkedList<>(); - } - implicitCtors.add(ctor); - } - } - // last option, as per [#725]: consider implicit-names-only, visible constructor, - // if just one found - if ((implicitCtors != null) && !creators.hasDelegatingCreator() - && !creators.hasPropertyBasedCreator()) { - _checkImplicitlyNamedConstructors(ctxt, beanDesc, vchecker, intr, - creators, implicitCtors); - } - } - - /* - /********************************************************************** - /* Creator introspection: factory creator introspection - /********************************************************************** - */ - - protected void _addExplicitFactoryCreators(DeserializationContext ctxt, - CreatorCollectionState ccState, boolean findImplicit) - throws JsonMappingException - { - final BeanDescription beanDesc = ccState.beanDesc; - final CreatorCollector creators = ccState.creators; - final AnnotationIntrospector intr = ccState.annotationIntrospector(); - final VisibilityChecker vchecker = ccState.vchecker; - final Map creatorParams = ccState.creatorParams; - - // 21-Sep-2017, tatu: First let's handle explicitly annotated ones - for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) { - JsonCreator.Mode creatorMode = intr.findCreatorAnnotation(ccState.config, factory); - final int argCount = factory.getParameterCount(); - if (creatorMode == null) { - // Only potentially accept 1-argument factory methods - if (findImplicit && (argCount == 1) && vchecker.isCreatorVisible(factory)) { - ccState.addImplicitFactoryCandidate(CreatorCandidate.construct(intr, factory, null)); - } + if ((injectCount + 1) == argCount) { + // Secondary: all but one injectable, one un-annotated (un-named) + creators.addDelegatingCreator(ctor, false, properties, 0); continue; } - if (creatorMode == JsonCreator.Mode.DISABLED) { - continue; - } - - switch (creatorMode) { - case DELEGATING: - _addExplicitDelegatingCreator(ctxt, beanDesc, creators, - CreatorCandidate.construct(intr, factory, null)); - break; - case PROPERTIES: - _addExplicitPropertyCreator(ctxt, beanDesc, creators, - CreatorCandidate.construct(intr, factory, creatorParams.get(factory))); - break; - case DEFAULT: - default: - _addExplicitAnyCreator(ctxt, beanDesc, creators, - CreatorCandidate.construct(intr, factory, creatorParams.get(factory)), - // 13-Sep-2020, tatu: Factory methods do not follow config settings - // (as of Jackson 2.12) - ConstructorDetector.DEFAULT); - break; - } - ccState.increaseExplicitFactoryCount(); + // otherwise, fail? Or no? + /* + ctxt.reportBadTypeDefinition(beanDesc, + "Delegating constructor has %d parameters (with %d Injectables): must have one and only one non-Injectable parameter", + argCount, injectCount); +*/ } } - - protected void _addImplicitFactoryCreators(DeserializationContext ctxt, - CreatorCollectionState ccState, List factoryCandidates) + + private void _addImplicitDelegatingFactories(DeserializationContext ctxt, + VisibilityChecker vchecker, + CreatorCollector creators, + List potentials) throws JsonMappingException { - final BeanDescription beanDesc = ccState.beanDesc; - final CreatorCollector creators = ccState.creators; - final AnnotationIntrospector intr = ccState.annotationIntrospector(); - final VisibilityChecker vchecker = ccState.vchecker; - final Map creatorParams = ccState.creatorParams; - - // And then implicitly found - for (CreatorCandidate candidate : factoryCandidates) { + for (PotentialCreator candidate : potentials) { final int argCount = candidate.paramCount(); AnnotatedWithParams factory = candidate.creator(); - final BeanPropertyDefinition[] propDefs = creatorParams.get(factory); // some single-arg factory methods (String, number) are auto-detected - if (argCount != 1) { - continue; // 2 and more args? Must be explicit, handled earlier - } - BeanPropertyDefinition argDef = candidate.propertyDef(0); - boolean useProps = _checkIfCreatorPropertyBased(beanDesc, intr, factory, argDef); - if (!useProps) { // not property based but delegating + if (argCount == 1) { /*boolean added=*/ _handleSingleArgumentCreator(creators, factory, false, vchecker.isCreatorVisible(factory)); - // 23-Sep-2016, tatu: [databind#1383]: Need to also sever link to avoid possible - // later problems with "unresolved" constructor property - if (argDef != null) { - ((POJOPropertyBuilder) argDef).removeConstructors(); - } - continue; - } - AnnotatedParameter nonAnnotatedParam = null; - SettableBeanProperty[] properties = new SettableBeanProperty[argCount]; - int implicitNameCount = 0; - int explicitNameCount = 0; - int injectCount = 0; - - for (int i = 0; i < argCount; ++i) { - final AnnotatedParameter param = factory.getParameter(i); - BeanPropertyDefinition propDef = (propDefs == null) ? null : propDefs[i]; - JacksonInject.Value injectable = intr.findInjectableValue(param); - final PropertyName name = (propDef == null) ? null : propDef.getFullName(); - - if (propDef != null && propDef.isExplicitlyNamed()) { - ++explicitNameCount; - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable); - continue; - } - if (injectable != null) { - ++injectCount; - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable); - continue; - } - NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param); - if (unwrapper != null) { - _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); - continue; - } - if (nonAnnotatedParam == null) { - nonAnnotatedParam = param; - } - } - final int namedCount = explicitNameCount + implicitNameCount; - - // Ok: if named or injectable, we have more work to do - if (explicitNameCount > 0 || injectCount > 0) { - // simple case; everything covered: - if ((namedCount + injectCount) == argCount) { - creators.addPropertyCreator(factory, false, properties); - } else if ((explicitNameCount == 0) && ((injectCount + 1) == argCount)) { - // secondary: all but one injectable, one un-annotated (un-named) - creators.addDelegatingCreator(factory, false, properties, 0); - } else { // otherwise, epic fail - ctxt.reportBadTypeDefinition(beanDesc, -"Argument #%d of factory method %s has no property name annotation; must have name when multiple-parameter constructor annotated as Creator", - (nonAnnotatedParam == null) ? -1 : nonAnnotatedParam.getIndex(), - factory); - } } + // 2 and more args? Must be explicit, handled earlier } } /* /********************************************************************** - /* Creator introspection: helper methods + /* Creator introspection: older (pre-2.18) helper methods /********************************************************************** */ @@ -717,7 +458,7 @@ protected void _addImplicitFactoryCreators(DeserializationContext ctxt, * * @since 2.9.2 */ - protected void _addExplicitDelegatingCreator(DeserializationContext ctxt, + private boolean _addExplicitDelegatingCreator(DeserializationContext ctxt, BeanDescription beanDesc, CreatorCollector creators, CreatorCandidate candidate) throws JsonMappingException @@ -752,16 +493,10 @@ protected void _addExplicitDelegatingCreator(DeserializationContext ctxt, // 17-Jan-2018, tatu: as per [databind#1853] need to ensure we will distinguish // "well-known" single-arg variants (String, int/long, boolean) from "generic" delegating... if (argCount == 1) { - _handleSingleArgumentCreator(creators, candidate.creator(), true, true); - // one more thing: sever link to creator property, to avoid possible later - // problems with "unresolved" constructor property - BeanPropertyDefinition paramDef = candidate.propertyDef(0); - if (paramDef != null) { - ((POJOPropertyBuilder) paramDef).removeConstructors(); - } - return; + return _handleSingleArgumentCreator(creators, candidate.creator(), true, true); } creators.addDelegatingCreator(candidate.creator(), true, properties, ix); + return true; } /** @@ -770,11 +505,12 @@ protected void _addExplicitDelegatingCreator(DeserializationContext ctxt, * * @since 2.9.2 */ - protected void _addExplicitPropertyCreator(DeserializationContext ctxt, + private void _addExplicitPropertyCreator(DeserializationContext ctxt, BeanDescription beanDesc, CreatorCollector creators, CreatorCandidate candidate) throws JsonMappingException { +//System.err.println("_addExplicitPropertyCreator(): "+candidate); final int paramCount = candidate.paramCount(); SettableBeanProperty[] properties = new SettableBeanProperty[paramCount]; @@ -798,206 +534,7 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, creators.addPropertyCreator(candidate.creator(), true, properties); } - /** - * Helper method called when there is explicit "is-creator" marker, - * but no mode declaration. - * - * @since 2.12 - */ - protected void _addExplicitAnyCreator(DeserializationContext ctxt, - BeanDescription beanDesc, CreatorCollector creators, - CreatorCandidate candidate, ConstructorDetector ctorDetector) - throws JsonMappingException - { - // Looks like there's bit of magic regarding 1-parameter creators; others simpler: - if (1 != candidate.paramCount()) { - // Ok: for delegates, we want one and exactly one parameter without - // injection AND without name - - // 13-Sep-2020, tatu: Can only be delegating if not forced to be properties-based - if (!ctorDetector.singleArgCreatorDefaultsToProperties()) { - int oneNotInjected = candidate.findOnlyParamWithoutInjection(); - if (oneNotInjected >= 0) { - // getting close; but most not have name (or be explicitly specified - // as default-to-delegate) - if (ctorDetector.singleArgCreatorDefaultsToDelegating() - || candidate.paramName(oneNotInjected) == null) { - _addExplicitDelegatingCreator(ctxt, beanDesc, creators, candidate); - return; - } - } - } - _addExplicitPropertyCreator(ctxt, beanDesc, creators, candidate); - return; - } - - // And here be the "simple" single-argument construct - final AnnotatedParameter param = candidate.parameter(0); - final JacksonInject.Value injectId = candidate.injection(0); - PropertyName paramName = null; - - boolean useProps; - switch (ctorDetector.singleArgMode()) { - case DELEGATING: - useProps = false; - break; - case PROPERTIES: - useProps = true; - // 13-Sep-2020, tatu: since we are configured to prefer Properties-style, - // any name (explicit OR implicit does): - paramName = candidate.paramName(0); - // [databind#2977]: Need better exception if name missing - if (paramName == null) { - _validateNamedPropertyParameter(ctxt, beanDesc, candidate, 0, - paramName, injectId); - } - break; - - case REQUIRE_MODE: - ctxt.reportBadTypeDefinition(beanDesc, -"Single-argument constructor (%s) is annotated but no 'mode' defined; `ConstructorDetector`" -+ "configured with `SingleArgConstructor.REQUIRE_MODE`", -candidate.creator()); - return; - case HEURISTIC: - default: - { // Note: behavior pre-Jackson-2.12 - final BeanPropertyDefinition paramDef = candidate.propertyDef(0); - // with heuristic, need to start with just explicit name - paramName = candidate.explicitParamName(0); - - // If there's injection or explicit name, should be properties-based - useProps = (paramName != null); - if (!useProps) { - // Otherwise, `@JsonValue` suggests delegation - if (beanDesc.findJsonValueAccessor() != null) { - } else if (injectId != null) { - // But Injection suggests property-based (for legacy reasons?) - useProps = true; - } else if (paramDef != null) { - // One more thing: if implicit name matches property with a getter - // or field, we'll consider it property-based as well - - // 25-May-2018, tatu: as per [databind#2051], looks like we have to get - // not implicit name, but name with possible strategy-based-rename - // paramName = candidate.findImplicitParamName(0); - paramName = candidate.paramName(0); - useProps = (paramName != null) && paramDef.couldSerialize(); - } - } - } - } - - if (useProps) { - SettableBeanProperty[] properties = new SettableBeanProperty[] { - constructCreatorProperty(ctxt, beanDesc, paramName, 0, param, injectId) - }; - creators.addPropertyCreator(candidate.creator(), true, properties); - return; - } - - _handleSingleArgumentCreator(creators, candidate.creator(), true, true); - - // one more thing: sever link to creator property, to avoid possible later - // problems with "unresolved" constructor property - final BeanPropertyDefinition paramDef = candidate.propertyDef(0); - if (paramDef != null) { - ((POJOPropertyBuilder) paramDef).removeConstructors(); - } - } - - private boolean _checkIfCreatorPropertyBased(BeanDescription beanDesc, - AnnotationIntrospector intr, - AnnotatedWithParams creator, BeanPropertyDefinition propDef) - { - // If explicit name, property-based - if ((propDef != null) && propDef.isExplicitlyNamed()) { - return true; - } - // 01-Dec-2022, tatu: [databind#3654] Consider `@JsonValue` to strongly - // hint at delegation-based - if (beanDesc.findJsonValueAccessor() != null) { - return false; - } - - // Inject id considered property-based - // 01-Dec-2022, tatu: ... but why? - if (intr.findInjectableValue(creator.getParameter(0)) != null) { - return true; - } - if (propDef != null) { - // One more thing: if implicit name matches property with a getter - // or field, we'll consider it property-based as well - String implName = propDef.getName(); - if (implName != null && !implName.isEmpty()) { - if (propDef.couldSerialize()) { - return true; - } - } - // [databind#3897]: Record canonical constructor will have implicitly named propDef - if (!propDef.isExplicitlyNamed() && beanDesc.isRecordType()) { - return true; - } - } - // in absence of everything else, default to delegating - return false; - } - - private void _checkImplicitlyNamedConstructors(DeserializationContext ctxt, - BeanDescription beanDesc, VisibilityChecker vchecker, - AnnotationIntrospector intr, CreatorCollector creators, - List implicitCtors) throws JsonMappingException - { - AnnotatedWithParams found = null; - SettableBeanProperty[] foundProps = null; - - // Further checks: (a) must have names for all parameters, (b) only one visible - // Also, since earlier matching of properties and creators relied on existence of - // `@JsonCreator` (or equivalent) annotation, we need to do bit more re-inspection... - - main_loop: - for (AnnotatedWithParams ctor : implicitCtors) { - if (!vchecker.isCreatorVisible(ctor)) { - continue; - } - // as per earlier notes, only end up here if no properties associated with creator - final int argCount = ctor.getParameterCount(); - SettableBeanProperty[] properties = new SettableBeanProperty[argCount]; - for (int i = 0; i < argCount; ++i) { - final AnnotatedParameter param = ctor.getParameter(i); - final PropertyName name = _findParamName(param, intr); - - // must have name (implicit fine) - if (name == null || name.isEmpty()) { - continue main_loop; - } - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, param.getIndex(), - param, /*injectId*/ null); - } - if (found != null) { // only one allowed; but multiple not an error - found = null; - break; - } - found = ctor; - foundProps = properties; - } - // found one and only one visible? Ship it! - if (found != null) { - creators.addPropertyCreator(found, /*isCreator*/ false, foundProps); - BasicBeanDescription bbd = (BasicBeanDescription) beanDesc; - // Also: add properties, to keep error messages complete wrt known properties... - for (SettableBeanProperty prop : foundProps) { - PropertyName pn = prop.getFullName(); - if (!bbd.hasProperty(pn)) { - BeanPropertyDefinition newDef = SimpleBeanPropertyDefinition.construct( - ctxt.getConfig(), prop.getMember(), pn); - bbd.addProperty(newDef); - } - } - } - } - - protected boolean _handleSingleArgumentCreator(CreatorCollector creators, + private boolean _handleSingleArgumentCreator(CreatorCollector creators, AnnotatedWithParams ctor, boolean isCreator, boolean isVisible) { // otherwise either 'simple' number, String, or general delegate: @@ -1054,7 +591,7 @@ protected boolean _handleSingleArgumentCreator(CreatorCollector creators, // has name or is marked as Injectable // // @since 2.12.1 - protected void _validateNamedPropertyParameter(DeserializationContext ctxt, + private void _validateNamedPropertyParameter(DeserializationContext ctxt, BeanDescription beanDesc, CreatorCandidate candidate, int paramIndex, PropertyName name, JacksonInject.Value injectId) @@ -1070,7 +607,7 @@ protected void _validateNamedPropertyParameter(DeserializationContext ctxt, // 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing // of unwrapped values through creator properties, so fail fast - protected void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, + private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, AnnotatedParameter param) throws JsonMappingException { @@ -1138,35 +675,13 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c return prop; } - private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr) - { - if (intr != null) { - PropertyName name = intr.findNameForDeserialization(param); - if (name != null) { - // 16-Nov-2020, tatu: One quirk, wrt [databind#2932]; may get "use implicit" - // marker; should not return that - if (!name.isEmpty()) { - return name; - } - } - // 14-Apr-2014, tatu: Need to also consider possible implicit name - // (for JDK8, or via paranamer) - - String str = intr.findImplicitPropertyName(param); - if (str != null && !str.isEmpty()) { - return PropertyName.construct(str); - } - } - return null; - } - /** * Helper method copied from {@code POJOPropertyBuilder} since that won't be * applied to creator parameters * * @since 2.10 */ - protected PropertyMetadata _getSetterInfo(MapperConfig config, + private PropertyMetadata _getSetterInfo(MapperConfig config, BeanProperty prop, PropertyMetadata metadata) { final AnnotationIntrospector intr = config.getAnnotationIntrospector(); @@ -2367,21 +1882,6 @@ protected EnumResolver constructEnumNamingStrategyResolver(DeserializationConfig return enumNamingStrategy == null ? null : EnumResolver.constructUsingEnumNamingStrategy(config, enumClass, enumNamingStrategy); } - - /** - * @since 2.9 - * @deprecated Since 2.18 use {@link #_hasCreatorAnnotation(MapperConfig, Annotated)} instead - */ - @Deprecated // since 2.18 - protected boolean _hasCreatorAnnotation(DeserializationContext ctxt, - Annotated ann) { - AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); - if (intr != null) { - JsonCreator.Mode mode = intr.findCreatorAnnotation(ctxt.getConfig(), ann); - return (mode != null) && (mode != JsonCreator.Mode.DISABLED); - } - return false; - } /** * @since 2.18 @@ -2473,90 +1973,4 @@ public static Class findMapFallback(JavaType type) { return _mapFallbacks.get(type.getRawClass().getName()); } } - - /** - * Helper class to contain largish number of parameters that need to be passed - * during Creator introspection. - * - * @since 2.12 - */ - protected static class CreatorCollectionState { - public final MapperConfig config; - public final BeanDescription beanDesc; - public final VisibilityChecker vchecker; - public final CreatorCollector creators; - public final Map creatorParams; - - private List _implicitFactoryCandidates; - private int _explicitFactoryCount; - - private List _implicitConstructorCandidates; - private int _explicitConstructorCount; - - public CreatorCollectionState(MapperConfig cfg, BeanDescription bd, - VisibilityChecker vc, - CreatorCollector cc, - Map cp) - { - config = cfg; - beanDesc = bd; - vchecker = vc; - creators = cc; - creatorParams = cp; - } - - public AnnotationIntrospector annotationIntrospector() { - return config.getAnnotationIntrospector(); - } - - // // // Factory creator candidate info - - public void addImplicitFactoryCandidate(CreatorCandidate cc) { - if (_implicitFactoryCandidates == null) { - _implicitFactoryCandidates = new LinkedList<>(); - } - _implicitFactoryCandidates.add(cc); - } - - public void increaseExplicitFactoryCount() { - ++_explicitFactoryCount; - } - - public boolean hasExplicitFactories() { - return _explicitFactoryCount > 0; - } - - public boolean hasImplicitFactoryCandidates() { - return _implicitFactoryCandidates != null; - } - - public List implicitFactoryCandidates() { - return _implicitFactoryCandidates; - } - - // // // Constructor creator candidate info - - public void addImplicitConstructorCandidate(CreatorCandidate cc) { - if (_implicitConstructorCandidates == null) { - _implicitConstructorCandidates = new LinkedList<>(); - } - _implicitConstructorCandidates.add(cc); - } - - public void increaseExplicitConstructorCount() { - ++_explicitConstructorCount; - } - - public boolean hasExplicitConstructors() { - return _explicitConstructorCount > 0; - } - - public boolean hasImplicitConstructorCandidates() { - return _implicitConstructorCandidates != null; - } - - public List implicitConstructorCandidates() { - return _implicitConstructorCandidates; - } - } } diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java index a3bfa6206f..7927c39909 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java @@ -259,6 +259,11 @@ protected boolean _isStdJDKCollection(JavaType type) // of matches), or ambitious? Let's do latter for now. if (Collection.class.isAssignableFrom(raw) || Map.class.isAssignableFrom(raw)) { + // 28-May-2024, tatu: Complications wrt [databind#4515] / [databind#2795] + // mean that partial introspection NOT for inner classes + if (raw.toString().indexOf('$') > 0) { + return false; + } return true; } } diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index a92acb91ea..8f28dabbc4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -662,6 +662,8 @@ protected void _addCreators(Map props) // Next: remove creators marked as explicitly disabled _removeDisabledCreators(constructors); _removeDisabledCreators(factories); + // And then remove non-annotated static methods that do not look like factories + _removeNonFactoryStaticMethods(factories); // and use annotations to find explicitly chosen Creators if (_useAnnotations) { // can't have explicit ones without Annotation introspection @@ -683,9 +685,30 @@ protected void _addCreators(Map props) // (JDK 17 Record/Scala/Kotlin) if (!creators.hasPropertiesBased()) { // for Records: - if ((canonical != null) && constructors.contains(canonical)) { - constructors.remove(canonical); - creators.setPropertiesBased(_config, canonical, "canonical"); + if (canonical != null) { + // ... but only process if still included as a candidate + if (constructors.remove(canonical)) { + // But wait! Could be delegating + if (_isDelegatingConstructor(canonical)) { + creators.addExplicitDelegating(canonical); + } else { + creators.setPropertiesBased(_config, canonical, "canonical"); + } + } + } + } + + // One more thing: if neither explicit (constructor or factory) nor + // canonical (constructor?), consider implicit Constructor with + // all named. + final ConstructorDetector ctorDetector = _config.getConstructorDetector(); + if (!creators.hasPropertiesBasedOrDelegating() + && !ctorDetector.requireCtorAnnotation()) { + // But only if no default constructor available OR if we are configured + // to prefer properties-based Creators + if ((_classDef.getDefaultConstructor() == null) + || ctorDetector.singleArgCreatorDefaultsToProperties()) { + _addImplicitConstructor(creators, constructors, props); } } @@ -703,6 +726,20 @@ protected void _addCreators(Map props) } } + // Method to determine if given non-explictly-annotated constructor + // looks like delegating one + private boolean _isDelegatingConstructor(PotentialCreator ctor) + { + // Only consider single-arg case, for now + if (ctor.paramCount() == 1) { + // Main thing: @JsonValue makes it delegating: + if ((_jsonValueAccessors != null) && !_jsonValueAccessors.isEmpty()) { + return true; + } + } + return false; + } + private List _collectCreators(List ctors) { if (ctors.isEmpty()) { @@ -728,6 +765,35 @@ private void _removeDisabledCreators(List ctors) } } + private void _removeNonFactoryStaticMethods(List ctors) + { + final Class rawType = _type.getRawClass(); + Iterator it = ctors.iterator(); + while (it.hasNext()) { + // explicit mode? Retain (for now) + PotentialCreator ctor = it.next(); + if (ctor.creatorMode() != null) { + continue; + } + // Copied from `BasicBeanDescription.isFactoryMethod()` + AnnotatedWithParams factory = ctor.creator(); + if (rawType.isAssignableFrom(factory.getRawType()) + && ctor.paramCount() == 1) { + String name = factory.getName(); + + if ("valueOf".equals(name)) { + continue; + } else if ("fromString".equals(name)) { + Class cls = factory.getRawParameterType(0); + if (cls == String.class || CharSequence.class.isAssignableFrom(cls)) { + continue; + } + } + } + it.remove(); + } + } + private void _addExplicitlyAnnotatedCreators(PotentialCreators collector, List ctors, Map props, @@ -743,48 +809,25 @@ private void _addExplicitlyAnnotatedCreators(PotentialCreators collector, if (ctor.creatorMode() == null) { continue; } + it.remove(); - Boolean propsBased = null; - + boolean isPropsBased; + switch (ctor.creatorMode()) { case DELEGATING: - propsBased = false; + isPropsBased = false; break; case PROPERTIES: - propsBased = true; + isPropsBased = true; break; case DEFAULT: default: - // First things first: if not single-arg Creator, must be Properties-based - // !!! Or does it? What if there's @JacksonInject etc? - if (ctor.paramCount() != 1) { - propsBased = true; - } - } - - // Must be 1-arg case: - if (propsBased == null) { - // Is ambiguity/heuristics allowed? - if (ctorDetector.requireCtorAnnotation()) { - throw new IllegalArgumentException(String.format( - "Ambiguous 1-argument Creator; `ConstructorDetector` requires specifying `mode`: %s", - ctor)); - } - - // First things first: if explicit names found, is Properties-based - ctor.introspectParamNames(_config); - propsBased = ctor.hasExplicitNames() - || ctorDetector.singleArgCreatorDefaultsToProperties(); - // One more possibility: implicit name that maps to implied - // property based on Field/Getter/Setter - if (!propsBased) { - String implName = ctor.implicitNameSimple(0); - propsBased = (implName != null) && props.containsKey(implName); - } + isPropsBased = _isExplicitlyAnnotatedCreatorPropsBased(ctor, + props, ctorDetector); } - if (propsBased) { + if (isPropsBased) { // Skipping done if we already got higher-precedence Creator if (!skipPropsBased) { collector.setPropertiesBased(_config, ctor, "explicit"); @@ -795,6 +838,53 @@ private void _addExplicitlyAnnotatedCreators(PotentialCreators collector, } } + private boolean _isExplicitlyAnnotatedCreatorPropsBased(PotentialCreator ctor, + Map props, ConstructorDetector ctorDetector) + { + if (ctor.paramCount() == 1) { + // Is ambiguity/heuristics allowed? + switch (ctorDetector.singleArgMode()) { + case DELEGATING: + return false; + case PROPERTIES: + return true; + case REQUIRE_MODE: + throw new IllegalArgumentException(String.format( +"Single-argument constructor (%s) is annotated but no 'mode' defined; `ConstructorDetector`" ++ "configured with `SingleArgConstructor.REQUIRE_MODE`", +ctor.creator())); + case HEURISTIC: + default: + } + } + + // First: if explicit names found, is Properties-based + ctor.introspectParamNames(_config); + if (ctor.hasExplicitNames()) { + return true; + } + // Second: [databind#3180] @JsonValue indicates delegating + if ((_jsonValueAccessors != null) && !_jsonValueAccessors.isEmpty()) { + return false; + } + if (ctor.paramCount() == 1) { + // One more possibility: implicit name that maps to implied + // property based on Field/Getter/Setter + String implName = ctor.implicitNameSimple(0); + if ((implName != null) && props.containsKey(implName)) { + return true; + } + // Second: injectable also suffices + if ((_annotationIntrospector != null) + && _annotationIntrospector.findInjectableValue(ctor.param(0)) != null) { + return true; + } + return false; + } + // Trickiest case: rely on existence of implicit names and/or injectables + return ctor.hasNameOrInjectForAllParams(_config); + } + private void _addCreatorsWithAnnotatedNames(PotentialCreators collector, List ctors) { @@ -813,6 +903,50 @@ private void _addCreatorsWithAnnotatedNames(PotentialCreators collector, } } + private boolean _addImplicitConstructor(PotentialCreators collector, + List ctors, Map props) + { + // Must have one and only one candidate + if (ctors.size() != 1) { + return false; + } + final PotentialCreator ctor = ctors.get(0); + // which needs to be visible + if (!_visibilityChecker.isCreatorVisible(ctor.creator())) { + return false; + } + ctor.introspectParamNames(_config); + + // As usual, 1-param case is distinct + if (ctor.paramCount() != 1) { + if (!ctor.hasNameOrInjectForAllParams(_config)) { + return false; + } + } else { + // First things first: if only param has Injectable, must be Props-based + if ((_annotationIntrospector != null) + && _annotationIntrospector.findInjectableValue(ctor.param(0)) != null) { + // props-based, continue + } else { + // may have explicit preference + final ConstructorDetector ctorDetector = _config.getConstructorDetector(); + if (ctorDetector.singleArgCreatorDefaultsToDelegating()) { + return false; + } + // if not, prefer Properties-based if explicit preference OR + // property with same name + if (!ctorDetector.singleArgCreatorDefaultsToProperties() + && !props.containsKey(ctor.implicitNameSimple(0))) { + return false; + } + } + } + + ctors.remove(0); + collector.setPropertiesBased(_config, ctor, "implicit"); + return true; + } + private void _addCreatorParams(Map props, PotentialCreator ctor, List creatorProps) { diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreator.java b/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreator.java index 04b2fba092..7333ddb977 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreator.java @@ -142,10 +142,28 @@ public boolean hasExplicitNames() { return false; } + public boolean hasNameFor(int ix) { + return (explicitParamNames[ix] != null) + || (implicitParamNames[ix] != null); + } + + public boolean hasNameOrInjectForAllParams(MapperConfig config) + { + final AnnotationIntrospector intr = config.getAnnotationIntrospector(); + for (int i = 0, end = implicitParamNames.length; i < end; ++i) { + if (!hasNameFor(i)) { + if (intr == null || intr.findInjectableValue(creator.getParameter(i)) == null) { + return false; + } + } + } + return true; + } + public PropertyName explicitName(int ix) { return explicitParamNames[ix]; } - + public PropertyName implicitName(int ix) { return implicitParamNames[ix]; } diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreators.java b/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreators.java index 130a44caf3..4441621aec 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreators.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/PotentialCreators.java @@ -69,10 +69,10 @@ public List getExplicitDelegating() { } public List getImplicitDelegatingFactories() { - return implicitDelegatingFactories; + return (implicitDelegatingFactories == null) ? Collections.emptyList() : implicitDelegatingFactories; } public List getImplicitDelegatingConstructors() { - return implicitDelegatingConstructors; + return (implicitDelegatingConstructors == null) ? Collections.emptyList() : implicitDelegatingConstructors; } } diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java index 9644eb49d3..078459097a 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java @@ -34,7 +34,7 @@ public RecordWithDelegation(String value) { this.value = "del:"+value; } - @JsonValue() + @JsonValue public String getValue() { return "val:"+value; } diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java index 0c3238baff..61eeb472e5 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java @@ -197,18 +197,15 @@ public void testDeserializeUsingCanonicalConstructor_WhenJsonCreatorConstructorE } } + // 23-May-2024, tatu: Logic changed as part of [databind#4515]: explicit properties-based + // Creator does NOT block implicit delegating Creators. So formerly (pre-2.18) failing + // case is now expected to pass. @Test public void testDeserializeUsingImplicitFactoryMethod_WhenJsonCreatorConstructorExists_WillFail() throws Exception { - try { - MAPPER.readValue("123", RecordWithJsonPropertyWithJsonCreator.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithJsonPropertyWithJsonCreator"); - verifyException(e, "although at least one Creator exists"); - verifyException(e, "no int/Int-argument constructor/factory method"); - } + RecordWithJsonPropertyWithJsonCreator value = MAPPER.readValue("123", + RecordWithJsonPropertyWithJsonCreator.class); + assertEquals(123, value.id()); + assertEquals("JsonCreatorConstructor", value.name()); } /* diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/ConstructorDetector1498Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ConstructorDetector1498Test.java index cbce6fa4ba..d4a2d5dcae 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/ConstructorDetector1498Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ConstructorDetector1498Test.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.*; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.json.JsonMapper; @@ -147,13 +148,20 @@ public void testMultiArgAsProperties() throws Exception @Test public void test1ArgDefaultsToPropsMultipleCtors() throws Exception { + // 23-May-2024, tatu: Will fail differently with [databind#4515]; default + // constructor available, implicit ones ignored try { MAPPER_PROPS.readValue(a2q("{'value' : 137 }"), SingleArg2CtorsNotAnnotated.class); fail("Should not pass"); + } catch (UnrecognizedPropertyException e) { + verifyException(e, "\"value\""); + } + /* } catch (InvalidDefinitionException e) { verifyException(e, "Conflicting property-based creators"); } + */ } /* diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java index 2b7818eaca..d3224a84b3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +import com.fasterxml.jackson.databind.json.JsonMapper; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -180,9 +181,15 @@ public void testNamedSingleArg() throws Exception @Test public void testSingleStringArgWithImplicitName() throws Exception { - final ObjectMapper mapper = new ObjectMapper(); - mapper.setAnnotationIntrospector(new MyParamIntrospector("value")); - StringyBean bean = mapper.readValue(q("foobar"), StringyBean.class); + final ObjectMapper mapper = JsonMapper.builder() + .annotationIntrospector(new MyParamIntrospector("value")) + .build(); + // 23-May-2024, tatu: [databind#4515] Clarifies handling to make + // 1-param Constructor with implicit name auto-discoverable + // This is compatibility change so hopefully won't bite us but... + // it seems like the right thing to do. +// StringyBean bean = mapper.readValue(q("foobar"), StringyBean.class); + StringyBean bean = mapper.readValue(a2q("{'value':'foobar'}"), StringyBean.class); assertEquals("foobar", bean.getValue()); } @@ -190,8 +197,9 @@ public void testSingleStringArgWithImplicitName() throws Exception @Test public void testSingleImplicitlyNamedNotDelegating() throws Exception { - final ObjectMapper mapper = new ObjectMapper(); - mapper.setAnnotationIntrospector(new MyParamIntrospector("value")); + final ObjectMapper mapper = JsonMapper.builder() + .annotationIntrospector(new MyParamIntrospector("value")) + .build(); StringyBeanWithProps bean = mapper.readValue("{\"value\":\"x\"}", StringyBeanWithProps.class); assertEquals("x", bean.getValue()); } diff --git a/src/test/java/com/fasterxml/jackson/databind/mixins/MixinForCreators2795Test.java b/src/test/java/com/fasterxml/jackson/databind/mixins/MixinForCreators2795Test.java index 2440cf7d01..7fe4e043fe 100644 --- a/src/test/java/com/fasterxml/jackson/databind/mixins/MixinForCreators2795Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/mixins/MixinForCreators2795Test.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.*; @@ -24,7 +23,7 @@ public UnmodifiableCollectionMixin(final Collection collection) { } @Test public void testMixinWithUnmmodifiableCollection() throws Exception { - ObjectMapper mapper = JsonMapper.builder() + ObjectMapper mapper = jsonMapperBuilder() .addMixIn(Collections.unmodifiableCollection(Collections.emptyList()).getClass(), UnmodifiableCollectionMixin.class) .build(); @@ -35,8 +34,8 @@ public void testMixinWithUnmmodifiableCollection() throws Exception final Collection unmodifiableCollection = Collections.unmodifiableCollection(strings); final byte[] bytes = mapper.writeValueAsBytes(unmodifiableCollection); - final Collection collection = mapper.readValue(bytes, Collection.class); + final Collection result = mapper.readValue(bytes, Collection.class); - assertEquals(2, collection.size()); + assertEquals(2, result.size()); } } diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectIdWithInjectables538Test.java b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectIdWithInjectables538Test.java index 157d74c84f..320407b6c8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectIdWithInjectables538Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectIdWithInjectables538Test.java @@ -34,7 +34,7 @@ public B(@JacksonInject("i2") String injected) { /***************************************************** */ - private final ObjectMapper MAPPER = new ObjectMapper(); + private final ObjectMapper MAPPER = newJsonMapper(); @Test public void testWithInjectables538() throws Exception