Skip to content

Commit 8865f1b

Browse files
committed
Merge branch '2.12' into 2.13
2 parents 2afa5c9 + 4e7cd9a commit 8865f1b

File tree

4 files changed

+176
-4
lines changed

4 files changed

+176
-4
lines changed

release-notes/CREDITS-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,9 @@ Marcos Passos (marcospassos@github(
977977
(2.10.0)
978978
* Reported #2795: Cannot detect creator arguments of mixins for JDK types
979979
(2.11.3)
980+
* Reported #3220: (regression) Factory method generic type resolution does not use
981+
Class-bound type parameter
982+
(2.12.5)
980983
981984
David Becker (dsbecker@github)
982985
* Suggested #2433: Improve `NullNode.equals()`

release-notes/VERSION-2.x

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ Project: jackson-databind
6969
via `AsNull`
7070
- Add `mvnw` wrapper
7171

72+
2.12.5 (not yet released)
73+
74+
#3220: (regression) Factory method generic type resolution does not use
75+
Class-bound type parameter
76+
(reported by Marcos P)
77+
7278
2.12.4 (06-Jul-2021)
7379

7480
#3139: Deserialization of "empty" subtype with DEDUCTION failed

src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedCreatorCollector.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,14 @@ private List<AnnotatedMethod> _findPotentialFactories(TypeFactory typeFactory,
219219
// 27-Oct-2020, tatu: SIGH. As per [databind#2894] there is widespread use of
220220
// incorrect bindings in the wild -- not supported (no tests) but used
221221
// nonetheless. So, for 2.11.x, put back "Bad Bindings"...
222-
// final TypeResolutionContext typeResCtxt = _typeContext;
222+
//
223223

224224
// 03-Nov-2020, ckozak: Implement generic JsonCreator TypeVariable handling [databind#2895]
225-
final TypeResolutionContext emptyTypeResCtxt = new TypeResolutionContext.Empty(typeFactory);
225+
// final TypeResolutionContext emptyTypeResCtxt = new TypeResolutionContext.Empty(typeFactory);
226+
227+
// 23-Aug-2021, tatu: Double-d'oh! As per [databind#3220], we must revert to
228+
// the Incorrect Illogical But Used By Users Bindings... for 2.x at least.
229+
final TypeResolutionContext initialTypeResCtxt = _typeContext;
226230

227231
int factoryCount = candidates.size();
228232
List<AnnotatedMethod> result = new ArrayList<>(factoryCount);
@@ -247,7 +251,7 @@ private List<AnnotatedMethod> _findPotentialFactories(TypeFactory typeFactory,
247251
if (key.equals(methodKeys[i])) {
248252
result.set(i,
249253
constructFactoryCreator(candidates.get(i),
250-
emptyTypeResCtxt, mixinFactory));
254+
initialTypeResCtxt, mixinFactory));
251255
break;
252256
}
253257
}
@@ -261,8 +265,9 @@ private List<AnnotatedMethod> _findPotentialFactories(TypeFactory typeFactory,
261265
// 06-Nov-2020, tatu: Fix from [databind#2895] will try to resolve
262266
// nominal static method type bindings into expected target type
263267
// (if generic types involved)
268+
// 23-Aug-2021, tatu: ... is this still needed, wrt un-fix in [databind#3220]?
264269
TypeResolutionContext typeResCtxt = MethodGenericTypeResolver.narrowMethodTypeParameters(
265-
candidate, type, typeFactory, emptyTypeResCtxt);
270+
candidate, type, typeFactory, initialTypeResCtxt);
266271
result.set(i,
267272
constructFactoryCreator(candidate, typeResCtxt, null));
268273
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package com.fasterxml.jackson.databind.mixins;
2+
3+
import java.util.Objects;
4+
5+
import com.fasterxml.jackson.annotation.*;
6+
7+
import com.fasterxml.jackson.databind.BaseMapTest;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.json.JsonMapper;
10+
11+
// 23-Aug-2021, tatu: UGGGGH. This is a recurring problem; users assuming
12+
// that a generic type parameter T (or whatever) of a Class will be related
13+
// to locally declared type parameter with same name (T), assume Creator
14+
// factory method will magically work. Despite Java Language quite clearly
15+
// considering these separate bindings regardless of name overlap.
16+
//
17+
// But for Jackson 2.x there is common enough usage of this anti-patterns so
18+
// we cannot block it.
19+
//
20+
// NOTE! Problem is actually not the mixin handling but type resolution;
21+
// see f.ex [databind#2821], [databind#2895]
22+
public class MixinForFactoryMethod3220Test
23+
extends BaseMapTest
24+
{
25+
// [databind#3220]
26+
static class Timestamped<T> {
27+
private final T value;
28+
private final int timestamp;
29+
30+
Timestamped(T value, int timestamp) {
31+
this.value = value;
32+
this.timestamp = timestamp;
33+
}
34+
35+
public static <T> Timestamped<T> stamp(T value, int timestamp) {
36+
return new Timestamped<>(value, timestamp);
37+
}
38+
39+
public T getValue() {
40+
return value;
41+
}
42+
43+
public int getTimestamp() {
44+
return timestamp;
45+
}
46+
47+
@Override
48+
public boolean equals(Object o) {
49+
if (this == o) return true;
50+
if (o == null || getClass() != o.getClass()) return false;
51+
Timestamped<?> that = (Timestamped<?>) o;
52+
return timestamp == that.timestamp && Objects.equals(value, that.value);
53+
}
54+
55+
@Override
56+
public int hashCode() {
57+
return Objects.hash(value, timestamp);
58+
}
59+
}
60+
61+
abstract static class TimestampedMixin<T> {
62+
@JsonCreator
63+
public static <T> void stamp(
64+
@JsonProperty("value") T value,
65+
@JsonProperty("timestamp") int timestamp) {
66+
}
67+
68+
@JsonGetter("value")
69+
abstract T getValue();
70+
71+
@JsonGetter("timestamp")
72+
abstract int getTimestamp();
73+
}
74+
75+
static class Profile {
76+
private final String firstName;
77+
private final String lastName;
78+
79+
@JsonCreator
80+
public Profile(@JsonProperty("firstName") String firstName,
81+
@JsonProperty("lastName") String lastName
82+
) {
83+
this.firstName = firstName;
84+
this.lastName = lastName;
85+
}
86+
87+
@JsonGetter("firstName")
88+
public String getFirstName() {
89+
return firstName;
90+
}
91+
92+
@JsonGetter("lastName")
93+
public String getLastName() {
94+
return lastName;
95+
}
96+
97+
@Override
98+
public boolean equals(Object o) {
99+
if (this == o) return true;
100+
if (o == null || getClass() != o.getClass()) return false;
101+
Profile profile = (Profile) o;
102+
return Objects.equals(firstName, profile.firstName) && Objects.equals(lastName, profile.lastName);
103+
}
104+
105+
@Override
106+
public int hashCode() {
107+
return Objects.hash(firstName, lastName);
108+
}
109+
}
110+
111+
static class User {
112+
private final Timestamped<Profile> profile;
113+
114+
@JsonCreator
115+
User(@JsonProperty("profile") Timestamped<Profile> profile) {
116+
this.profile = profile;
117+
}
118+
119+
@JsonGetter("profile")
120+
public Timestamped<Profile> getProfile() {
121+
return profile;
122+
}
123+
124+
@Override
125+
public boolean equals(Object o) {
126+
if (this == o) return true;
127+
if (o == null || getClass() != o.getClass()) return false;
128+
User user = (User) o;
129+
return Objects.equals(profile, user.profile);
130+
}
131+
132+
@Override
133+
public int hashCode() {
134+
return Objects.hash(profile);
135+
}
136+
}
137+
138+
// [databind#3220]
139+
public void testMixin3220() throws Exception
140+
{
141+
ObjectMapper mapper = JsonMapper.builder()
142+
.addMixIn(Timestamped.class, TimestampedMixin.class)
143+
.build();
144+
145+
Profile profile = new Profile("Jackson", "Databind");
146+
User user = new User(new Timestamped<>(profile, 1));
147+
148+
User deserializedUser = mapper.readValue(
149+
mapper.writerFor(User.class).writeValueAsString(user),
150+
User.class
151+
);
152+
153+
Timestamped<Profile> td = deserializedUser.getProfile();
154+
Profile deserializedProfile = td.getValue();
155+
assertEquals(profile, deserializedProfile);
156+
assertEquals(user, deserializedUser);
157+
}
158+
}

0 commit comments

Comments
 (0)