Skip to content

Commit

Permalink
Fixed #3009
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jan 8, 2021
1 parent 0ad3ff7 commit b6eff91
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 7 deletions.
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Project: jackson-databind
#2990: Breaking API change in `BasicClassIntrospector` (2.12.0)
(reported, fix contributed by Faron D)
#3005: `JsonNode.requiredAt()` does NOT fail on some path expressions
#3009: Exception thrown when `Collections.synchronizedList()` is serialized
with type info, deserialized
(reported by pcloves@github)

2.12.0 (29-Nov-2020)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import java.util.*;

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

public final static int TYPE_AS_LIST = 7;
// 2.12.1
private final static int TYPE_SYNC_SET = 7;
private final static int TYPE_SYNC_COLLECTION = 8;
private final static int TYPE_SYNC_LIST = 9;
private final static int TYPE_SYNC_MAP = 10;

public final static int TYPE_AS_LIST = 11;

private final static String PREFIX_JAVA_UTIL_COLLECTIONS = "java.util.Collections$";

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

Expand Down Expand Up @@ -67,6 +72,7 @@ public static JsonDeserializer<?> findForCollection(DeserializationContext ctxt,
throws JsonMappingException
{
JavaUtilCollectionsConverter conv;

// 10-Jan-2017, tatu: Some types from `java.util.Collections`/`java.util.Arrays` need bit of help...
if (type.hasRawClass(CLASS_AS_ARRAYS_LIST)) {
conv = converter(TYPE_AS_LIST, type, List.class);
Expand All @@ -80,7 +86,17 @@ public static JsonDeserializer<?> findForCollection(DeserializationContext ctxt,
} else if (type.hasRawClass(CLASS_UNMODIFIABLE_SET)) {
conv = converter(TYPE_UNMODIFIABLE_SET, type, Set.class);
} else {
return null;
final String utilName = _findUtilSyncTypeName(type.getRawClass());
// [databind#3009]: synchronized, too
if (utilName.endsWith("Set")) {
conv = converter(TYPE_SYNC_SET, type, Set.class);
} else if (utilName.endsWith("List")) {
conv = converter(TYPE_SYNC_LIST, type, List.class);
} else if (utilName.endsWith("Collection")) {
conv = converter(TYPE_SYNC_COLLECTION, type, Collection.class);
} else {
return null;
}
}
return new StdDelegatingDeserializer<Object>(conv);
}
Expand All @@ -97,7 +113,13 @@ public static JsonDeserializer<?> findForMap(DeserializationContext ctxt,
} else if (type.hasRawClass(CLASS_UNMODIFIABLE_MAP)) {
conv = converter(TYPE_UNMODIFIABLE_MAP, type, Map.class);
} else {
return null;
final String utilName = _findUtilSyncTypeName(type.getRawClass());
// [databind#3009]: synchronized, too
if (utilName.endsWith("Map")) {
conv = converter(TYPE_SYNC_MAP, type, Map.class);
} else {
return null;
}
}
return new StdDelegatingDeserializer<Object>(conv);
}
Expand All @@ -108,6 +130,24 @@ static JavaUtilCollectionsConverter converter(int kind,
return new JavaUtilCollectionsConverter(kind, concreteType.findSuperType(rawSuper));
}

private static String _findUtilSyncTypeName(Class<?> raw) {
String clsName = _findUtilCollectionsTypeName(raw);
if (clsName != null) {
if (clsName.startsWith("Synchronized")) {
return clsName.substring(12);
}
}
return "";
}

private static String _findUtilCollectionsTypeName(Class<?> raw) {
final String clsName = raw.getName();
if (clsName.startsWith(PREFIX_JAVA_UTIL_COLLECTIONS)) {
return clsName.substring(PREFIX_JAVA_UTIL_COLLECTIONS.length());
}
return "";
}

/**
* Implementation used for converting from various generic container
* types ({@link java.util.Set}, {@link java.util.List}, {@link java.util.Map})
Expand Down Expand Up @@ -158,6 +198,15 @@ public Object convert(Object value) {
case TYPE_UNMODIFIABLE_MAP:
return Collections.unmodifiableMap((Map<?,?>) value);

case TYPE_SYNC_SET:
return Collections.synchronizedSet((Set<?>) value);
case TYPE_SYNC_LIST:
return Collections.synchronizedList((List<?>) value);
case TYPE_SYNC_COLLECTION:
return Collections.synchronizedCollection((Collection<?>) value);
case TYPE_SYNC_MAP:
return Collections.synchronizedMap((Map<?,?>) value);

case TYPE_AS_LIST:
default:
// Here we do not actually care about impl type, just return List as-is:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,60 @@ public void testUnmodifiableMap() throws Exception
_verifyMap(Collections.unmodifiableMap(input));
}

/*
/**********************************************************
/* Unit tests, "synchronized" types, [databind#3009]
/**********************************************************
*/

// [databind#3009]
public void testSynchronizedCollection() throws Exception
{
// 07-Jan-2021, tatu: Some oddities, need to inline checking:
final Collection<?> input = Collections.synchronizedCollection(
Arrays.asList("first", "second"));
final Collection<?> output = _writeReadCollection(input);
final Class<?> actType = output.getClass();
if (!Collection.class.isAssignableFrom(actType)) {
fail("Should be subtype of java.util.Collection, is: "+actType.getName());
}

// And for some reason iteration order varies or something: direct equality
// check fails, so simply check contents:
assertEquals(input.size(), output.size());
assertEquals(new ArrayList<>(input),
new ArrayList<>(output));
}

// [databind#3009]
public void testSynchronizedSet() throws Exception {
Set<String> input = new LinkedHashSet<>(Arrays.asList("first", "second"));
_verifyCollection(Collections.synchronizedSet(input));
}

// [databind#3009]
public void testSynchronizedListRandomAccess() throws Exception {
_verifyCollection(Collections.synchronizedList(
Arrays.asList("first", "second")));
}

// [databind#3009]
public void testSynchronizedListLinked() throws Exception {
final List<String> linked = new LinkedList<>();
linked.add("first");
linked.add("second");
_verifyApproxCollection(Collections.synchronizedList(linked),
List.class);
}

// [databind#3009]
public void testSynchronizedMap() throws Exception {
Map<String,String> input = new LinkedHashMap<>();
input.put("a", "b");
input.put("c", "d");
_verifyMap(Collections.synchronizedMap(input));
}

/*
/**********************************************************
/* Unit tests, other
Expand Down Expand Up @@ -119,6 +173,17 @@ protected void _verifyCollection(Collection<?> exp) throws Exception {
assertEquals(exp.getClass(), act.getClass());
}

protected void _verifyApproxCollection(Collection<?> exp,
Class<?> expType) throws Exception
{
Collection<?> act = _writeReadCollection(exp);
assertEquals(exp, act);
final Class<?> actType = act.getClass();
if (!expType.isAssignableFrom(actType)) {
fail("Should be subtype of "+expType.getName()+", is: "+actType.getName());
}
}

protected Collection<?> _writeReadCollection(Collection<?> input) throws Exception {
final String json = DEFAULT_MAPPER.writeValueAsString(input);
return DEFAULT_MAPPER.readValue(json, Collection.class);
Expand Down

0 comments on commit b6eff91

Please sign in to comment.