Skip to content

Commit b6eff91

Browse files
committed
Fixed #3009
1 parent 0ad3ff7 commit b6eff91

File tree

3 files changed

+124
-7
lines changed

3 files changed

+124
-7
lines changed

release-notes/VERSION-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Project: jackson-databind
2020
#2990: Breaking API change in `BasicClassIntrospector` (2.12.0)
2121
(reported, fix contributed by Faron D)
2222
#3005: `JsonNode.requiredAt()` does NOT fail on some path expressions
23+
#3009: Exception thrown when `Collections.synchronizedList()` is serialized
24+
with type info, deserialized
25+
(reported by pcloves@github)
2326

2427
2.12.0 (29-Nov-2020)
2528

src/main/java/com/fasterxml/jackson/databind/deser/impl/JavaUtilCollectionsDeserializers.java

+56-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
import java.util.*;
44

5-
import com.fasterxml.jackson.databind.DeserializationContext;
6-
import com.fasterxml.jackson.databind.JavaType;
7-
import com.fasterxml.jackson.databind.JsonDeserializer;
8-
import com.fasterxml.jackson.databind.JsonMappingException;
5+
import com.fasterxml.jackson.databind.*;
96
import com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer;
107
import com.fasterxml.jackson.databind.type.TypeFactory;
118
import com.fasterxml.jackson.databind.util.Converter;
@@ -28,7 +25,15 @@ public abstract class JavaUtilCollectionsDeserializers
2825
private final static int TYPE_UNMODIFIABLE_LIST = 5;
2926
private final static int TYPE_UNMODIFIABLE_MAP = 6;
3027

31-
public final static int TYPE_AS_LIST = 7;
28+
// 2.12.1
29+
private final static int TYPE_SYNC_SET = 7;
30+
private final static int TYPE_SYNC_COLLECTION = 8;
31+
private final static int TYPE_SYNC_LIST = 9;
32+
private final static int TYPE_SYNC_MAP = 10;
33+
34+
public final static int TYPE_AS_LIST = 11;
35+
36+
private final static String PREFIX_JAVA_UTIL_COLLECTIONS = "java.util.Collections$";
3237

3338
// 10-Jan-2018, tatu: There are a few "well-known" special containers in JDK too:
3439

@@ -67,6 +72,7 @@ public static JsonDeserializer<?> findForCollection(DeserializationContext ctxt,
6772
throws JsonMappingException
6873
{
6974
JavaUtilCollectionsConverter conv;
75+
7076
// 10-Jan-2017, tatu: Some types from `java.util.Collections`/`java.util.Arrays` need bit of help...
7177
if (type.hasRawClass(CLASS_AS_ARRAYS_LIST)) {
7278
conv = converter(TYPE_AS_LIST, type, List.class);
@@ -80,7 +86,17 @@ public static JsonDeserializer<?> findForCollection(DeserializationContext ctxt,
8086
} else if (type.hasRawClass(CLASS_UNMODIFIABLE_SET)) {
8187
conv = converter(TYPE_UNMODIFIABLE_SET, type, Set.class);
8288
} else {
83-
return null;
89+
final String utilName = _findUtilSyncTypeName(type.getRawClass());
90+
// [databind#3009]: synchronized, too
91+
if (utilName.endsWith("Set")) {
92+
conv = converter(TYPE_SYNC_SET, type, Set.class);
93+
} else if (utilName.endsWith("List")) {
94+
conv = converter(TYPE_SYNC_LIST, type, List.class);
95+
} else if (utilName.endsWith("Collection")) {
96+
conv = converter(TYPE_SYNC_COLLECTION, type, Collection.class);
97+
} else {
98+
return null;
99+
}
84100
}
85101
return new StdDelegatingDeserializer<Object>(conv);
86102
}
@@ -97,7 +113,13 @@ public static JsonDeserializer<?> findForMap(DeserializationContext ctxt,
97113
} else if (type.hasRawClass(CLASS_UNMODIFIABLE_MAP)) {
98114
conv = converter(TYPE_UNMODIFIABLE_MAP, type, Map.class);
99115
} else {
100-
return null;
116+
final String utilName = _findUtilSyncTypeName(type.getRawClass());
117+
// [databind#3009]: synchronized, too
118+
if (utilName.endsWith("Map")) {
119+
conv = converter(TYPE_SYNC_MAP, type, Map.class);
120+
} else {
121+
return null;
122+
}
101123
}
102124
return new StdDelegatingDeserializer<Object>(conv);
103125
}
@@ -108,6 +130,24 @@ static JavaUtilCollectionsConverter converter(int kind,
108130
return new JavaUtilCollectionsConverter(kind, concreteType.findSuperType(rawSuper));
109131
}
110132

133+
private static String _findUtilSyncTypeName(Class<?> raw) {
134+
String clsName = _findUtilCollectionsTypeName(raw);
135+
if (clsName != null) {
136+
if (clsName.startsWith("Synchronized")) {
137+
return clsName.substring(12);
138+
}
139+
}
140+
return "";
141+
}
142+
143+
private static String _findUtilCollectionsTypeName(Class<?> raw) {
144+
final String clsName = raw.getName();
145+
if (clsName.startsWith(PREFIX_JAVA_UTIL_COLLECTIONS)) {
146+
return clsName.substring(PREFIX_JAVA_UTIL_COLLECTIONS.length());
147+
}
148+
return "";
149+
}
150+
111151
/**
112152
* Implementation used for converting from various generic container
113153
* types ({@link java.util.Set}, {@link java.util.List}, {@link java.util.Map})
@@ -158,6 +198,15 @@ public Object convert(Object value) {
158198
case TYPE_UNMODIFIABLE_MAP:
159199
return Collections.unmodifiableMap((Map<?,?>) value);
160200

201+
case TYPE_SYNC_SET:
202+
return Collections.synchronizedSet((Set<?>) value);
203+
case TYPE_SYNC_LIST:
204+
return Collections.synchronizedList((List<?>) value);
205+
case TYPE_SYNC_COLLECTION:
206+
return Collections.synchronizedCollection((Collection<?>) value);
207+
case TYPE_SYNC_MAP:
208+
return Collections.synchronizedMap((Map<?,?>) value);
209+
161210
case TYPE_AS_LIST:
162211
default:
163212
// Here we do not actually care about impl type, just return List as-is:

src/test/java/com/fasterxml/jackson/databind/deser/jdk/UtilCollectionsTypesTest.java

+65
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,60 @@ public void testUnmodifiableMap() throws Exception
9191
_verifyMap(Collections.unmodifiableMap(input));
9292
}
9393

94+
/*
95+
/**********************************************************
96+
/* Unit tests, "synchronized" types, [databind#3009]
97+
/**********************************************************
98+
*/
99+
100+
// [databind#3009]
101+
public void testSynchronizedCollection() throws Exception
102+
{
103+
// 07-Jan-2021, tatu: Some oddities, need to inline checking:
104+
final Collection<?> input = Collections.synchronizedCollection(
105+
Arrays.asList("first", "second"));
106+
final Collection<?> output = _writeReadCollection(input);
107+
final Class<?> actType = output.getClass();
108+
if (!Collection.class.isAssignableFrom(actType)) {
109+
fail("Should be subtype of java.util.Collection, is: "+actType.getName());
110+
}
111+
112+
// And for some reason iteration order varies or something: direct equality
113+
// check fails, so simply check contents:
114+
assertEquals(input.size(), output.size());
115+
assertEquals(new ArrayList<>(input),
116+
new ArrayList<>(output));
117+
}
118+
119+
// [databind#3009]
120+
public void testSynchronizedSet() throws Exception {
121+
Set<String> input = new LinkedHashSet<>(Arrays.asList("first", "second"));
122+
_verifyCollection(Collections.synchronizedSet(input));
123+
}
124+
125+
// [databind#3009]
126+
public void testSynchronizedListRandomAccess() throws Exception {
127+
_verifyCollection(Collections.synchronizedList(
128+
Arrays.asList("first", "second")));
129+
}
130+
131+
// [databind#3009]
132+
public void testSynchronizedListLinked() throws Exception {
133+
final List<String> linked = new LinkedList<>();
134+
linked.add("first");
135+
linked.add("second");
136+
_verifyApproxCollection(Collections.synchronizedList(linked),
137+
List.class);
138+
}
139+
140+
// [databind#3009]
141+
public void testSynchronizedMap() throws Exception {
142+
Map<String,String> input = new LinkedHashMap<>();
143+
input.put("a", "b");
144+
input.put("c", "d");
145+
_verifyMap(Collections.synchronizedMap(input));
146+
}
147+
94148
/*
95149
/**********************************************************
96150
/* Unit tests, other
@@ -119,6 +173,17 @@ protected void _verifyCollection(Collection<?> exp) throws Exception {
119173
assertEquals(exp.getClass(), act.getClass());
120174
}
121175

176+
protected void _verifyApproxCollection(Collection<?> exp,
177+
Class<?> expType) throws Exception
178+
{
179+
Collection<?> act = _writeReadCollection(exp);
180+
assertEquals(exp, act);
181+
final Class<?> actType = act.getClass();
182+
if (!expType.isAssignableFrom(actType)) {
183+
fail("Should be subtype of "+expType.getName()+", is: "+actType.getName());
184+
}
185+
}
186+
122187
protected Collection<?> _writeReadCollection(Collection<?> input) throws Exception {
123188
final String json = DEFAULT_MAPPER.writeValueAsString(input);
124189
return DEFAULT_MAPPER.readValue(json, Collection.class);

0 commit comments

Comments
 (0)