| package com.fasterxml.jackson.databind.type; |
| |
| import java.lang.reflect.Field; |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import com.fasterxml.jackson.core.type.TypeReference; |
| import com.fasterxml.jackson.databind.*; |
| |
| /** |
| * Simple tests to verify that the {@link TypeFactory} constructs |
| * type information as expected. |
| */ |
| public class TestTypeFactory |
| extends BaseMapTest |
| { |
| /* |
| /********************************************************** |
| /* Helper types |
| /********************************************************** |
| */ |
| |
| enum EnumForCanonical { YES, NO; } |
| |
| static class SingleArgGeneric<X> { } |
| |
| abstract static class MyMap extends IntermediateMap<String,Long> { } |
| abstract static class IntermediateMap<K,V> implements Map<K,V> { } |
| |
| abstract static class MyList extends IntermediateList<Long> { } |
| abstract static class IntermediateList<E> implements List<E> { } |
| |
| @SuppressWarnings("serial") |
| static class GenericList<T> extends ArrayList<T> { } |
| |
| interface MapInterface extends Cloneable, IntermediateInterfaceMap<String> { } |
| interface IntermediateInterfaceMap<FOO> extends Map<FOO, Integer> { } |
| |
| @SuppressWarnings("serial") |
| static class MyStringIntMap extends MyStringXMap<Integer> { } |
| @SuppressWarnings("serial") |
| static class MyStringXMap<V> extends HashMap<String,V> { } |
| |
| // And one more, now with obfuscated type names; essentially it's just Map<Int,Long> |
| static abstract class IntLongMap extends XLongMap<Integer> { } |
| // trick here is that V now refers to key type, not value type |
| static abstract class XLongMap<V> extends XXMap<V,Long> { } |
| static abstract class XXMap<K,V> implements Map<K,V> { } |
| |
| static class SneakyBean { |
| public IntLongMap intMap; |
| public MyList longList; |
| } |
| |
| static class SneakyBean2 { |
| // self-reference; should be resolved as "Comparable<Object>" |
| public <T extends Comparable<T>> T getFoobar() { return null; } |
| } |
| |
| @SuppressWarnings("serial") |
| public static class LongValuedMap<K> extends HashMap<K, Long> { } |
| |
| static class StringLongMapBean { |
| public LongValuedMap<String> value; |
| } |
| |
| static class StringListBean { |
| public GenericList<String> value; |
| } |
| |
| static class CollectionLike<E> { } |
| static class MapLike<K,V> { } |
| |
| static class Wrapper1297<T> { |
| public T content; |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests |
| /********************************************************** |
| */ |
| |
| public void testSimpleTypes() |
| { |
| Class<?>[] classes = new Class<?>[] { |
| boolean.class, byte.class, char.class, |
| short.class, int.class, long.class, |
| float.class, double.class, |
| |
| Boolean.class, Byte.class, Character.class, |
| Short.class, Integer.class, Long.class, |
| Float.class, Double.class, |
| |
| String.class, |
| Object.class, |
| |
| Calendar.class, |
| Date.class, |
| }; |
| |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| for (Class<?> clz : classes) { |
| assertSame(clz, tf.constructType(clz).getRawClass()); |
| assertSame(clz, tf.constructType(clz).getRawClass()); |
| } |
| } |
| |
| public void testArrays() |
| { |
| Class<?>[] classes = new Class<?>[] { |
| boolean[].class, byte[].class, char[].class, |
| short[].class, int[].class, long[].class, |
| float[].class, double[].class, |
| |
| String[].class, Object[].class, |
| Calendar[].class, |
| }; |
| |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| for (Class<?> clz : classes) { |
| assertSame(clz, tf.constructType(clz).getRawClass()); |
| Class<?> elemType = clz.getComponentType(); |
| assertSame(clz, tf.constructArrayType(elemType).getRawClass()); |
| } |
| } |
| |
| // [databind#810]: Fake Map type for Properties as <String,String> |
| public void testProperties() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType t = tf.constructType(Properties.class); |
| assertEquals(MapType.class, t.getClass()); |
| assertSame(Properties.class, t.getRawClass()); |
| |
| MapType mt = (MapType) t; |
| |
| // so far so good. But how about parameterization? |
| assertSame(String.class, mt.getKeyType().getRawClass()); |
| assertSame(String.class, mt.getContentType().getRawClass()); |
| } |
| |
| public void testIterator() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType t = tf.constructType(new TypeReference<Iterator<String>>() { }); |
| assertEquals(SimpleType.class, t.getClass()); |
| assertSame(Iterator.class, t.getRawClass()); |
| assertEquals(1, t.containedTypeCount()); |
| assertEquals(tf.constructType(String.class), t.containedType(0)); |
| assertNull(t.containedType(1)); |
| } |
| |
| /** |
| * Test for verifying that parametric types can be constructed |
| * programmatically |
| */ |
| @SuppressWarnings("deprecation") |
| public void testParametricTypes() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| // first, simple class based |
| JavaType t = tf.constructParametrizedType(ArrayList.class, Collection.class, String.class); // ArrayList<String> |
| assertEquals(CollectionType.class, t.getClass()); |
| JavaType strC = tf.constructType(String.class); |
| assertEquals(1, t.containedTypeCount()); |
| assertEquals(strC, t.containedType(0)); |
| assertNull(t.containedType(1)); |
| |
| // Then using JavaType |
| JavaType t2 = tf.constructParametrizedType(Map.class, Map.class, strC, t); // Map<String,ArrayList<String>> |
| // should actually produce a MapType |
| assertEquals(MapType.class, t2.getClass()); |
| assertEquals(2, t2.containedTypeCount()); |
| assertEquals(strC, t2.containedType(0)); |
| assertEquals(t, t2.containedType(1)); |
| assertNull(t2.containedType(2)); |
| |
| // and then custom generic type as well |
| JavaType custom = tf.constructParametrizedType(SingleArgGeneric.class, SingleArgGeneric.class, |
| String.class); |
| assertEquals(SimpleType.class, custom.getClass()); |
| assertEquals(1, custom.containedTypeCount()); |
| assertEquals(strC, custom.containedType(0)); |
| assertNull(custom.containedType(1)); |
| |
| // should also be able to access variable name: |
| assertEquals("X", custom.containedTypeName(0)); |
| |
| // And finally, ensure that we can't create invalid combinations |
| try { |
| // Maps must take 2 type parameters, not just one |
| tf.constructParametrizedType(Map.class, Map.class, strC); |
| } catch (IllegalArgumentException e) { |
| verifyException(e, "Can not create TypeBindings for class java.util.Map"); |
| } |
| |
| try { |
| // Type only accepts one type param |
| tf.constructParametrizedType(SingleArgGeneric.class, SingleArgGeneric.class, strC, strC); |
| } catch (IllegalArgumentException e) { |
| verifyException(e, "Can not create TypeBindings for class "); |
| } |
| } |
| |
| /** |
| * Test for checking that canonical name handling works ok |
| */ |
| public void testCanonicalNames() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType t = tf.constructType(java.util.Calendar.class); |
| String can = t.toCanonical(); |
| assertEquals("java.util.Calendar", can); |
| assertEquals(t, tf.constructFromCanonical(can)); |
| |
| // Generic maps and collections will default to Object.class if type-erased |
| t = tf.constructType(java.util.ArrayList.class); |
| can = t.toCanonical(); |
| assertEquals("java.util.ArrayList<java.lang.Object>", can); |
| assertEquals(t, tf.constructFromCanonical(can)); |
| |
| t = tf.constructType(java.util.TreeMap.class); |
| can = t.toCanonical(); |
| assertEquals("java.util.TreeMap<java.lang.Object,java.lang.Object>", can); |
| assertEquals(t, tf.constructFromCanonical(can)); |
| |
| // And then EnumMap (actual use case for us) |
| t = tf.constructMapType(EnumMap.class, EnumForCanonical.class, String.class); |
| can = t.toCanonical(); |
| assertEquals("java.util.EnumMap<com.fasterxml.jackson.databind.type.TestTypeFactory$EnumForCanonical,java.lang.String>", |
| can); |
| assertEquals(t, tf.constructFromCanonical(can)); |
| |
| // [databind#2109]: also ReferenceTypes |
| t = tf.constructType(new TypeReference<AtomicReference<Long>>() { }); |
| can = t.toCanonical(); |
| assertEquals("java.util.concurrent.atomic.AtomicReference<java.lang.Long>", |
| can); |
| assertEquals(t, tf.constructFromCanonical(can)); |
| |
| // [databind#1941]: allow "raw" types too |
| t = tf.constructFromCanonical("java.util.List"); |
| assertEquals(List.class, t.getRawClass()); |
| assertEquals(CollectionType.class, t.getClass()); |
| // 01-Mar-2018, tatu: not 100% should we expect type parameters here... |
| // But currently we do NOT get any |
| /* |
| assertEquals(1, t.containedTypeCount()); |
| assertEquals(Object.class, t.containedType(0).getRawClass()); |
| */ |
| assertEquals(Object.class, t.getContentType().getRawClass()); |
| can = t.toCanonical(); |
| assertEquals("java.util.List<java.lang.Object>", can); |
| assertEquals(t, tf.constructFromCanonical(can)); |
| } |
| |
| // [databind#1768] |
| @SuppressWarnings("serial") |
| public void testCanonicalWithSpaces() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| Object objects = new TreeMap<Object, Object>() { }; // to get subtype |
| String reflectTypeName = objects.getClass().getGenericSuperclass().toString(); |
| JavaType t1 = tf.constructType(objects.getClass().getGenericSuperclass()); |
| // This will throw an Exception if you don't remove all white spaces from the String. |
| JavaType t2 = tf.constructFromCanonical(reflectTypeName); |
| assertNotNull(t2); |
| assertEquals(t2, t1); |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests: collection type parameter resolution |
| /********************************************************** |
| */ |
| |
| public void testCollections() |
| { |
| // Ok, first: let's test what happens when we pass 'raw' Collection: |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType t = tf.constructType(ArrayList.class); |
| assertEquals(CollectionType.class, t.getClass()); |
| assertSame(ArrayList.class, t.getRawClass()); |
| |
| // And then the proper way |
| t = tf.constructType(new TypeReference<ArrayList<String>>() { }); |
| assertEquals(CollectionType.class, t.getClass()); |
| assertSame(ArrayList.class, t.getRawClass()); |
| |
| JavaType elemType = ((CollectionType) t).getContentType(); |
| assertNotNull(elemType); |
| assertSame(SimpleType.class, elemType.getClass()); |
| assertSame(String.class, elemType.getRawClass()); |
| |
| // And alternate method too |
| t = tf.constructCollectionType(ArrayList.class, String.class); |
| assertEquals(CollectionType.class, t.getClass()); |
| assertSame(String.class, ((CollectionType) t).getContentType().getRawClass()); |
| } |
| |
| // since 2.7 |
| public void testCollectionTypesRefined() |
| { |
| TypeFactory tf = newTypeFactory(); |
| JavaType type = tf.constructType(new TypeReference<List<Long>>() { }); |
| assertEquals(List.class, type.getRawClass()); |
| assertEquals(Long.class, type.getContentType().getRawClass()); |
| // No super-class, since it's an interface: |
| assertNull(type.getSuperClass()); |
| |
| // But then refine to reflect sub-classing |
| JavaType subtype = tf.constructSpecializedType(type, ArrayList.class); |
| assertEquals(ArrayList.class, subtype.getRawClass()); |
| assertEquals(Long.class, subtype.getContentType().getRawClass()); |
| |
| // but with refinement, should have non-null super class |
| JavaType superType = subtype.getSuperClass(); |
| assertNotNull(superType); |
| assertEquals(AbstractList.class, superType.getRawClass()); |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests: map type parameter resolution |
| /********************************************************** |
| */ |
| |
| public void testMaps() |
| { |
| TypeFactory tf = newTypeFactory(); |
| |
| // Ok, first: let's test what happens when we pass 'raw' Map: |
| JavaType t = tf.constructType(HashMap.class); |
| assertEquals(MapType.class, t.getClass()); |
| assertSame(HashMap.class, t.getRawClass()); |
| |
| // Then explicit construction |
| t = tf.constructMapType(TreeMap.class, String.class, Integer.class); |
| assertEquals(MapType.class, t.getClass()); |
| assertSame(String.class, ((MapType) t).getKeyType().getRawClass()); |
| assertSame(Integer.class, ((MapType) t).getContentType().getRawClass()); |
| |
| // And then with TypeReference |
| t = tf.constructType(new TypeReference<HashMap<String,Integer>>() { }); |
| assertEquals(MapType.class, t.getClass()); |
| assertSame(HashMap.class, t.getRawClass()); |
| MapType mt = (MapType) t; |
| assertEquals(tf.constructType(String.class), mt.getKeyType()); |
| assertEquals(tf.constructType(Integer.class), mt.getContentType()); |
| |
| t = tf.constructType(new TypeReference<LongValuedMap<Boolean>>() { }); |
| assertEquals(MapType.class, t.getClass()); |
| assertSame(LongValuedMap.class, t.getRawClass()); |
| mt = (MapType) t; |
| assertEquals(tf.constructType(Boolean.class), mt.getKeyType()); |
| assertEquals(tf.constructType(Long.class), mt.getContentType()); |
| |
| JavaType type = tf.constructType(new TypeReference<Map<String,Boolean>>() { }); |
| MapType mapType = (MapType) type; |
| assertEquals(tf.constructType(String.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Boolean.class), mapType.getContentType()); |
| } |
| |
| // since 2.7 |
| public void testMapTypesRefined() |
| { |
| TypeFactory tf = newTypeFactory(); |
| JavaType type = tf.constructType(new TypeReference<Map<String,List<Integer>>>() { }); |
| MapType mapType = (MapType) type; |
| assertEquals(Map.class, mapType.getRawClass()); |
| assertEquals(String.class, mapType.getKeyType().getRawClass()); |
| assertEquals(List.class, mapType.getContentType().getRawClass()); |
| assertEquals(Integer.class, mapType.getContentType().getContentType().getRawClass()); |
| // No super-class, since it's an interface: |
| assertNull(type.getSuperClass()); |
| |
| // But then refine to reflect sub-classing |
| JavaType subtype = tf.constructSpecializedType(type, LinkedHashMap.class); |
| assertEquals(LinkedHashMap.class, subtype.getRawClass()); |
| assertEquals(String.class, subtype.getKeyType().getRawClass()); |
| assertEquals(List.class, subtype.getContentType().getRawClass()); |
| assertEquals(Integer.class, subtype.getContentType().getContentType().getRawClass()); |
| |
| // but with refinement, should have non-null super class |
| // 20-Oct-2015, tatu: For now refinement does not faithfully replicate the |
| // structure, it only retains most important information. Here it means |
| // that actually existing super-classes are skipped, and only original |
| // type is linked as expected |
| |
| JavaType superType = subtype.getSuperClass(); |
| assertNotNull(superType); |
| assertEquals(HashMap.class, superType.getRawClass()); |
| // which also should have proper typing |
| assertEquals(String.class, superType.getKeyType().getRawClass()); |
| assertEquals(List.class, superType.getContentType().getRawClass()); |
| assertEquals(Integer.class, superType.getContentType().getContentType().getRawClass()); |
| } |
| |
| public void testMapTypesRaw() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType type = tf.constructType(HashMap.class); |
| MapType mapType = (MapType) type; |
| assertEquals(tf.constructType(Object.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Object.class), mapType.getContentType()); |
| } |
| |
| public void testMapTypesAdvanced() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType type = tf.constructType(MyMap.class); |
| MapType mapType = (MapType) type; |
| assertEquals(tf.constructType(String.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Long.class), mapType.getContentType()); |
| |
| type = tf.constructType(MapInterface.class); |
| mapType = (MapType) type; |
| |
| assertEquals(tf.constructType(String.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Integer.class), mapType.getContentType()); |
| |
| type = tf.constructType(MyStringIntMap.class); |
| mapType = (MapType) type; |
| assertEquals(tf.constructType(String.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Integer.class), mapType.getContentType()); |
| } |
| |
| /** |
| * Specific test to verify that complicate name mangling schemes |
| * do not fool type resolver |
| */ |
| public void testMapTypesSneaky() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType type = tf.constructType(IntLongMap.class); |
| MapType mapType = (MapType) type; |
| assertEquals(tf.constructType(Integer.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Long.class), mapType.getContentType()); |
| } |
| |
| /** |
| * Plus sneaky types may be found via introspection as well. |
| */ |
| public void testSneakyFieldTypes() throws Exception |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| Field field = SneakyBean.class.getDeclaredField("intMap"); |
| JavaType type = tf.constructType(field.getGenericType()); |
| assertTrue(type instanceof MapType); |
| MapType mapType = (MapType) type; |
| assertEquals(tf.constructType(Integer.class), mapType.getKeyType()); |
| assertEquals(tf.constructType(Long.class), mapType.getContentType()); |
| |
| field = SneakyBean.class.getDeclaredField("longList"); |
| type = tf.constructType(field.getGenericType()); |
| assertTrue(type instanceof CollectionType); |
| CollectionType collectionType = (CollectionType) type; |
| assertEquals(tf.constructType(Long.class), collectionType.getContentType()); |
| } |
| |
| /** |
| * Looks like type handling actually differs for properties, too. |
| */ |
| public void testSneakyBeanProperties() throws Exception |
| { |
| ObjectMapper mapper = new ObjectMapper(); |
| StringLongMapBean bean = mapper.readValue("{\"value\":{\"a\":123}}", StringLongMapBean.class); |
| assertNotNull(bean); |
| Map<String,Long> map = bean.value; |
| assertEquals(1, map.size()); |
| assertEquals(Long.valueOf(123), map.get("a")); |
| |
| StringListBean bean2 = mapper.readValue("{\"value\":[\"...\"]}", StringListBean.class); |
| assertNotNull(bean2); |
| List<String> list = bean2.value; |
| assertSame(GenericList.class, list.getClass()); |
| assertEquals(1, list.size()); |
| assertEquals("...", list.get(0)); |
| } |
| |
| public void testSneakySelfRefs() throws Exception |
| { |
| ObjectMapper mapper = new ObjectMapper(); |
| String json = mapper.writeValueAsString(new SneakyBean2()); |
| assertEquals("{\"foobar\":null}", json); |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests: handling of specific JDK types |
| /********************************************************** |
| */ |
| |
| public void testAtomicArrayRefParameters() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType type = tf.constructType(new TypeReference<AtomicReference<long[]>>() { }); |
| JavaType[] params = tf.findTypeParameters(type, AtomicReference.class); |
| assertNotNull(params); |
| assertEquals(1, params.length); |
| assertEquals(tf.constructType(long[].class), params[0]); |
| } |
| |
| static abstract class StringIntMapEntry implements Map.Entry<String,Integer> { } |
| |
| public void testMapEntryResolution() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType t = tf.constructType(StringIntMapEntry.class); |
| JavaType mapEntryType = t.findSuperType(Map.Entry.class); |
| assertNotNull(mapEntryType); |
| assertTrue(mapEntryType.hasGenericTypes()); |
| assertEquals(2, mapEntryType.containedTypeCount()); |
| assertEquals(String.class, mapEntryType.containedType(0).getRawClass()); |
| assertEquals(Integer.class, mapEntryType.containedType(1).getRawClass()); |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests: construction of "raw" types |
| /********************************************************** |
| */ |
| |
| public void testRawCollections() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType type = tf.constructRawCollectionType(ArrayList.class); |
| assertTrue(type.isContainerType()); |
| assertEquals(TypeFactory.unknownType(), type.getContentType()); |
| type = tf.constructRawCollectionLikeType(CollectionLike.class); // must have type vars |
| assertTrue(type.isCollectionLikeType()); |
| assertEquals(TypeFactory.unknownType(), type.getContentType()); |
| |
| // actually, should also allow "no type vars" case |
| type = tf.constructRawCollectionLikeType(String.class); |
| assertTrue(type.isCollectionLikeType()); |
| assertEquals(TypeFactory.unknownType(), type.getContentType()); |
| } |
| |
| public void testRawMaps() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| JavaType type = tf.constructRawMapType(HashMap.class); |
| assertTrue(type.isContainerType()); |
| assertEquals(TypeFactory.unknownType(), type.getKeyType()); |
| assertEquals(TypeFactory.unknownType(), type.getContentType()); |
| |
| type = tf.constructRawMapLikeType(MapLike.class); // must have type vars |
| assertTrue(type.isMapLikeType()); |
| assertEquals(TypeFactory.unknownType(), type.getKeyType()); |
| assertEquals(TypeFactory.unknownType(), type.getContentType()); |
| |
| // actually, should also allow "no type vars" case |
| type = tf.constructRawMapLikeType(String.class); |
| assertTrue(type.isMapLikeType()); |
| assertEquals(TypeFactory.unknownType(), type.getKeyType()); |
| assertEquals(TypeFactory.unknownType(), type.getContentType()); |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests: other |
| /********************************************************** |
| */ |
| |
| public void testMoreSpecificType() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance(); |
| |
| JavaType t1 = tf.constructCollectionType(Collection.class, Object.class); |
| JavaType t2 = tf.constructCollectionType(List.class, Object.class); |
| assertSame(t2, tf.moreSpecificType(t1, t2)); |
| assertSame(t2, tf.moreSpecificType(t2, t1)); |
| |
| t1 = tf.constructType(Double.class); |
| t2 = tf.constructType(Number.class); |
| assertSame(t1, tf.moreSpecificType(t1, t2)); |
| assertSame(t1, tf.moreSpecificType(t2, t1)); |
| |
| // and then unrelated, return first |
| t1 = tf.constructType(Double.class); |
| t2 = tf.constructType(String.class); |
| assertSame(t1, tf.moreSpecificType(t1, t2)); |
| assertSame(t2, tf.moreSpecificType(t2, t1)); |
| } |
| |
| // [databind#489] |
| public void testCacheClearing() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance().withModifier(null); |
| assertEquals(0, tf._typeCache.size()); |
| tf.constructType(getClass()); |
| // 19-Oct-2015, tatu: This is pretty fragile but |
| assertEquals(6, tf._typeCache.size()); |
| tf.clearCache(); |
| assertEquals(0, tf._typeCache.size()); |
| } |
| |
| // for [databind#1297] |
| public void testRawMapType() |
| { |
| TypeFactory tf = TypeFactory.defaultInstance().withModifier(null); // to get a new copy |
| |
| JavaType type = tf.constructParametricType(Wrapper1297.class, Map.class); |
| assertNotNull(type); |
| assertEquals(Wrapper1297.class, type.getRawClass()); |
| } |
| } |