Skip to content

Commit 4675896

Browse files
committed
Fix #1604
1 parent d0bbfe3 commit 4675896

File tree

7 files changed

+226
-103
lines changed

7 files changed

+226
-103
lines changed

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ Project: jackson-databind
55

66
2.8.11 (not yet released)
77

8+
#1604: Nested type arguments doesn't work with polymorphic types
89
#1767: Allow `DeserializationProblemHandler` to respond to primitive types
910
(reported by nhtzr@github)
1011
#1768: Improve `TypeFactory.constructFromCanonical()` to work with
1112
`java.lang.reflect.Type.getTypeName()` format
1213

14+
1315
2.8.10 (24-Aug-2017)
1416

1517
#1657: `StdDateFormat` deserializes dates with no tz/offset as UTC instead of
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.fasterxml.jackson.databind.type;
2+
3+
import com.fasterxml.jackson.databind.JavaType;
4+
5+
/**
6+
* Helper type used when introspecting bindings for already resolved types,
7+
* needed for specialization.
8+
*
9+
* @since 2.8.11
10+
*/
11+
public class PlaceholderForType extends TypeBase
12+
{
13+
private static final long serialVersionUID = 1L;
14+
15+
protected final int _ordinal;
16+
17+
/**
18+
* Type assigned during wildcard resolution (which follows type
19+
* structure resolution)
20+
*/
21+
protected JavaType _actualType;
22+
23+
public PlaceholderForType(int ordinal)
24+
{
25+
super(Object.class, TypeBindings.emptyBindings(),
26+
TypeFactory.unknownType(), null, 1, // super-class, super-interfaces, hashCode
27+
null, null, false); // value/type handler, as-static
28+
_ordinal = ordinal;
29+
}
30+
31+
public JavaType actualType() { return _actualType; }
32+
public void actualType(JavaType t) { _actualType = t; }
33+
34+
// Override to get better diagnostics
35+
@Override
36+
protected String buildCanonicalName() {
37+
return toString();
38+
}
39+
40+
@Override
41+
public StringBuilder getGenericSignature(StringBuilder sb) {
42+
return getErasedSignature(sb);
43+
}
44+
45+
@Override
46+
public StringBuilder getErasedSignature(StringBuilder sb) {
47+
sb.append('$').append(_ordinal+1);
48+
return sb;
49+
}
50+
51+
@Override
52+
public JavaType withTypeHandler(Object h) {
53+
return _unsupported();
54+
}
55+
56+
@Override
57+
public JavaType withContentTypeHandler(Object h) {
58+
return _unsupported();
59+
}
60+
61+
@Override
62+
public JavaType withValueHandler(Object h) {
63+
return _unsupported();
64+
}
65+
66+
@Override
67+
public JavaType withContentValueHandler(Object h) {
68+
return _unsupported();
69+
}
70+
71+
@Override
72+
public JavaType withContentType(JavaType contentType) {
73+
return _unsupported();
74+
}
75+
76+
@Override
77+
public JavaType withStaticTyping() {
78+
return _unsupported();
79+
}
80+
81+
@Override
82+
public JavaType refine(Class<?> rawType, TypeBindings bindings, JavaType superClass, JavaType[] superInterfaces) {
83+
return _unsupported();
84+
}
85+
86+
@Override
87+
protected JavaType _narrow(Class<?> subclass) {
88+
return _unsupported();
89+
}
90+
91+
@Override
92+
public boolean isContainerType() {
93+
return false;
94+
}
95+
96+
@Override
97+
public String toString() {
98+
return getErasedSignature(new StringBuilder()).toString();
99+
}
100+
101+
@Override
102+
public boolean equals(Object o) {
103+
return (o == this);
104+
}
105+
106+
private <T> T _unsupported() {
107+
throw new UnsupportedOperationException("Operation should not be attempted on "+getClass().getName());
108+
}
109+
}

src/main/java/com/fasterxml/jackson/databind/type/ReferenceType.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class ReferenceType extends SimpleType
2525
* @since 2.8
2626
*/
2727
protected final JavaType _anchorType;
28-
28+
2929
protected ReferenceType(Class<?> cls, TypeBindings bindings,
3030
JavaType superClass, JavaType[] superInts, JavaType refType,
3131
JavaType anchorType,

src/main/java/com/fasterxml/jackson/databind/type/SimpleType.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public JavaType refine(Class<?> rawType, TypeBindings bindings,
211211
// SimpleType means something not-specialized, so:
212212
return null;
213213
}
214-
214+
215215
@Override
216216
protected String buildCanonicalName()
217217
{

src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java

+72-81
Original file line numberDiff line numberDiff line change
@@ -399,102 +399,94 @@ public JavaType constructSpecializedType(JavaType baseType, Class<?> subclass)
399399
newType = _fromClass(null, subclass, TypeBindings.emptyBindings());
400400
break;
401401
}
402-
403-
// If not, we'll need to do more thorough forward+backwards resolution. Sigh.
404-
405-
// 20-Oct-2015, tatu: Container, Map-types somewhat special. There is
406-
// a way to fully resolve and merge hierarchies; but that gets expensive
407-
// so let's, for now, try to create close-enough approximation that
408-
// is not 100% same, structurally, but has equivalent information for
409-
// our specific neeeds.
410-
// 29-Mar-2016, tatu: See [databind#1173] (and test `TypeResolverTest`)
411-
// for a case where this code does get invoked: not ideal
412-
// 29-Jun-2016, tatu: As to bindings, this works for [databind#1215], but
413-
// not certain it would reliably work... but let's hope for best for now
402+
// (4) If all else fails, do the full traversal using placeholders
414403
TypeBindings tb = _bindingsForSubtype(baseType, typeParamCount, subclass);
415-
if (baseType.isInterface()) {
416-
newType = baseType.refine(subclass, tb, null, new JavaType[] { baseType });
417-
} else {
418-
newType = baseType.refine(subclass, tb, baseType, NO_TYPES);
419-
}
420-
// Only SimpleType returns null, but if so just resolve regularly
421-
if (newType == null) {
422-
newType = _fromClass(null, subclass, tb);
423-
}
404+
newType = _fromClass(null, subclass, tb);
405+
424406
} while (false);
425407

426408
// 25-Sep-2016, tatu: As per [databind#1384] also need to ensure handlers get
427409
// copied as well
428410
newType = newType.withHandlersFrom(baseType);
429411
return newType;
412+
}
430413

431-
// 20-Oct-2015, tatu: Old simplistic approach
432-
433-
/*
434-
// Currently mostly SimpleType instances can become something else
435-
if (baseType instanceof SimpleType) {
436-
// and only if subclass is an array, Collection or Map
437-
if (subclass.isArray()
438-
|| Map.class.isAssignableFrom(subclass)
439-
|| Collection.class.isAssignableFrom(subclass)) {
440-
// need to assert type compatibility...
441-
if (!baseType.getRawClass().isAssignableFrom(subclass)) {
442-
throw new IllegalArgumentException("Class "+subclass.getClass().getName()+" not subtype of "+baseType);
443-
}
444-
// this _should_ work, right?
445-
JavaType subtype = _fromClass(null, subclass, TypeBindings.emptyBindings());
446-
// one more thing: handlers to copy?
447-
Object h = baseType.getValueHandler();
448-
if (h != null) {
449-
subtype = subtype.withValueHandler(h);
450-
}
451-
h = baseType.getTypeHandler();
452-
if (h != null) {
453-
subtype = subtype.withTypeHandler(h);
454-
}
455-
return subtype;
456-
}
414+
private TypeBindings _bindingsForSubtype(JavaType baseType, int typeParamCount, Class<?> subclass)
415+
{
416+
PlaceholderForType[] placeholders = new PlaceholderForType[typeParamCount];
417+
for (int i = 0; i < typeParamCount; ++i) {
418+
placeholders[i] = new PlaceholderForType(i);
457419
}
458-
// But there is the need for special case for arrays too, it seems
459-
if (baseType instanceof ArrayType) {
460-
if (subclass.isArray()) {
461-
// actually see if it might be a no-op first:
462-
ArrayType at = (ArrayType) baseType;
463-
Class<?> rawComp = subclass.getComponentType();
464-
if (at.getContentType().getRawClass() == rawComp) {
465-
return baseType;
466-
}
467-
JavaType componentType = _fromAny(null, rawComp, null);
468-
return ((ArrayType) baseType).withComponentType(componentType);
469-
}
420+
TypeBindings b = TypeBindings.create(subclass, placeholders);
421+
// First: pseudo-resolve to get placeholders in place:
422+
JavaType tmpSub = _fromClass(null, subclass, b);
423+
// Then find super-type
424+
JavaType baseWithPlaceholders = tmpSub.findSuperType(baseType.getRawClass());
425+
if (baseWithPlaceholders == null) { // should be found but...
426+
throw new IllegalArgumentException(String.format(
427+
"Internal error: unable to locate supertype (%s) from resolved subtype %s", baseType.getRawClass().getName(),
428+
subclass.getName()));
429+
}
430+
// and traverse type hierarchies to both verify and to resolve placeholders
431+
String error = _resolveTypePlaceholders(baseType, baseWithPlaceholders);
432+
if (error != null) {
433+
throw new IllegalArgumentException("Failed to specialize base type "+baseType.toCanonical()+" as "
434+
+subclass.getName()+", problem: "+error);
470435
}
471436

472-
// otherwise regular narrowing should work just fine
473-
return baseType.narrowBy(subclass);
474-
*/
437+
final JavaType[] typeParams = new JavaType[typeParamCount];
438+
for (int i = 0; i < typeParamCount; ++i) {
439+
JavaType t = placeholders[i].actualType();
440+
// 18-Oct-2017, tatu: Looks like sometimes we have incomplete bindings (even if not
441+
// common, it is possible if subtype is type-erased class with added type
442+
// variable -- see test(s) with "bogus" type(s)).
443+
if (t == null) {
444+
t = unknownType();
445+
}
446+
typeParams[i] = t;
447+
}
448+
return TypeBindings.create(subclass, typeParams);
475449
}
476450

477-
private TypeBindings _bindingsForSubtype(JavaType baseType, int typeParamCount, Class<?> subclass)
451+
private String _resolveTypePlaceholders(JavaType sourceType, JavaType actualType)
452+
throws IllegalArgumentException
478453
{
479-
// But otherwise gets bit tricky, as we need to partially resolve the type hierarchy
480-
// (hopefully passing null Class for root is ok)
481-
int baseCount = baseType.containedTypeCount();
482-
if (baseCount == typeParamCount) {
483-
if (typeParamCount == 1) {
484-
return TypeBindings.create(subclass, baseType.containedType(0));
485-
}
486-
if (typeParamCount == 2) {
487-
return TypeBindings.create(subclass, baseType.containedType(0),
488-
baseType.containedType(1));
454+
List<JavaType> expectedTypes = sourceType.getBindings().getTypeParameters();
455+
List<JavaType> actualTypes = actualType.getBindings().getTypeParameters();
456+
for (int i = 0, len = expectedTypes.size(); i < len; ++i) {
457+
JavaType exp = expectedTypes.get(i);
458+
JavaType act = actualTypes.get(i);
459+
if (!_verifyAndResolvePlaceholders(exp, act)) {
460+
return String.format("Type parameter #%d/%d differs; can not specialize %s with %s",
461+
(i+1), len, exp.toCanonical(), act.toCanonical());
489462
}
490-
List<JavaType> types = new ArrayList<JavaType>(baseCount);
491-
for (int i = 0; i < baseCount; ++i) {
492-
types.add(baseType.containedType(i));
463+
}
464+
return null;
465+
}
466+
467+
private boolean _verifyAndResolvePlaceholders(JavaType exp, JavaType act)
468+
{
469+
// See if we have an actual type placeholder to resolve; if yes, replace
470+
if (act instanceof PlaceholderForType) {
471+
((PlaceholderForType) act).actualType(exp);
472+
return true;
473+
}
474+
// if not, try to verify compatibility. But note that we can not
475+
// use simple equality as we need to resolve recursively
476+
if (exp.getRawClass() != act.getRawClass()) {
477+
return false;
478+
}
479+
// But we can check type parameters "blindly"
480+
List<JavaType> expectedTypes = exp.getBindings().getTypeParameters();
481+
List<JavaType> actualTypes = act.getBindings().getTypeParameters();
482+
for (int i = 0, len = expectedTypes.size(); i < len; ++i) {
483+
JavaType exp2 = expectedTypes.get(i);
484+
JavaType act2 = actualTypes.get(i);
485+
if (!_verifyAndResolvePlaceholders(exp2, act2)) {
486+
return false;
493487
}
494-
return TypeBindings.create(subclass, types);
495488
}
496-
// Otherwise, two choices: match N first, or empty. Do latter, for now
497-
return TypeBindings.emptyBindings();
489+
return true;
498490
}
499491

500492
/**
@@ -1394,13 +1386,12 @@ protected JavaType _fromParamType(ClassStack context, ParameterizedType ptype,
13941386
// always of type Class: if not, need to add more code to resolve it to Class.
13951387
Type[] args = ptype.getActualTypeArguments();
13961388
int paramCount = (args == null) ? 0 : args.length;
1397-
JavaType[] pt;
13981389
TypeBindings newBindings;
13991390

14001391
if (paramCount == 0) {
14011392
newBindings = EMPTY_BINDINGS;
14021393
} else {
1403-
pt = new JavaType[paramCount];
1394+
JavaType[] pt = new JavaType[paramCount];
14041395
for (int i = 0; i < paramCount; ++i) {
14051396
pt[i] = _fromAny(context, args[i], parentBindings);
14061397
}

src/test/java/com/fasterxml/jackson/failing/NestedTypes1604Test.java renamed to src/test/java/com/fasterxml/jackson/databind/type/NestedTypes1604Test.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.type;
22

33
import java.util.ArrayList;
44
import java.util.List;

0 commit comments

Comments
 (0)