Skip to content

Commit

Permalink
Merge branch '2.16'
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Aug 26, 2023
2 parents 5dd6d9b + 39c4f1f commit 599ab04
Show file tree
Hide file tree
Showing 7 changed files with 509 additions and 8 deletions.
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1684,3 +1684,8 @@ Jason Laber (jlaber@github)
Andreas Zahnen (azahnen@github)
* Reported #4078: `java.desktop` module is no longer optional
(2.16.0)
Omar Aloraini (ooraini@github)
* Requested #4061: Add JsonTypeInfo.Id.SIMPLE_NAME which defaults type id
to `Class.getSimpleName()`
(2.16.0)
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Project: jackson-databind
#4056: Provide the "ObjectMapper.treeToValue(TreeNode, TypeReference)" method
(contributed by @fantasy0v0)
#4060: Expose `NativeImageUtil.isRunningInNativeImage()` method
#4061: Add JsonTypeInfo.Id.SIMPLE_NAME which defaults type id to `Class.getSimpleName()`
(requested by Omar A)
(contributed by Joo-Hyuk K)
#4071: Impossible to deserialize custom `Throwable` sub-classes that do not
have single-String constructors
(reported by @PasKal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import tools.jackson.databind.JavaType;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;

/**
* Specialization of {@link ClassNameIdResolver} that instead uses a
* "minimal" derivation of {@link Class} name, using relative reference
* from the base type (base class) that polymorphic value has.
*/
public class MinimalClassNameIdResolver
extends ClassNameIdResolver
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package tools.jackson.databind.jsontype.impl;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import tools.jackson.databind.*;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.jsontype.NamedType;

/**
* {@link tools.jackson.databind.jsontype.TypeIdResolver} implementation
* that converts using explicitly (annotation-) specified type names
* and maps to implementation classes; or, in absence of annotated type name,
* defaults to simple {@link Class} names (obtained with {@link Class#getSimpleName()}.
* Basically same as {@link TypeNameIdResolver} except for default to "simple"
* and not "full" class name.
*
* @since 2.16
*/
public class SimpleNameIdResolver
extends TypeIdResolverBase
{
/**
* Mappings from class name to type id, used for serialization.
*<p>
* Since lazily constructed will require synchronization (either internal
* by type, or external)
*/
protected final ConcurrentHashMap<String, String> _typeToId;

/**
* Mappings from type id to JavaType, used for deserialization.
*<p>
* Eagerly constructed, not modified, can use regular unsynchronized {@link Map}.
*/
protected final Map<String, JavaType> _idToType;

protected final boolean _caseInsensitive;

protected SimpleNameIdResolver(JavaType baseType,
ConcurrentHashMap<String, String> typeToId,
HashMap<String, JavaType> idToType,
boolean caseInsensitive)
{
super(baseType);
_typeToId = typeToId;
_idToType = idToType;
_caseInsensitive = caseInsensitive;
}

public static SimpleNameIdResolver construct(MapperConfig<?> config, JavaType baseType,
Collection<NamedType> subtypes, boolean forSer, boolean forDeser)
{
// sanity check
if (forSer == forDeser) throw new IllegalArgumentException();

final ConcurrentHashMap<String, String> typeToId;
final HashMap<String, JavaType> idToType;

if (forSer) {
// Only need Class-to-id for serialization; but synchronized since may be
// lazily built (if adding type-id-mappings dynamically)
typeToId = new ConcurrentHashMap<>();
idToType = null;
} else {
idToType = new HashMap<>();
// 14-Apr-2016, tatu: Apparently needed for special case of `defaultImpl`;
// see [databind#1198] for details: but essentially we only need room
// for a single value.
typeToId = new ConcurrentHashMap<>(4);
}
final boolean caseInsensitive = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES);

if (subtypes != null) {
for (NamedType t : subtypes) {
// no name? Need to figure out default; for now, let's just
// use non-qualified class name
Class<?> cls = t.getType();
String id = t.hasName() ? t.getName() : _defaultTypeId(cls);
if (forSer) {
typeToId.put(cls.getName(), id);
}
if (forDeser) {
// [databind#1983]: for case-insensitive lookups must canonicalize:
if (caseInsensitive) {
id = id.toLowerCase();
}
// One more problem; sometimes we have same name for multiple types;
// if so, use most specific
JavaType prev = idToType.get(id); // lgtm [java/dereferenced-value-may-be-null]
if (prev != null) { // Can only override if more specific
if (cls.isAssignableFrom(prev.getRawClass())) { // nope, more generic (or same)
continue;
}
}
idToType.put(id, config.constructType(cls));
}
}
}
return new SimpleNameIdResolver(baseType, typeToId, idToType, caseInsensitive);
}

@Override
public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.SIMPLE_NAME; }

@Override
public String idFromValue(DatabindContext ctxt, Object value) {
return idFromClass(ctxt, value.getClass());
}

protected String idFromClass(DatabindContext ctxt, Class<?> cls)
{
if (cls == null) {
return null;
}
// 12-Oct-2019, tatu: This looked weird; was done in 2.x to force application
// of `TypeModifier`. But that just... does not seem right, at least not in
// the sense that raw class would be changed (intent for modifier is to change
// `JavaType` being resolved, not underlying class. Hence commented out in
// 3.x. There should be better way to support whatever the use case is.

// 29-Nov-2019, tatu: Looking at 2.x, test in `TestTypeModifierNameResolution` suggested
// that use of `TypeModifier` was used for demoting some types (from impl class to
// interface. For what that's worth. Still not supported for 3.x until proven necessary

// cls = _typeFactory.constructType(cls).getRawClass();

final String key = cls.getName();
String name = _typeToId.get(key);

if (name == null) {
// 24-Feb-2011, tatu: As per [JACKSON-498], may need to dynamically look up name
// can either throw an exception, or use default name...
if (ctxt.isAnnotationProcessingEnabled()) {
name = ctxt.getAnnotationIntrospector().findTypeName(ctxt.getConfig(),
ctxt.introspectClassAnnotations(cls));
}
if (name == null) {
// And if still not found, let's choose default?
name = _defaultTypeId(cls);
}
_typeToId.put(key, name);
}
return name;
}

@Override
public String idFromValueAndType(DatabindContext ctxt, Object value, Class<?> type) {
// 18-Jan-2013, tatu: We may be called with null value occasionally
// it seems; nothing much we can figure out that way.
if (value == null) {
return idFromClass(ctxt, type);
}
return idFromValue(ctxt, value);
}

@Override
public JavaType typeFromId(DatabindContext ctxt, String id) {
return _typeFromId(id);
}

protected JavaType _typeFromId(String id) {
// [databind#1983]: for case-insensitive lookups must canonicalize:
if (_caseInsensitive) {
id = id.toLowerCase();
}
return _idToType.get(id);
}

@Override
public String getDescForKnownTypeIds() {
// 05-May-2020, tatu: As per [databind#1919], only include ids for
// non-abstract types
final TreeSet<String> ids = new TreeSet<>();
for (Map.Entry<String, JavaType> entry : _idToType.entrySet()) {
if (entry.getValue().isConcrete()) {
ids.add(entry.getKey());
}
}
return ids.toString();
}

/*
/**********************************************************************
/* Helper methods
/**********************************************************************
*/

/**
* If no name was explicitly given for a class, we will just
* use simple class name
*/
protected static String _defaultTypeId(Class<?> cls)
{
String n = cls.getName();
int ix = Math.max(n.lastIndexOf('.'), n.lastIndexOf('$'));
return (ix < 0) ? n : n.substring(ix+1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,12 @@ protected TypeIdResolver idResolver(DatabindContext ctxt,
return ClassNameIdResolver.construct(baseType, subtypeValidator);
case MINIMAL_CLASS:
return MinimalClassNameIdResolver.construct(baseType, subtypeValidator);
case SIMPLE_NAME:
return SimpleNameIdResolver.construct(ctxt.getConfig(), baseType,
subtypes, forSer, forDeser);
case NAME:
return TypeNameIdResolver.construct(ctxt.getConfig(), baseType, subtypes, forSer, forDeser);
return TypeNameIdResolver.construct(ctxt.getConfig(), baseType,
subtypes, forSer, forDeser);
case NONE: // hmmh. should never get this far with 'none'
return null;
case CUSTOM: // need custom resolver...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.jsontype.NamedType;

/**
* {@link tools.jackson.databind.jsontype.TypeIdResolver} implementation
* that converts using explicitly (annotation-) specified type names
* and maps to implementation classes; or, in absence of annotated type name,
* defaults to fully-qualified {@link Class} names (obtained with {@link Class#getName()}
*/
public class TypeNameIdResolver extends TypeIdResolverBase
{
/**
Expand Down Expand Up @@ -177,13 +183,6 @@ public String getDescForKnownTypeIds() {
return ids.toString();
}

/*
@Override
public String toString() {
return String.format("[%s; id-to-type=%s]", getClass().getName(), _idToType);
}
*/

/*
/**********************************************************************
/* Helper methods
Expand Down
Loading

0 comments on commit 599ab04

Please sign in to comment.