From 745404b1e4264b616761ad39aaa6ffa9e2b38e71 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 22 May 2023 20:41:48 -0700 Subject: [PATCH 1/5] Add basic impl of IterationType --- .../jackson/databind/type/IterationType.java | 153 +++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java b/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java index 1892a39533..31b80d6003 100644 --- a/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java +++ b/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java @@ -1,29 +1,176 @@ package com.fasterxml.jackson.databind.type; +import java.util.Objects; + import com.fasterxml.jackson.databind.JavaType; /** * Specialized {@link SimpleType} for types that are allow iteration * over Collection(-like) types: this includes types like * {@link java.util.Iterator}. - * Referenced type is accessible using {@link #getContentType()}. + * Iterated (content) type is accessible using {@link #getContentType()}. * * @since 2.16 */ -public abstract class IterationType extends SimpleType +public class IterationType extends SimpleType { private static final long serialVersionUID = 1L; protected final JavaType _iteratedType; + protected IterationType(Class cls, TypeBindings bindings, + JavaType superClass, JavaType[] superInts, JavaType iteratedType, + Object valueHandler, Object typeHandler, boolean asStatic) + { + super(cls, bindings, superClass, superInts, Objects.hashCode(iteratedType), + valueHandler, typeHandler, asStatic); + _iteratedType = iteratedType; + } + /** * Constructor used when upgrading into this type (via {@link #upgradeFrom}, * the usual way for {@link IterationType}s to come into existence. - * Sets up what is considered the "base" reference type + * Sets up what is considered the "base" iteration type */ protected IterationType(TypeBase base, JavaType iteratedType) { super(base); _iteratedType = iteratedType; } + + /** + * Factory method that can be used to "upgrade" a basic type into iteration + * type; usually done via {@link TypeModifier} + * + * @param baseType Resolved non-iteration type (usually {@link SimpleType}) that is being upgraded + * @param iteratedType Iterated type; usually the first and only type parameter, but not necessarily + */ + public static IterationType upgradeFrom(JavaType baseType, JavaType iteratedType) { + Objects.requireNonNull(iteratedType); + // 19-Oct-2015, tatu: Not sure if and how other types could be used as base; + // will cross that bridge if and when need be + if (baseType instanceof TypeBase) { + return new IterationType((TypeBase) baseType, iteratedType); + } + throw new IllegalArgumentException("Cannot upgrade from an instance of "+baseType.getClass()); + } + + public static IterationType construct(Class cls, TypeBindings bindings, + JavaType superClass, JavaType[] superInts, JavaType iteratedType) + { + return new IterationType(cls, bindings, superClass, superInts, + iteratedType, null, null, false); + } + + @Override + public JavaType withContentType(JavaType contentType) { + if (_iteratedType == contentType) { + return this; + } + return new IterationType(_class, _bindings, _superClass, _superInterfaces, + contentType, _valueHandler, _typeHandler, _asStatic); + } + + @Override + public IterationType withTypeHandler(Object h) + { + if (h == _typeHandler) { + return this; + } + return new IterationType(_class, _bindings, _superClass, _superInterfaces, + _iteratedType, _valueHandler, h, _asStatic); + } + + @Override + public IterationType withContentTypeHandler(Object h) + { + if (h == _iteratedType.getTypeHandler()) { + return this; + } + return new IterationType(_class, _bindings, _superClass, _superInterfaces, + _iteratedType.withTypeHandler(h), + _valueHandler, _typeHandler, _asStatic); + } + + @Override + public IterationType withValueHandler(Object h) { + if (h == _valueHandler) { + return this; + } + return new IterationType(_class, _bindings, + _superClass, _superInterfaces, _iteratedType, + h, _typeHandler,_asStatic); + } + + @Override + public IterationType withContentValueHandler(Object h) { + if (h == _iteratedType.getValueHandler()) { + return this; + } + return new IterationType(_class, _bindings, + _superClass, _superInterfaces, _iteratedType.withValueHandler(h), + _valueHandler, _typeHandler, _asStatic); + } + + @Override + public IterationType withStaticTyping() { + if (_asStatic) { + return this; + } + return new IterationType(_class, _bindings, _superClass, _superInterfaces, + _iteratedType.withStaticTyping(), + _valueHandler, _typeHandler, true); + } + + @Override + public JavaType refine(Class rawType, TypeBindings bindings, + JavaType superClass, JavaType[] superInterfaces) { + return new IterationType(rawType, _bindings, + superClass, superInterfaces, _iteratedType, + _valueHandler, _typeHandler, _asStatic); + } + + @Override + protected String buildCanonicalName() + { + StringBuilder sb = new StringBuilder(); + sb.append(_class.getName()); + if ((_iteratedType != null) && _hasNTypeParameters(1)) { + sb.append('<'); + sb.append(_iteratedType.toCanonical()); + sb.append('>'); + } + return sb.toString(); + } + + /* + /********************************************************** + /* Public API overrides + /********************************************************** + */ + + @Override + public JavaType getContentType() { + return _iteratedType; + } + + @Override + public boolean hasContentType() { + return true; + } + + @Override + public StringBuilder getErasedSignature(StringBuilder sb) { + return _classSignature(_class, sb, true); + } + + @Override + public StringBuilder getGenericSignature(StringBuilder sb) + { + _classSignature(_class, sb, false); + sb.append('<'); + sb = _iteratedType.getGenericSignature(sb); + sb.append(">;"); + return sb; + } } From 28b6f7d1c49f23d6dcc2b41d32e9ba4d95b8a4a6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 23 May 2023 20:23:34 -0700 Subject: [PATCH 2/5] Add actual implementation, tests --- release-notes/VERSION-2.x | 1 + .../fasterxml/jackson/databind/JavaType.java | 12 ++++ .../jackson/databind/type/IterationType.java | 5 ++ .../jackson/databind/type/TypeFactory.java | 47 ++++++++++++++- .../jackson/databind/type/TestJavaType.java | 58 +++++++++++++++++++ 5 files changed, 122 insertions(+), 1 deletion(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 3a80213e88..c97ef80453 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,7 @@ Project: jackson-databind #3928: `@JsonProperty` on constructor parameter changes default field serialization order (contributed by @pjfanning) +#3950: Create new `JavaType` subtype `IterationType` (extending `SimpleType`) 2.15.2 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/JavaType.java b/src/main/java/com/fasterxml/jackson/databind/JavaType.java index 1323b52ac5..b708f24e12 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JavaType.java +++ b/src/main/java/com/fasterxml/jackson/databind/JavaType.java @@ -359,6 +359,18 @@ public final boolean isRecordType() { return ClassUtil.isRecordType(_class); } + /** + * Method that returns true if this instance is of type + * {@code IterationType}. + * + * @since 2.16 + * + * @return True if this type is considered "iteration type" + */ + public boolean isIterationType() { + return false; + } + @Override public final boolean isInterface() { return _class.isInterface(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java b/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java index 31b80d6003..a2e5823511 100644 --- a/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java +++ b/src/main/java/com/fasterxml/jackson/databind/type/IterationType.java @@ -159,6 +159,11 @@ public boolean hasContentType() { return true; } + @Override + public boolean isIterationType() { + return true; + } + @Override public StringBuilder getErasedSignature(StringBuilder sb) { return _classSignature(_class, sb, true); diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java index 9a4971e7b9..aa273c5939 100644 --- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java @@ -2,6 +2,11 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.BaseStream; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; import java.lang.reflect.*; import com.fasterxml.jackson.core.type.TypeReference; @@ -1318,6 +1323,30 @@ private JavaType _referenceType(Class rawClass, TypeBindings bindings, return ReferenceType.construct(rawClass, bindings, superClass, superInterfaces, ct); } + private JavaType _iterationType(Class rawClass, TypeBindings bindings, + JavaType superClass, JavaType[] superInterfaces) + { + List typeParams = bindings.getTypeParameters(); + // ok to have no types ("raw") + JavaType ct; + if (typeParams.isEmpty()) { + ct = _unknownType(); + } else if (typeParams.size() == 1) { + ct = typeParams.get(0); + } else { + throw new IllegalArgumentException("Strange Iteration type "+rawClass.getName()+": cannot determine type parameters"); + } + return _iterationType(rawClass, bindings, superClass, superInterfaces, ct); + } + + private JavaType _iterationType(Class rawClass, TypeBindings bindings, + JavaType superClass, JavaType[] superInterfaces, + JavaType iteratedType) + { + return IterationType.construct(rawClass, bindings, superClass, superInterfaces, + iteratedType); + } + /** * Factory method to call when no special {@link JavaType} is needed, * no generic parameters are passed. Default implementation may check @@ -1582,9 +1611,25 @@ protected JavaType _fromWellKnownClass(ClassStack context, Class rawType, Typ return _referenceType(rawType, bindings, superClass, superInterfaces); } // 01-Nov-2015, tatu: As of 2.7, couple of potential `CollectionLikeType`s (like - // `Iterable`, `Iterator`), and `MapLikeType`s (`Map.Entry`) are not automatically + // `Iterable`), and `MapLikeType`s (`Map.Entry`) are not automatically // detected, related to difficulties in propagating type upwards (Iterable, for // example, is a weak, tag-on type). They may be detectable in future. + // 23-May-2023, tatu: As of 2.16 we do, however, recognized certain `IterationType`s. + if (rawType == Iterator.class || rawType == Stream.class) { + return _iterationType(rawType, bindings, superClass, superInterfaces); + } + if (BaseStream.class.isAssignableFrom(rawType)) { + if (DoubleStream.class.isAssignableFrom(rawType)) { + return _iterationType(rawType, bindings, superClass, superInterfaces, + constructType(Double.class)); + } else if (IntStream.class.isAssignableFrom(rawType)) { + return _iterationType(rawType, bindings, superClass, superInterfaces, + constructType(Integer.class)); + } else if (LongStream.class.isAssignableFrom(rawType)) { + return _iterationType(rawType, bindings, superClass, superInterfaces, + constructType(Long.class)); + } + } return null; } diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java index d70979d73e..c514a7daf0 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java @@ -3,7 +3,12 @@ import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.BaseMapTest; import com.fasterxml.jackson.databind.JavaType; @@ -94,6 +99,7 @@ public void testSimpleClass() assertFalse(baseType.isContainerType()); assertFalse(baseType.isEnumType()); assertFalse(baseType.isInterface()); + assertFalse(baseType.isIterationType()); assertFalse(baseType.isPrimitive()); assertFalse(baseType.isReferenceType()); assertFalse(baseType.hasContentType()); @@ -125,6 +131,7 @@ public void testArrayType() JavaType arrayT = ArrayType.construct(tf.constructType(String.class), null); assertNotNull(arrayT); assertTrue(arrayT.isContainerType()); + assertFalse(arrayT.isIterationType()); assertFalse(arrayT.isReferenceType()); assertTrue(arrayT.hasContentType()); @@ -146,6 +153,7 @@ public void testMapType() TypeFactory tf = TypeFactory.defaultInstance(); JavaType mapT = tf.constructType(HashMap.class); assertTrue(mapT.isContainerType()); + assertFalse(mapT.isIterationType()); assertFalse(mapT.isReferenceType()); assertTrue(mapT.hasContentType()); @@ -170,6 +178,7 @@ public void testEnumType() assertTrue(enumT.getRawClass().isEnum()); assertTrue(enumT.isEnumType()); assertTrue(enumT.isEnumImplType()); + assertFalse(enumT.isIterationType()); assertFalse(enumT.hasHandlers()); assertTrue(enumT.isTypeOrSubTypeOf(MyEnum.class)); @@ -240,6 +249,7 @@ public void testGenericSignature1194() throws Exception assertEquals("Ljava/util/concurrent/atomic/AtomicReference;", t.getGenericSignature()); } + @Deprecated public void testAnchorTypeForRefTypes() throws Exception { TypeFactory tf = TypeFactory.defaultInstance(); @@ -285,4 +295,52 @@ public void testConstructReferenceType() throws Exception // Should we even verify this or not? assertEquals("V", bindings.getBoundName(0)); } + + // for [databind#3950]: resolve `Iterator`, `Stream` + public void testIterationTypesDirect() throws Exception + { + TypeFactory tf = TypeFactory.defaultInstance(); + + // First, type-erased types + _verifyIteratorType(tf.constructType(Iterator.class), + Iterator.class, Object.class); + _verifyIteratorType(tf.constructType(Stream.class), + Stream.class, Object.class); + + // Then generic but direct + _verifyIteratorType(tf.constructType(new TypeReference>() { }), + Iterator.class, String.class); + _verifyIteratorType(tf.constructType(new TypeReference>() { }), + Stream.class, Long.class); + + // Then numeric typed: + _verifyIteratorType(tf.constructType(DoubleStream.class), + DoubleStream.class, Double.class); + _verifyIteratorType(tf.constructType(IntStream.class), + IntStream.class, Integer.class); + _verifyIteratorType(tf.constructType(LongStream.class), + LongStream.class, Long.class); + } + + // for [databind#3950]: resolve `Iterator`, `Stream` + public void testIterationTypesFromValues() throws Exception + { + TypeFactory tf = TypeFactory.defaultInstance(); + List strings = Arrays.asList("foo", "bar"); + // We will get type-erased, alas, so: + Iterator stringIT = strings.iterator(); + _verifyIteratorType(tf.constructType(stringIT.getClass()), + stringIT.getClass(), Object.class); + Stream stringStream = strings.stream(); + _verifyIteratorType(tf.constructType(stringStream.getClass()), + stringStream.getClass(), Object.class); + } + + private void _verifyIteratorType(JavaType type, + Class expType, Class expContentType) { + assertTrue(type.isIterationType()); + assertEquals(IterationType.class, type.getClass()); + assertEquals(expType, type.getRawClass()); + assertEquals(expContentType, type.getContentType().getRawClass()); + } } From a966f0da4f571cfe5df0cad36bd367aa95f75a6f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 23 May 2023 20:29:30 -0700 Subject: [PATCH 3/5] Minor addition to tests --- .../com/fasterxml/jackson/databind/type/TestJavaType.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java index c514a7daf0..42bb6e8da2 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java @@ -308,8 +308,11 @@ public void testIterationTypesDirect() throws Exception Stream.class, Object.class); // Then generic but direct - _verifyIteratorType(tf.constructType(new TypeReference>() { }), + JavaType t = _verifyIteratorType(tf.constructType(new TypeReference>() { }), Iterator.class, String.class); + assertEquals("java.util.Iterator", t.toCanonical()); + assertEquals("Ljava/util/Iterator;", t.getErasedSignature()); + assertEquals("Ljava/util/Iterator;", t.getGenericSignature()); _verifyIteratorType(tf.constructType(new TypeReference>() { }), Stream.class, Long.class); @@ -336,11 +339,12 @@ public void testIterationTypesFromValues() throws Exception stringStream.getClass(), Object.class); } - private void _verifyIteratorType(JavaType type, + private JavaType _verifyIteratorType(JavaType type, Class expType, Class expContentType) { assertTrue(type.isIterationType()); assertEquals(IterationType.class, type.getClass()); assertEquals(expType, type.getRawClass()); assertEquals(expContentType, type.getContentType().getRawClass()); + return type; } } From e78d20e23d18ab7f76e3953ba9424c92c0999407 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 23 May 2023 20:32:46 -0700 Subject: [PATCH 4/5] ... --- .../com/fasterxml/jackson/databind/type/TestTypeFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java index d0032d083f..849ea407f3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java @@ -142,11 +142,13 @@ public void testProperties() assertSame(String.class, mt.getContentType().getRawClass()); } + // note: changed for [databind#3950] public void testIterator() { TypeFactory tf = TypeFactory.defaultInstance(); JavaType t = tf.constructType(new TypeReference>() { }); - assertEquals(SimpleType.class, t.getClass()); + assertEquals(IterationType.class, t.getClass()); + assertTrue(t.isIterationType()); assertSame(Iterator.class, t.getRawClass()); assertEquals(1, t.containedTypeCount()); assertEquals(tf.constructType(String.class), t.containedType(0)); From a61dcf66d29f5b81c26e2ebfc60d4a19b74b921c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 23 May 2023 20:40:27 -0700 Subject: [PATCH 5/5] Bit more testing --- .../jackson/databind/type/TestJavaType.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java index 42bb6e8da2..d93ba5cb8f 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java @@ -60,6 +60,9 @@ public interface Generic1194 { @SuppressWarnings("serial") static class AtomicStringReference extends AtomicReference { } + static interface StringStream extends Stream { } + static interface StringIterator extends Iterator { } + /* /********************************************************** /* Test methods @@ -339,6 +342,16 @@ public void testIterationTypesFromValues() throws Exception stringStream.getClass(), Object.class); } + // for [databind#3950]: resolve `Iterator`, `Stream` + public void testIterationSubTypes() throws Exception + { + TypeFactory tf = TypeFactory.defaultInstance(); + _verifyIteratorType(tf.constructType(StringIterator.class), + StringIterator.class, String.class); + _verifyIteratorType(tf.constructType(StringStream.class), + StringStream.class, String.class); + } + private JavaType _verifyIteratorType(JavaType type, Class expType, Class expContentType) { assertTrue(type.isIterationType());