Skip to content

Commit 37879ab

Browse files
committed
Fix #312
1 parent f7270ed commit 37879ab

File tree

6 files changed

+118
-20
lines changed

6 files changed

+118
-20
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Project: jackson-databind
66

77
2.6.0 (not yet released)
88

9+
#312: Support Type Id mappings where two ids map to same Class
910
#649: Make `BeanDeserializer` use new `parser.nextFieldName()` and `.hasTokenId()` methods
1011
#696: Copy constructor does not preserve `_injectableValues`
1112
(reported by Charles A)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ public TypeDeserializer findTypeDeserializer(JavaType baseType)
750750
return null;
751751
}
752752
} else {
753-
subtypes = getSubtypeResolver().collectAndResolveSubtypesByName(this, ac);
753+
subtypes = getSubtypeResolver().collectAndResolveSubtypesByTypeId(this, ac);
754754
}
755755
/* 04-May-2014, tatu: When called from DeserializerFactory, additional code like
756756
* this is invoked. But here we do not actually have access to mappings, so not

src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,7 @@ public TypeDeserializer findTypeDeserializer(DeserializationConfig config,
12471247
return null;
12481248
}
12491249
} else {
1250-
subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByName(config, ac);
1250+
subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config, ac);
12511251
}
12521252
// [JACKSON-505]: May need to figure out default implementation, if none found yet
12531253
// (note: check for abstract type is not 100% mandatory, more of an optimization)
@@ -1387,7 +1387,7 @@ public TypeDeserializer findPropertyTypeDeserializer(DeserializationConfig confi
13871387
return findTypeDeserializer(config, baseType);
13881388
}
13891389
// but if annotations found, may need to resolve subtypes:
1390-
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByName(
1390+
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(
13911391
config, annotated, baseType);
13921392
return b.buildTypeDeserializer(config, baseType, subtypes);
13931393
}
@@ -1415,7 +1415,7 @@ public TypeDeserializer findPropertyContentTypeDeserializer(DeserializationConfi
14151415
return findTypeDeserializer(config, contentType);
14161416
}
14171417
// but if annotations found, may need to resolve subtypes:
1418-
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByName(
1418+
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(
14191419
config, propertyEntity, contentType);
14201420
return b.buildTypeDeserializer(config, contentType, subtypes);
14211421
}

src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public Collection<NamedType> collectAndResolveSubtypesByClass(MapperConfig<?> co
8383
*
8484
* @since 2.6
8585
*/
86-
public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> config,
86+
public Collection<NamedType> collectAndResolveSubtypesByTypeId(MapperConfig<?> config,
8787
AnnotatedMember property, JavaType baseType) {
8888
// for backwards compatibility...
8989
return collectAndResolveSubtypes(property, config,
@@ -101,7 +101,7 @@ public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> con
101101
*
102102
* @since 2.6
103103
*/
104-
public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> config,
104+
public Collection<NamedType> collectAndResolveSubtypesByTypeId(MapperConfig<?> config,
105105
AnnotatedClass baseType) {
106106
// for backwards compatibility...
107107
return collectAndResolveSubtypes(baseType, config, config.getAnnotationIntrospector());
@@ -116,7 +116,7 @@ public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> con
116116
/**
117117
* @deprecated Since 2.6 Use either
118118
* {@link #collectAndResolveSubtypesByClass(MapperConfig, AnnotatedMember, JavaType)}
119-
* or {@link #collectAndResolveSubtypesByName(MapperConfig, AnnotatedMember, JavaType)}
119+
* or {@link #collectAndResolveSubtypesByTypeId(MapperConfig, AnnotatedMember, JavaType)}
120120
* instead.
121121
*/
122122
@Deprecated
@@ -126,7 +126,7 @@ public abstract Collection<NamedType> collectAndResolveSubtypes(AnnotatedMember
126126
/**
127127
* @deprecated Since 2.6 Use either
128128
* {@link #collectAndResolveSubtypesByClass(MapperConfig, AnnotatedClass)}
129-
* or {@link #collectAndResolveSubtypesByName(MapperConfig, AnnotatedClass)}
129+
* or {@link #collectAndResolveSubtypesByTypeId(MapperConfig, AnnotatedClass)}
130130
* instead.
131131
*/
132132
@Deprecated

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java

+108-11
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,65 @@ public Collection<NamedType> collectAndResolveSubtypesByClass(MapperConfig<?> co
121121
*/
122122

123123
@Override
124-
public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> config,
124+
public Collection<NamedType> collectAndResolveSubtypesByTypeId(MapperConfig<?> config,
125125
AnnotatedMember property, JavaType baseType)
126126
{
127-
// !!! TODO: implement properly
128-
return collectAndResolveSubtypesByClass(config, property, baseType);
127+
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
128+
Class<?> rawBase = (baseType == null) ? property.getRawType() : baseType.getRawClass();
129+
130+
// Need to keep track of classes that have been handled already
131+
Set<Class<?>> typesHandled = new HashSet<Class<?>>();
132+
Map<String,NamedType> byName = new LinkedHashMap<String,NamedType>();
133+
134+
// start with lowest-precedence, which is from type hierarchy
135+
NamedType rootType = new NamedType(rawBase, null);
136+
AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(rawBase, ai, config);
137+
_collectAndResolveByTypeId(ac, rootType, config, typesHandled, byName);
138+
139+
// then with definitions from property
140+
Collection<NamedType> st = ai.findSubtypes(property);
141+
if (st != null) {
142+
for (NamedType nt : st) {
143+
ac = AnnotatedClass.constructWithoutSuperTypes(nt.getType(), ai, config);
144+
_collectAndResolveByTypeId(ac, nt, config, typesHandled, byName);
145+
}
146+
}
147+
148+
// and finally explicit type registrations (highest precedence)
149+
if (_registeredSubtypes != null) {
150+
for (NamedType subtype : _registeredSubtypes) {
151+
// is it a subtype of root type?
152+
if (rawBase.isAssignableFrom(subtype.getType())) { // yes
153+
AnnotatedClass curr = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), ai, config);
154+
_collectAndResolveByTypeId(curr, subtype, config, typesHandled, byName);
155+
}
156+
}
157+
}
158+
return _combineNamedAndUnnamed(typesHandled, byName);
129159
}
130160

131161
@Override
132-
public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> config,
162+
public Collection<NamedType> collectAndResolveSubtypesByTypeId(MapperConfig<?> config,
133163
AnnotatedClass type)
134164
{
135-
// !!! TODO: implement properly
136-
return collectAndResolveSubtypesByClass(config, type);
165+
Set<Class<?>> typesHandled = new HashSet<Class<?>>();
166+
Map<String,NamedType> byName = new LinkedHashMap<String,NamedType>();
167+
168+
NamedType rootType = new NamedType(type.getRawType(), null);
169+
_collectAndResolveByTypeId(type, rootType, config, typesHandled, byName);
170+
171+
if (_registeredSubtypes != null) {
172+
Class<?> rawBase = type.getRawType();
173+
for (NamedType subtype : _registeredSubtypes) {
174+
// is it a subtype of root type?
175+
if (rawBase.isAssignableFrom(subtype.getType())) { // yes
176+
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
177+
AnnotatedClass curr = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), ai, config);
178+
_collectAndResolveByTypeId(curr, subtype, config, typesHandled, byName);
179+
}
180+
}
181+
}
182+
return _combineNamedAndUnnamed(typesHandled, byName);
137183
}
138184

139185
/*
@@ -143,13 +189,15 @@ public Collection<NamedType> collectAndResolveSubtypesByName(MapperConfig<?> con
143189
*/
144190

145191
@Override
192+
@Deprecated
146193
public Collection<NamedType> collectAndResolveSubtypes(AnnotatedMember property,
147194
MapperConfig<?> config, AnnotationIntrospector ai, JavaType baseType)
148195
{
149196
return collectAndResolveSubtypesByClass(config, property, baseType);
150197
}
151198

152199
@Override
200+
@Deprecated
153201
public Collection<NamedType> collectAndResolveSubtypes(AnnotatedClass type,
154202
MapperConfig<?> config, AnnotationIntrospector ai)
155203
{
@@ -163,7 +211,8 @@ public Collection<NamedType> collectAndResolveSubtypes(AnnotatedClass type,
163211
*/
164212

165213
/**
166-
* Method called to find subtypes for a specific type (class)
214+
* Method called to find subtypes for a specific type (class), using
215+
* type (class) as the unique key (in case of conflicts).
167216
*/
168217
protected void _collectAndResolve(AnnotatedClass annotatedType, NamedType namedType,
169218
MapperConfig<?> config, AnnotationIntrospector ai,
@@ -193,12 +242,60 @@ protected void _collectAndResolve(AnnotatedClass annotatedType, NamedType namedT
193242
if (st != null && !st.isEmpty()) {
194243
for (NamedType subtype : st) {
195244
AnnotatedClass subtypeClass = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), ai, config);
196-
// One more thing: name may be either in reference, or in subtype:
197-
if (!subtype.hasName()) {
198-
subtype = new NamedType(subtype.getType(), ai.findTypeName(subtypeClass));
199-
}
200245
_collectAndResolve(subtypeClass, subtype, config, ai, collectedSubtypes);
201246
}
202247
}
203248
}
249+
250+
/**
251+
* Method called to find subtypes for a specific type (class), using
252+
* type id as the unique key (in case of conflicts).
253+
*/
254+
protected void _collectAndResolveByTypeId(AnnotatedClass annotatedType, NamedType namedType,
255+
MapperConfig<?> config,
256+
Set<Class<?>> typesHandled, Map<String,NamedType> byName)
257+
{
258+
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
259+
if (!namedType.hasName()) {
260+
String name = ai.findTypeName(annotatedType);
261+
if (name != null) {
262+
namedType = new NamedType(namedType.getType(), name);
263+
}
264+
}
265+
if (namedType.hasName()) {
266+
byName.put(namedType.getName(), namedType);
267+
}
268+
269+
// only check subtypes if this type hadn't yet been handled
270+
if (typesHandled.add(namedType.getType())) {
271+
Collection<NamedType> st = ai.findSubtypes(annotatedType);
272+
if (st != null && !st.isEmpty()) {
273+
for (NamedType subtype : st) {
274+
AnnotatedClass subtypeClass = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), ai, config);
275+
_collectAndResolveByTypeId(subtypeClass, subtype, config, typesHandled, byName);
276+
}
277+
}
278+
}
279+
}
280+
281+
/**
282+
* Helper method used for merging explicitly named types and handled classes
283+
* without explicit names.
284+
*/
285+
protected Collection<NamedType> _combineNamedAndUnnamed(Set<Class<?>> typesHandled,
286+
Map<String,NamedType> byName)
287+
{
288+
ArrayList<NamedType> result = new ArrayList<NamedType>(byName.values());
289+
290+
// Ok, so... we will figure out which classes have no explicitly assigned name,
291+
// by removing Classes from Set. And for remaining classes, add an anonymous
292+
// marker
293+
for (NamedType t : byName.values()) {
294+
typesHandled.remove(t.getType());
295+
}
296+
for (Class<?> cls : typesHandled) {
297+
result.add(new NamedType(cls));
298+
}
299+
return result;
300+
}
204301
}

src/test/java/com/fasterxml/jackson/failing/TestOverlappingTypeIdNames312.java renamed to src/test/java/com/fasterxml/jackson/databind/jsontype/TestOverlappingTypeIdNames312.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.failing;
1+
package com.fasterxml.jackson.databind.jsontype;
22

33
import com.fasterxml.jackson.annotation.*;
44
import com.fasterxml.jackson.databind.*;

0 commit comments

Comments
 (0)