From 2d6fbccf2a2fe77ca26179bb1ca0878d2a87be1b Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Tue, 26 Jul 2022 17:08:13 -0500 Subject: [PATCH 01/14] add support for inferred polymorphic serialization of sealed classes using permitted subclasses --- .../databind/AnnotationIntrospector.java | 26 +++++- .../jackson/databind/MapperFeature.java | 10 ++- .../AnnotationIntrospectorPair.java | 23 ++++- .../JacksonAnnotationIntrospector.java | 18 +++- .../jackson/databind/jdk17/JDK17Util.java | 85 +++++++++++++++++++ .../jackson/databind/jdk17/package-info.java | 6 ++ .../jsontype/impl/StdSubtypeResolver.java | 18 ++-- 7 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java create mode 100644 src/main/java/com/fasterxml/jackson/databind/jdk17/package-info.java diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index bed08b655c..11d1c07627 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -475,7 +475,7 @@ public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, * This includes not only * instantiating resolver builder, but also configuring it based on * relevant annotations (not including ones checked with a call to - * {@link #findSubtypes} + * {@link #findSubtypesByAnnotations} * * @param config Configuration settings in effect (for serialization or deserialization) * @param ac Annotated class to check for annotations @@ -494,7 +494,7 @@ public TypeResolverBuilder findTypeResolver(MapperConfig config, * This includes not only * instantiating resolver builder, but also configuring it based on * relevant annotations (not including ones checked with a call to - * {@link #findSubtypes} + * {@link #findSubtypesByAnnotations} * * @param config Configuration settings in effect (for serialization or deserialization) * @param am Annotated member (field or method) to check for annotations @@ -516,7 +516,7 @@ public TypeResolverBuilder findPropertyTypeResolver(MapperConfig config, * This includes not only * instantiating resolver builder, but also configuring it based on * relevant annotations (not including ones checked with a call to - * {@link #findSubtypes} + * {@link #findSubtypesByAnnotations} * * @param config Configuration settings in effect (for serialization or deserialization) * @param am Annotated member (field or method) to check for annotations @@ -529,6 +529,11 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co AnnotatedMember am, JavaType containerType) { return null; } + + /** + * @deprecated Use {@link #findSubtypesByAnnotations(Annotated)} instead. + */ + public List findSubtypes(Annotated a) { return findSubtypesByAnnotations(a); } /** * Method for locating annotation-specified subtypes related to annotated @@ -541,7 +546,20 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co * * @return List of subtype definitions found if any; {@code null} if none */ - public List findSubtypes(Annotated a) { return null; } + public List findSubtypesByAnnotations(Annotated a) { return null; } + + /** + * Method for locating the permitted subclasses specified by a sealed class. + * Note that this is only guaranteed to be a list of direct subtypes, no + * recursive processing is guaranteed (i.e., caller has to do it if/as + * necessary). Note that invoking this method may implicitly load all Jackson + * Java 17 integrations and features. + * + * @param klass A Java content type {@link Class} + * + * @return List of subtype definitions found if any; {@code null} if none + */ + public List findSubtypesByPermittedSubclasses(Class klass) { return null; } /** * Method for checking if specified type has explicit name. diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java index 5cace6d369..869e0b64ef 100644 --- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java @@ -582,7 +582,15 @@ public enum MapperFeature implements ConfigFeature * * @since 2.13 */ - APPLY_DEFAULT_VALUES(true) + APPLY_DEFAULT_VALUES(true), + + /** + * Feature that determines whether subtypes can be discovered automatically from permitted + * subclasses in sealed classes, new in JDK 17. + * + * @since 2.14 + */ + DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES(true) ; private final boolean _defaultState; diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java index 63d55e64c3..34af1f1797 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java @@ -260,8 +260,27 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co @Override public List findSubtypes(Annotated a) { - List types1 = _primary.findSubtypes(a); - List types2 = _secondary.findSubtypes(a); + return findSubtypesByAnnotations(a); + } + + @Override + public List findSubtypesByAnnotations(Annotated a) + { + List types1 = _primary.findSubtypesByAnnotations(a); + List types2 = _secondary.findSubtypesByAnnotations(a); + if (types1 == null || types1.isEmpty()) return types2; + if (types2 == null || types2.isEmpty()) return types1; + ArrayList result = new ArrayList(types1.size() + types2.size()); + result.addAll(types1); + result.addAll(types2); + return result; + } + + @Override + public List findSubtypesByPermittedSubclasses(Class klass) + { + List types1 = _primary.findSubtypesByPermittedSubclasses(klass); + List types2 = _secondary.findSubtypesByPermittedSubclasses(klass); if (types1 == null || types1.isEmpty()) return types2; if (types2 == null || types2.isEmpty()) return types1; ArrayList result = new ArrayList(types1.size() + types2.size()); diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index c8bebb5199..bc3bb7943c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.ext.Java7Support; +import com.fasterxml.jackson.databind.jdk17.JDK17Util; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; @@ -614,10 +615,9 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co } return _findTypeResolver(config, am, containerType); } - + @Override - public List findSubtypes(Annotated a) - { + public List findSubtypesByAnnotations(Annotated a) { JsonSubTypes t = _findAnnotation(a, JsonSubTypes.class); if (t == null) return null; JsonSubTypes.Type[] types = t.value(); @@ -632,6 +632,18 @@ public List findSubtypes(Annotated a) return result; } + @Override + public List findSubtypesByPermittedSubclasses(Class klass) { + boolean sealed = Optional.ofNullable(JDK17Util.isSealed(klass)).orElse(false); + if (sealed) { + Class[] permittedSubclasses = JDK17Util.getPermittedSubclasses(klass); + if (permittedSubclasses != null && permittedSubclasses.length > 0) { + return Arrays.stream(permittedSubclasses).map(NamedType::new).toList(); + } + } + return null; + } + @Override public String findTypeName(AnnotatedClass ac) { diff --git a/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java b/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java new file mode 100644 index 0000000000..f84c61f9bc --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java @@ -0,0 +1,85 @@ +package com.fasterxml.jackson.databind.jdk17; + +import java.lang.reflect.Method; +import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.NativeImageUtil; + +/** + * Helper class to support some of JDK 17 (and later) features without Jackson itself being run on + * (or even built with) Java 17. In particular allows better support of sealed class types (see + * JEP 409). + * + * @since 2.14 + */ +public class JDK17Util { + public static Boolean isSealed(Class type) { + return SealedClassAccessor.instance().isSealed(type); + } + + public static Class[] getPermittedSubclasses(Class sealedType) { + return SealedClassAccessor.instance().getPermittedSubclasses(sealedType); + } + + static class SealedClassAccessor { + private final Method SEALED_IS_SEALED; + private final Method SEALED_GET_PERMITTED_SUBCLASSES; + + private final static SealedClassAccessor INSTANCE; + private final static RuntimeException PROBLEM; + + static { + RuntimeException prob = null; + SealedClassAccessor inst = null; + try { + inst = new SealedClassAccessor(); + } catch (RuntimeException e) { + prob = e; + } + INSTANCE = inst; + PROBLEM = prob; + } + + private SealedClassAccessor() throws RuntimeException { + try { + SEALED_IS_SEALED = Class.class.getMethod("isSealed"); + SEALED_GET_PERMITTED_SUBCLASSES = Class.class.getMethod("getPermittedSubclasses"); + } catch (Exception e) { + throw new RuntimeException( + String.format("Failed to access Methods needed to support sealed classes: (%s) %s", + e.getClass().getName(), e.getMessage()), + e); + } + } + + public static SealedClassAccessor instance() { + if (PROBLEM != null) { + throw PROBLEM; + } + return INSTANCE; + } + + public Boolean isSealed(Class type) throws IllegalArgumentException { + try { + return (Boolean) SEALED_IS_SEALED.invoke(type); + } catch (Exception e) { + if (NativeImageUtil.isUnsupportedFeatureError(e)) { + return null; + } + throw new IllegalArgumentException( + "Failed to access sealedness of type " + ClassUtil.nameOf(type)); + } + } + + public Class[] getPermittedSubclasses(Class sealedType) throws IllegalArgumentException { + try { + return (Class[]) SEALED_GET_PERMITTED_SUBCLASSES.invoke(sealedType); + } catch (Exception e) { + if (NativeImageUtil.isUnsupportedFeatureError(e)) { + return null; + } + throw new IllegalArgumentException( + "Failed to access permitted subclasses of type " + ClassUtil.nameOf(sealedType)); + } + } + } +} diff --git a/src/main/java/com/fasterxml/jackson/databind/jdk17/package-info.java b/src/main/java/com/fasterxml/jackson/databind/jdk17/package-info.java new file mode 100644 index 0000000000..65a44fee53 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/jdk17/package-info.java @@ -0,0 +1,6 @@ +/** +Contains helper class(es) needed to support some of JDK17+ +features without requiring running or building using JDK 17. +*/ + +package com.fasterxml.jackson.databind.jdk17; diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java index e919888288..55c70bcba4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java @@ -2,9 +2,9 @@ import java.lang.reflect.Modifier; import java.util.*; - import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.databind.jsontype.NamedType; @@ -109,7 +109,9 @@ public Collection collectAndResolveSubtypesByClass(MapperConfig co // then annotated types for property itself if (property != null) { - Collection st = ai.findSubtypes(property); + Collection st = ai.findSubtypesByAnnotations(property); + if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + st = ai.findSubtypesByPermittedSubclasses(rawBase); if (st != null) { for (NamedType nt : st) { AnnotatedClass ac = AnnotatedClassResolver.resolveWithoutSuperTypes(config, @@ -178,7 +180,9 @@ public Collection collectAndResolveSubtypesByTypeId(MapperConfig c // then with definitions from property if (property != null) { - Collection st = ai.findSubtypes(property); + Collection st = ai.findSubtypesByAnnotations(property); + if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES) && baseType.hasContentType()) + st = ai.findSubtypesByPermittedSubclasses(rawBase); if (st != null) { for (NamedType nt : st) { ac = AnnotatedClassResolver.resolveWithoutSuperTypes(config, nt.getType()); @@ -262,7 +266,9 @@ protected void _collectAndResolve(AnnotatedClass annotatedType, NamedType namedT } // if it wasn't, add and check subtypes recursively collectedSubtypes.put(typeOnlyNamedType, namedType); - Collection st = ai.findSubtypes(annotatedType); + Collection st = ai.findSubtypesByAnnotations(annotatedType); + if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + st = ai.findSubtypesByPermittedSubclasses(annotatedType.getRawType()); if (st != null && !st.isEmpty()) { for (NamedType subtype : st) { AnnotatedClass subtypeClass = AnnotatedClassResolver.resolveWithoutSuperTypes(config, @@ -293,7 +299,9 @@ protected void _collectAndResolveByTypeId(AnnotatedClass annotatedType, NamedTyp // only check subtypes if this type hadn't yet been handled if (typesHandled.add(namedType.getType())) { - Collection st = ai.findSubtypes(annotatedType); + Collection st = ai.findSubtypesByAnnotations(annotatedType); + if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + st = ai.findSubtypesByPermittedSubclasses(annotatedType.getRawType()); if (st != null && !st.isEmpty()) { for (NamedType subtype : st) { AnnotatedClass subtypeClass = AnnotatedClassResolver.resolveWithoutSuperTypes(config, From d57f5a9ee9ba40373fa1e8380de402b0e4e39cd9 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Tue, 26 Jul 2022 20:28:31 -0500 Subject: [PATCH 02/14] small logic fix --- .../jackson/databind/jsontype/impl/StdSubtypeResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java index 55c70bcba4..d30e273c36 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java @@ -181,7 +181,7 @@ public Collection collectAndResolveSubtypesByTypeId(MapperConfig c // then with definitions from property if (property != null) { Collection st = ai.findSubtypesByAnnotations(property); - if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES) && baseType.hasContentType()) + if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) st = ai.findSubtypesByPermittedSubclasses(rawBase); if (st != null) { for (NamedType nt : st) { From cfb837295b322fc4b4344880e6e7ddc0367b821b Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Tue, 26 Jul 2022 20:28:52 -0500 Subject: [PATCH 03/14] add sealed classes tests for JDK17 --- pom.xml | 1 + .../databind/jdk17/SealedBasicsTest.java | 443 ++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java diff --git a/pom.xml b/pom.xml index 5cfe2e2d53..6ab5f4932d 100644 --- a/pom.xml +++ b/pom.xml @@ -296,6 +296,7 @@ src/test-jdk14/java + src/test-jdk17/java diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java new file mode 100644 index 0000000000..b399bd1c2c --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java @@ -0,0 +1,443 @@ +package com.fasterxml.jackson.databind.jdk17; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import java.io.IOException; +import java.util.Objects; +import org.junit.Test; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; + +public class SealedBasicsTest { + /** + * Our {@link ObjectMapper} uses the default configuration that explicitly enables the sealed + * classes subtype discovery. + */ + @SuppressWarnings("deprecation") + public static final ObjectMapper MAPPER = + new ObjectMapper().configure(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES, true); + + /** + * The "ExampleOne" objects test serialization of sealed classes without a JsonSubTypes + * annotation. + */ + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + public static sealed class ExampleOne permits AlphaExampleOne, BravoExampleOne { + } + + /** + * Provide an explicit JsonTypeName here + */ + @JsonTypeName("alpha") + public static final class AlphaExampleOne extends ExampleOne { + private String alpha; + + public AlphaExampleOne() {} + + public AlphaExampleOne(String alpha) { + this.alpha = alpha; + } + + /** + * @return the alpha + */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + */ + public void setAlpha(String alpha) { + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Objects.hash(alpha); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AlphaExampleOne other = (AlphaExampleOne) obj; + return Objects.equals(alpha, other.alpha); + } + } + + /** + * Use the default type name here + */ + public static final class BravoExampleOne extends ExampleOne { + public String bravo; + + public BravoExampleOne() {} + + public BravoExampleOne(String bravo) { + this.bravo = bravo; + } + + /** + * @return the bravo + */ + public String getBravo() { + return bravo; + } + + /** + * @param bravo the bravo to set + */ + public void setBravo(String bravo) { + this.bravo = bravo; + } + + @Override + public int hashCode() { + return Objects.hash(bravo); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BravoExampleOne other = (BravoExampleOne) obj; + return Objects.equals(bravo, other.bravo); + } + } + + @Test + public void oneAlphaSerializationTest() throws IOException { + final String alpha = "apple"; + final String serialized = MAPPER.writeValueAsString(new AlphaExampleOne(alpha)); + assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); + } + + @Test + public void oneAlphaDeserializationTest() throws IOException { + final String alpha = "apple"; + ExampleOne one = + MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleOne.class); + assertThat(one, is(new AlphaExampleOne(alpha))); + } + + @Test + public void oneBravoDeserializationTest() throws IOException { + final String bravo = "blueberry"; + ExampleOne one = MAPPER.readValue( + "{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}", + ExampleOne.class); + assertThat(one, is(new BravoExampleOne(bravo))); + } + + @Test + public void oneBravoSerializationTest() throws IOException { + final String bravo = "blueberry"; + final String serialized = MAPPER.writeValueAsString(new BravoExampleOne(bravo)); + assertThat(serialized, + is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); + } + + /** + * Jackson is quite smart and still picks up the supertype relationship during serialization, even + * without automatic subtype discovery. + */ + @Test + @SuppressWarnings("deprecation") + public void oneAlphaSerializationTestDiscoveryDisabled() throws IOException { + final String bravo = "blueberry"; + final String serialized = + new ObjectMapper().configure(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES, false) + .writeValueAsString(new BravoExampleOne(bravo)); + assertThat(serialized, + is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); + } + + /** + * Jackson should not pick up the subtype relationship without automatic discovery. + */ + @SuppressWarnings("deprecation") + @Test(expected = InvalidTypeIdException.class) + public void oneAlphaDeserializationTestDiscoveryDisabled() throws IOException { + new ObjectMapper().configure(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES, false) + .readValue("{\"type\":\"alpha\",\"alpha\":\"apple\"}", ExampleOne.class); + } + + /** + * The "ExampleTwo" objects test serialization of sealed classes with a JsonSubTypes annotation, + * which is the existing approach that must not break. + */ + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + @JsonSubTypes({@JsonSubTypes.Type(AlphaExampleTwo.class), + @JsonSubTypes.Type(value = BravoExampleTwo.class, name = "bravo")}) + public static sealed class ExampleTwo permits AlphaExampleTwo, BravoExampleTwo { + } + + /** + * Provide an explicit JsonTypeName here + */ + @JsonTypeName("alpha") + public static final class AlphaExampleTwo extends ExampleTwo { + private String alpha; + + public AlphaExampleTwo() {} + + public AlphaExampleTwo(String alpha) { + this.alpha = alpha; + } + + /** + * @return the alpha + */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + */ + public void setAlpha(String alpha) { + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Objects.hash(alpha); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AlphaExampleTwo other = (AlphaExampleTwo) obj; + return Objects.equals(alpha, other.alpha); + } + } + + + /** + * Use the default type name here + */ + public static final class BravoExampleTwo extends ExampleTwo { + public String bravo; + + public BravoExampleTwo() {} + + public BravoExampleTwo(String bravo) { + this.bravo = bravo; + } + + /** + * @return the bravo + */ + public String getBravo() { + return bravo; + } + + /** + * @param bravo the bravo to set + */ + public void setBravo(String bravo) { + this.bravo = bravo; + } + + @Override + public int hashCode() { + return Objects.hash(bravo); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BravoExampleTwo other = (BravoExampleTwo) obj; + return Objects.equals(bravo, other.bravo); + } + + } + + @Test + public void twoAlphaSerializationTest() throws IOException { + final String alpha = "apple"; + final String serialized = MAPPER.writeValueAsString(new AlphaExampleTwo(alpha)); + assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); + } + + @Test + public void twoAlphaDeserializationTest() throws IOException { + final String alpha = "apple"; + ExampleTwo two = + MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleTwo.class); + assertThat(two, is(new AlphaExampleTwo(alpha))); + } + + @Test + public void twoBravoDeserializationTest() throws IOException { + final String bravo = "blueberry"; + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + ExampleTwo two = + MAPPER.readValue("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}", ExampleTwo.class); + assertThat(two, is(new BravoExampleTwo(bravo))); + } + + @Test + public void twoBravoSerializationTest() throws IOException { + final String bravo = "blueberry"; + final String serialized = MAPPER.writeValueAsString(new BravoExampleTwo(bravo)); + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + assertThat(serialized, is("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}")); + } + + /** + * The "ExampleTwo" objects test serialization of conventional classes with a JsonSubTypes + * annotation, which is the existing approach that must not break. + */ + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + @JsonSubTypes({@JsonSubTypes.Type(AlphaExampleThree.class), + @JsonSubTypes.Type(value = BravoExampleThree.class, name = "bravo")}) + public static class ExampleThree { + } + + /** + * Provide an explicit JsonTypeName here + */ + @JsonTypeName("alpha") + public static final class AlphaExampleThree extends ExampleThree { + private String alpha; + + public AlphaExampleThree() {} + + public AlphaExampleThree(String alpha) { + this.alpha = alpha; + } + + /** + * @return the alpha + */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + */ + public void setAlpha(String alpha) { + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Objects.hash(alpha); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AlphaExampleThree other = (AlphaExampleThree) obj; + return Objects.equals(alpha, other.alpha); + } + } + + + /** + * Use the default type name here + */ + public static final class BravoExampleThree extends ExampleThree { + public String bravo; + + public BravoExampleThree() {} + + public BravoExampleThree(String bravo) { + this.bravo = bravo; + } + + /** + * @return the bravo + */ + public String getBravo() { + return bravo; + } + + /** + * @param bravo the bravo to set + */ + public void setBravo(String bravo) { + this.bravo = bravo; + } + + @Override + public int hashCode() { + return Objects.hash(bravo); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BravoExampleThree other = (BravoExampleThree) obj; + return Objects.equals(bravo, other.bravo); + } + + } + + @Test + public void threeAlphaSerializationTest() throws IOException { + final String alpha = "apple"; + final String serialized = MAPPER.writeValueAsString(new AlphaExampleThree(alpha)); + assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); + } + + @Test + public void threeAlphaDeserializationTest() throws IOException { + final String alpha = "apple"; + ExampleThree three = + MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleThree.class); + assertThat(three, is(new AlphaExampleThree(alpha))); + } + + @Test + public void threeBravoDeserializationTest() throws IOException { + final String bravo = "blueberry"; + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + ExampleThree three = + MAPPER.readValue("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}", ExampleThree.class); + assertThat(three, is(new BravoExampleThree(bravo))); + } + + @Test + public void threeBravoSerializationTest() throws IOException { + final String bravo = "blueberry"; + final String serialized = MAPPER.writeValueAsString(new BravoExampleThree(bravo)); + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + assertThat(serialized, is("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}")); + } +} From 63a6250dc3fc206f7d94776ebe590abb930ec3f8 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Tue, 26 Jul 2022 22:46:56 -0500 Subject: [PATCH 04/14] fix build on jdk17 --- pom.xml | 9 ++++++ .../failing/RecordWithJsonNaming3102Test.java | 7 +++-- .../failing/RecordWithJsonSetter2974Test.java | 3 +- .../databind/records/RecordBasicsTest.java | 22 ++++++------- .../records/RecordUpdate3079Test.java | 31 ++++++++++++------- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 6ab5f4932d..9f7a867634 100644 --- a/pom.xml +++ b/pom.xml @@ -313,9 +313,18 @@ 17 -parameters + --add-opens=java.base/java.lang=ALL-UNNAMED + --add-opens=java.base/java.util=ALL-UNNAMED + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED + + diff --git a/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonNaming3102Test.java b/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonNaming3102Test.java index f554450143..8f604e0d25 100644 --- a/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonNaming3102Test.java +++ b/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonNaming3102Test.java @@ -1,15 +1,16 @@ package com.fasterxml.jackson.databind.failing; +import org.junit.Ignore; import com.fasterxml.jackson.annotation.JsonCreator; - import com.fasterxml.jackson.databind.BaseMapTest; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; -// [databind#3102]: fails on JDK 16 which finally blocks mutation -// of Record fields. +//[databind#3102]: fails on JDK 16 which finally blocks mutation +//of Record fields. +@Ignore public class RecordWithJsonNaming3102Test extends BaseMapTest { @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) diff --git a/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonSetter2974Test.java b/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonSetter2974Test.java index 06ee8e75c2..c3f48a7380 100644 --- a/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonSetter2974Test.java +++ b/src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordWithJsonSetter2974Test.java @@ -2,7 +2,7 @@ import java.util.List; import java.util.Map; - +import org.junit.Ignore; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.BaseMapTest; @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.exc.InvalidNullException; +@Ignore public class RecordWithJsonSetter2974Test extends BaseMapTest { record RecordWithNonNullDefs(@JsonSetter(nulls=Nulls.AS_EMPTY) List names, diff --git a/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java b/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java index 5c6be142b6..9b2ff0a212 100644 --- a/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java +++ b/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java @@ -1,12 +1,7 @@ package com.fasterxml.jackson.databind.records; import com.fasterxml.jackson.annotation.*; - import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.databind.util.ClassUtil; - import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -142,14 +137,17 @@ public void testDeserializeJsonRename() throws Exception { /********************************************************************** */ + // TODO Ignore this test case // [databind#2992] - public void testNamingStrategy() throws Exception - { - SnakeRecord input = new SnakeRecord("123", "value"); - String json = MAPPER.writeValueAsString(input); - SnakeRecord output = MAPPER.readValue(json, SnakeRecord.class); - assertEquals(input, output); - } + // [databind#3102]: fails on JDK 16 which finally blocks mutation + // of Record fields. + // public void testNamingStrategy() throws Exception + // { + // SnakeRecord input = new SnakeRecord("123", "value"); + // String json = MAPPER.writeValueAsString(input); + // SnakeRecord output = MAPPER.readValue(json, SnakeRecord.class); + // assertEquals(input, output); + // } /* /********************************************************************** diff --git a/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java b/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java index 97dd7ce737..8f3f2b3792 100644 --- a/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java +++ b/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java @@ -1,7 +1,5 @@ package com.fasterxml.jackson.databind.records; -import java.util.Collections; - import com.fasterxml.jackson.databind.*; public class RecordUpdate3079Test extends BaseMapTest @@ -17,17 +15,26 @@ protected IdNameWrapper() { } private final ObjectMapper MAPPER = newJsonMapper(); + // TODO Ignore this test case + /** + * This test no longer works as of JDK 15 for records. Maybe Unsafe will work? + * + * https://medium.com/@atdsqdjyfkyziqkezu/java-15-breaks-deserialization-of-records-902fcc81253d + * https://stackoverflow.com/questions/61141836/change-static-final-field-in-java-12 + */ // [databind#3079]: Should be able to Record value directly - public void testDirectRecordUpdate() throws Exception - { - IdNameRecord orig = new IdNameRecord(123, "Bob"); - IdNameRecord result = MAPPER.updateValue(orig, - Collections.singletonMap("id", 137)); - assertNotNull(result); - assertEquals(137, result.id()); - assertEquals("Bob", result.name()); - assertNotSame(orig, result); - } + // [databind#3102]: fails on JDK 16 which finally blocks mutation + // of Record fields. + // public void testDirectRecordUpdate() throws Exception + // { + // IdNameRecord orig = new IdNameRecord(123, "Bob"); + // IdNameRecord result = MAPPER.updateValue(orig, + // Collections.singletonMap("id", 137)); + // assertNotNull(result); + // assertEquals(137, result.id()); + // assertEquals("Bob", result.name()); + // assertNotSame(orig, result); + // } // [databind#3079]: also: should be able to Record valued property public void testRecordAsPropertyUpdate() throws Exception From f0af3bbfb644839e5fd2d9027085d9cb84bf6265 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Tue, 26 Jul 2022 22:51:16 -0500 Subject: [PATCH 05/14] rename sealed class mapper feature --- .../com/fasterxml/jackson/databind/MapperFeature.java | 6 +++--- .../databind/jsontype/impl/StdSubtypeResolver.java | 8 ++++---- .../jackson/databind/jdk17/SealedBasicsTest.java | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java index 869e0b64ef..2c589ea96c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java @@ -585,12 +585,12 @@ public enum MapperFeature implements ConfigFeature APPLY_DEFAULT_VALUES(true), /** - * Feature that determines whether subtypes can be discovered automatically from permitted - * subclasses in sealed classes, new in JDK 17. + * Feature that determines whether subtypes are discovered from sealed class permitted + * subclasses automatically. * * @since 2.14 */ - DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES(true) + DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES(true) ; private final boolean _defaultState; diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java index d30e273c36..eac253f93b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java @@ -110,7 +110,7 @@ public Collection collectAndResolveSubtypesByClass(MapperConfig co // then annotated types for property itself if (property != null) { Collection st = ai.findSubtypesByAnnotations(property); - if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + if(st == null && config.isEnabled(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES)) st = ai.findSubtypesByPermittedSubclasses(rawBase); if (st != null) { for (NamedType nt : st) { @@ -181,7 +181,7 @@ public Collection collectAndResolveSubtypesByTypeId(MapperConfig c // then with definitions from property if (property != null) { Collection st = ai.findSubtypesByAnnotations(property); - if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + if(st == null && config.isEnabled(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES)) st = ai.findSubtypesByPermittedSubclasses(rawBase); if (st != null) { for (NamedType nt : st) { @@ -267,7 +267,7 @@ protected void _collectAndResolve(AnnotatedClass annotatedType, NamedType namedT // if it wasn't, add and check subtypes recursively collectedSubtypes.put(typeOnlyNamedType, namedType); Collection st = ai.findSubtypesByAnnotations(annotatedType); - if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + if(st == null && config.isEnabled(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES)) st = ai.findSubtypesByPermittedSubclasses(annotatedType.getRawType()); if (st != null && !st.isEmpty()) { for (NamedType subtype : st) { @@ -300,7 +300,7 @@ protected void _collectAndResolveByTypeId(AnnotatedClass annotatedType, NamedTyp // only check subtypes if this type hadn't yet been handled if (typesHandled.add(namedType.getType())) { Collection st = ai.findSubtypesByAnnotations(annotatedType); - if(st == null && config.isEnabled(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES)) + if(st == null && config.isEnabled(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES)) st = ai.findSubtypesByPermittedSubclasses(annotatedType.getRawType()); if (st != null && !st.isEmpty()) { for (NamedType subtype : st) { diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java index b399bd1c2c..c6acb81c58 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java @@ -19,7 +19,7 @@ public class SealedBasicsTest { */ @SuppressWarnings("deprecation") public static final ObjectMapper MAPPER = - new ObjectMapper().configure(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES, true); + new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, true); /** * The "ExampleOne" objects test serialization of sealed classes without a JsonSubTypes @@ -159,7 +159,7 @@ public void oneBravoSerializationTest() throws IOException { public void oneAlphaSerializationTestDiscoveryDisabled() throws IOException { final String bravo = "blueberry"; final String serialized = - new ObjectMapper().configure(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES, false) + new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, false) .writeValueAsString(new BravoExampleOne(bravo)); assertThat(serialized, is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); @@ -171,7 +171,7 @@ public void oneAlphaSerializationTestDiscoveryDisabled() throws IOException { @SuppressWarnings("deprecation") @Test(expected = InvalidTypeIdException.class) public void oneAlphaDeserializationTestDiscoveryDisabled() throws IOException { - new ObjectMapper().configure(MapperFeature.DETECT_SEALED_CLASS_PERMITTED_SUBCLASSES, false) + new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, false) .readValue("{\"type\":\"alpha\",\"alpha\":\"apple\"}", ExampleOne.class); } From 48c61186200c1a8e1255d7784e4e1b87a6a0674f Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Tue, 26 Jul 2022 22:54:54 -0500 Subject: [PATCH 06/14] add deprecation metadata --- .../com/fasterxml/jackson/databind/AnnotationIntrospector.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index 11d1c07627..6187088982 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -533,6 +533,7 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co /** * @deprecated Use {@link #findSubtypesByAnnotations(Annotated)} instead. */ + @Deprecated(since="2.14") public List findSubtypes(Annotated a) { return findSubtypesByAnnotations(a); } /** From 4417418a70f48ad801ec9913032ffe3ae4b2c6af Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Wed, 27 Jul 2022 08:50:21 -0500 Subject: [PATCH 07/14] spacing --- .../jackson/databind/jdk17/JDK17Util.java | 118 +-- .../databind/jdk17/SealedBasicsTest.java | 731 +++++++++--------- 2 files changed, 425 insertions(+), 424 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java b/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java index f84c61f9bc..946a19dc71 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java +++ b/src/main/java/com/fasterxml/jackson/databind/jdk17/JDK17Util.java @@ -12,74 +12,74 @@ * @since 2.14 */ public class JDK17Util { - public static Boolean isSealed(Class type) { - return SealedClassAccessor.instance().isSealed(type); - } + public static Boolean isSealed(Class type) { + return SealedClassAccessor.instance().isSealed(type); + } - public static Class[] getPermittedSubclasses(Class sealedType) { - return SealedClassAccessor.instance().getPermittedSubclasses(sealedType); - } + public static Class[] getPermittedSubclasses(Class sealedType) { + return SealedClassAccessor.instance().getPermittedSubclasses(sealedType); + } - static class SealedClassAccessor { - private final Method SEALED_IS_SEALED; - private final Method SEALED_GET_PERMITTED_SUBCLASSES; + static class SealedClassAccessor { + private final Method SEALED_IS_SEALED; + private final Method SEALED_GET_PERMITTED_SUBCLASSES; - private final static SealedClassAccessor INSTANCE; - private final static RuntimeException PROBLEM; + private final static SealedClassAccessor INSTANCE; + private final static RuntimeException PROBLEM; - static { - RuntimeException prob = null; - SealedClassAccessor inst = null; - try { - inst = new SealedClassAccessor(); - } catch (RuntimeException e) { - prob = e; - } - INSTANCE = inst; - PROBLEM = prob; - } + static { + RuntimeException prob = null; + SealedClassAccessor inst = null; + try { + inst = new SealedClassAccessor(); + } catch (RuntimeException e) { + prob = e; + } + INSTANCE = inst; + PROBLEM = prob; + } - private SealedClassAccessor() throws RuntimeException { - try { - SEALED_IS_SEALED = Class.class.getMethod("isSealed"); - SEALED_GET_PERMITTED_SUBCLASSES = Class.class.getMethod("getPermittedSubclasses"); - } catch (Exception e) { - throw new RuntimeException( - String.format("Failed to access Methods needed to support sealed classes: (%s) %s", - e.getClass().getName(), e.getMessage()), - e); - } - } + private SealedClassAccessor() throws RuntimeException { + try { + SEALED_IS_SEALED = Class.class.getMethod("isSealed"); + SEALED_GET_PERMITTED_SUBCLASSES = Class.class.getMethod("getPermittedSubclasses"); + } catch (Exception e) { + throw new RuntimeException( + String.format("Failed to access Methods needed to support sealed classes: (%s) %s", + e.getClass().getName(), e.getMessage()), + e); + } + } - public static SealedClassAccessor instance() { - if (PROBLEM != null) { - throw PROBLEM; - } - return INSTANCE; - } + public static SealedClassAccessor instance() { + if (PROBLEM != null) { + throw PROBLEM; + } + return INSTANCE; + } - public Boolean isSealed(Class type) throws IllegalArgumentException { - try { - return (Boolean) SEALED_IS_SEALED.invoke(type); - } catch (Exception e) { - if (NativeImageUtil.isUnsupportedFeatureError(e)) { - return null; + public Boolean isSealed(Class type) throws IllegalArgumentException { + try { + return (Boolean) SEALED_IS_SEALED.invoke(type); + } catch (Exception e) { + if (NativeImageUtil.isUnsupportedFeatureError(e)) { + return null; + } + throw new IllegalArgumentException( + "Failed to access sealedness of type " + ClassUtil.nameOf(type)); + } } - throw new IllegalArgumentException( - "Failed to access sealedness of type " + ClassUtil.nameOf(type)); - } - } - public Class[] getPermittedSubclasses(Class sealedType) throws IllegalArgumentException { - try { - return (Class[]) SEALED_GET_PERMITTED_SUBCLASSES.invoke(sealedType); - } catch (Exception e) { - if (NativeImageUtil.isUnsupportedFeatureError(e)) { - return null; + public Class[] getPermittedSubclasses(Class sealedType) throws IllegalArgumentException { + try { + return (Class[]) SEALED_GET_PERMITTED_SUBCLASSES.invoke(sealedType); + } catch (Exception e) { + if (NativeImageUtil.isUnsupportedFeatureError(e)) { + return null; + } + throw new IllegalArgumentException( + "Failed to access permitted subclasses of type " + ClassUtil.nameOf(sealedType)); + } } - throw new IllegalArgumentException( - "Failed to access permitted subclasses of type " + ClassUtil.nameOf(sealedType)); - } } - } } diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java index c6acb81c58..e8e913b5e5 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/SealedBasicsTest.java @@ -13,431 +13,432 @@ import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; public class SealedBasicsTest { - /** - * Our {@link ObjectMapper} uses the default configuration that explicitly enables the sealed - * classes subtype discovery. - */ - @SuppressWarnings("deprecation") - public static final ObjectMapper MAPPER = - new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, true); - - /** - * The "ExampleOne" objects test serialization of sealed classes without a JsonSubTypes - * annotation. - */ - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") - public static sealed class ExampleOne permits AlphaExampleOne, BravoExampleOne { - } - - /** - * Provide an explicit JsonTypeName here - */ - @JsonTypeName("alpha") - public static final class AlphaExampleOne extends ExampleOne { - private String alpha; - - public AlphaExampleOne() {} - - public AlphaExampleOne(String alpha) { - this.alpha = alpha; - } + /** + * Our {@link ObjectMapper} uses the default configuration that explicitly enables the sealed + * classes subtype discovery. + */ + @SuppressWarnings("deprecation") + public static final ObjectMapper MAPPER = + new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, true); /** - * @return the alpha + * The "ExampleOne" objects test serialization of sealed classes without a JsonSubTypes + * annotation. */ - public String getAlpha() { - return alpha; - } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + public static sealed class ExampleOne permits AlphaExampleOne, BravoExampleOne {} /** - * @param alpha the alpha to set + * Provide an explicit JsonTypeName here */ - public void setAlpha(String alpha) { - this.alpha = alpha; + @JsonTypeName("alpha") + public static final class AlphaExampleOne extends ExampleOne { + private String alpha; + + public AlphaExampleOne() {} + + public AlphaExampleOne(String alpha) { + this.alpha = alpha; + } + + /** + * @return the alpha + */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + */ + public void setAlpha(String alpha) { + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Objects.hash(alpha); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AlphaExampleOne other = (AlphaExampleOne) obj; + return Objects.equals(alpha, other.alpha); + } } - @Override - public int hashCode() { - return Objects.hash(alpha); + /** + * Use the default type name here + */ + public static final class BravoExampleOne extends ExampleOne { + public String bravo; + + public BravoExampleOne() {} + + public BravoExampleOne(String bravo) { + this.bravo = bravo; + } + + /** + * @return the bravo + */ + public String getBravo() { + return bravo; + } + + /** + * @param bravo the bravo to set + */ + public void setBravo(String bravo) { + this.bravo = bravo; + } + + @Override + public int hashCode() { + return Objects.hash(bravo); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BravoExampleOne other = (BravoExampleOne) obj; + return Objects.equals(bravo, other.bravo); + } } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AlphaExampleOne other = (AlphaExampleOne) obj; - return Objects.equals(alpha, other.alpha); + @Test + public void oneAlphaSerializationTest() throws IOException { + final String alpha = "apple"; + final String serialized = MAPPER.writeValueAsString(new AlphaExampleOne(alpha)); + assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); } - } - /** - * Use the default type name here - */ - public static final class BravoExampleOne extends ExampleOne { - public String bravo; + @Test + public void oneAlphaDeserializationTest() throws IOException { + final String alpha = "apple"; + ExampleOne one = + MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleOne.class); + assertThat(one, is(new AlphaExampleOne(alpha))); + } - public BravoExampleOne() {} + @Test + public void oneBravoDeserializationTest() throws IOException { + final String bravo = "blueberry"; + ExampleOne one = MAPPER.readValue( + "{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}", + ExampleOne.class); + assertThat(one, is(new BravoExampleOne(bravo))); + } - public BravoExampleOne(String bravo) { - this.bravo = bravo; + @Test + public void oneBravoSerializationTest() throws IOException { + final String bravo = "blueberry"; + final String serialized = MAPPER.writeValueAsString(new BravoExampleOne(bravo)); + assertThat(serialized, + is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); } /** - * @return the bravo + * Jackson is quite smart and still picks up the supertype relationship during serialization, even + * without automatic subtype discovery. */ - public String getBravo() { - return bravo; + @Test + @SuppressWarnings("deprecation") + public void oneAlphaSerializationTestDiscoveryDisabled() throws IOException { + final String bravo = "blueberry"; + final String serialized = + new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, false) + .writeValueAsString(new BravoExampleOne(bravo)); + assertThat(serialized, + is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); } /** - * @param bravo the bravo to set + * Jackson should not pick up the subtype relationship without automatic discovery. */ - public void setBravo(String bravo) { - this.bravo = bravo; - } - - @Override - public int hashCode() { - return Objects.hash(bravo); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - BravoExampleOne other = (BravoExampleOne) obj; - return Objects.equals(bravo, other.bravo); - } - } - - @Test - public void oneAlphaSerializationTest() throws IOException { - final String alpha = "apple"; - final String serialized = MAPPER.writeValueAsString(new AlphaExampleOne(alpha)); - assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); - } - - @Test - public void oneAlphaDeserializationTest() throws IOException { - final String alpha = "apple"; - ExampleOne one = - MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleOne.class); - assertThat(one, is(new AlphaExampleOne(alpha))); - } - - @Test - public void oneBravoDeserializationTest() throws IOException { - final String bravo = "blueberry"; - ExampleOne one = MAPPER.readValue( - "{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}", - ExampleOne.class); - assertThat(one, is(new BravoExampleOne(bravo))); - } - - @Test - public void oneBravoSerializationTest() throws IOException { - final String bravo = "blueberry"; - final String serialized = MAPPER.writeValueAsString(new BravoExampleOne(bravo)); - assertThat(serialized, - is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); - } - - /** - * Jackson is quite smart and still picks up the supertype relationship during serialization, even - * without automatic subtype discovery. - */ - @Test - @SuppressWarnings("deprecation") - public void oneAlphaSerializationTestDiscoveryDisabled() throws IOException { - final String bravo = "blueberry"; - final String serialized = + @SuppressWarnings("deprecation") + @Test(expected = InvalidTypeIdException.class) + public void oneAlphaDeserializationTestDiscoveryDisabled() throws IOException { new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, false) - .writeValueAsString(new BravoExampleOne(bravo)); - assertThat(serialized, - is("{\"type\":\"SealedBasicsTest$BravoExampleOne\",\"bravo\":\"" + bravo + "\"}")); - } - - /** - * Jackson should not pick up the subtype relationship without automatic discovery. - */ - @SuppressWarnings("deprecation") - @Test(expected = InvalidTypeIdException.class) - public void oneAlphaDeserializationTestDiscoveryDisabled() throws IOException { - new ObjectMapper().configure(MapperFeature.DISCOVER_SEALED_CLASS_PERMITTED_SUBCLASSES, false) - .readValue("{\"type\":\"alpha\",\"alpha\":\"apple\"}", ExampleOne.class); - } - - /** - * The "ExampleTwo" objects test serialization of sealed classes with a JsonSubTypes annotation, - * which is the existing approach that must not break. - */ - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") - @JsonSubTypes({@JsonSubTypes.Type(AlphaExampleTwo.class), - @JsonSubTypes.Type(value = BravoExampleTwo.class, name = "bravo")}) - public static sealed class ExampleTwo permits AlphaExampleTwo, BravoExampleTwo { - } - - /** - * Provide an explicit JsonTypeName here - */ - @JsonTypeName("alpha") - public static final class AlphaExampleTwo extends ExampleTwo { - private String alpha; - - public AlphaExampleTwo() {} - - public AlphaExampleTwo(String alpha) { - this.alpha = alpha; + .readValue("{\"type\":\"alpha\",\"alpha\":\"apple\"}", ExampleOne.class); } /** - * @return the alpha + * The "ExampleTwo" objects test serialization of sealed classes with a JsonSubTypes annotation, + * which is the existing approach that must not break. */ - public String getAlpha() { - return alpha; - } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(AlphaExampleTwo.class), + @JsonSubTypes.Type(value = BravoExampleTwo.class, name = "bravo") + }) + public static sealed class ExampleTwo permits AlphaExampleTwo, BravoExampleTwo {} /** - * @param alpha the alpha to set + * Provide an explicit JsonTypeName here */ - public void setAlpha(String alpha) { - this.alpha = alpha; - } - - @Override - public int hashCode() { - return Objects.hash(alpha); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AlphaExampleTwo other = (AlphaExampleTwo) obj; - return Objects.equals(alpha, other.alpha); + @JsonTypeName("alpha") + public static final class AlphaExampleTwo extends ExampleTwo { + private String alpha; + + public AlphaExampleTwo() {} + + public AlphaExampleTwo(String alpha) { + this.alpha = alpha; + } + + /** + * @return the alpha + */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + */ + public void setAlpha(String alpha) { + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Objects.hash(alpha); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AlphaExampleTwo other = (AlphaExampleTwo) obj; + return Objects.equals(alpha, other.alpha); + } } - } - /** - * Use the default type name here - */ - public static final class BravoExampleTwo extends ExampleTwo { - public String bravo; - - public BravoExampleTwo() {} - - public BravoExampleTwo(String bravo) { - this.bravo = bravo; - } - /** - * @return the bravo + * Use the default type name here */ - public String getBravo() { - return bravo; + public static final class BravoExampleTwo extends ExampleTwo { + public String bravo; + + public BravoExampleTwo() {} + + public BravoExampleTwo(String bravo) { + this.bravo = bravo; + } + + /** + * @return the bravo + */ + public String getBravo() { + return bravo; + } + + /** + * @param bravo the bravo to set + */ + public void setBravo(String bravo) { + this.bravo = bravo; + } + + @Override + public int hashCode() { + return Objects.hash(bravo); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BravoExampleTwo other = (BravoExampleTwo) obj; + return Objects.equals(bravo, other.bravo); + } + } - /** - * @param bravo the bravo to set - */ - public void setBravo(String bravo) { - this.bravo = bravo; + @Test + public void twoAlphaSerializationTest() throws IOException { + final String alpha = "apple"; + final String serialized = MAPPER.writeValueAsString(new AlphaExampleTwo(alpha)); + assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); } - @Override - public int hashCode() { - return Objects.hash(bravo); + @Test + public void twoAlphaDeserializationTest() throws IOException { + final String alpha = "apple"; + ExampleTwo two = + MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleTwo.class); + assertThat(two, is(new AlphaExampleTwo(alpha))); } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - BravoExampleTwo other = (BravoExampleTwo) obj; - return Objects.equals(bravo, other.bravo); + @Test + public void twoBravoDeserializationTest() throws IOException { + final String bravo = "blueberry"; + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + ExampleTwo two = + MAPPER.readValue("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}", ExampleTwo.class); + assertThat(two, is(new BravoExampleTwo(bravo))); } - } - - @Test - public void twoAlphaSerializationTest() throws IOException { - final String alpha = "apple"; - final String serialized = MAPPER.writeValueAsString(new AlphaExampleTwo(alpha)); - assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); - } - - @Test - public void twoAlphaDeserializationTest() throws IOException { - final String alpha = "apple"; - ExampleTwo two = - MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleTwo.class); - assertThat(two, is(new AlphaExampleTwo(alpha))); - } - - @Test - public void twoBravoDeserializationTest() throws IOException { - final String bravo = "blueberry"; - // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. - ExampleTwo two = - MAPPER.readValue("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}", ExampleTwo.class); - assertThat(two, is(new BravoExampleTwo(bravo))); - } - - @Test - public void twoBravoSerializationTest() throws IOException { - final String bravo = "blueberry"; - final String serialized = MAPPER.writeValueAsString(new BravoExampleTwo(bravo)); - // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. - assertThat(serialized, is("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}")); - } - - /** - * The "ExampleTwo" objects test serialization of conventional classes with a JsonSubTypes - * annotation, which is the existing approach that must not break. - */ - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") - @JsonSubTypes({@JsonSubTypes.Type(AlphaExampleThree.class), - @JsonSubTypes.Type(value = BravoExampleThree.class, name = "bravo")}) - public static class ExampleThree { - } - - /** - * Provide an explicit JsonTypeName here - */ - @JsonTypeName("alpha") - public static final class AlphaExampleThree extends ExampleThree { - private String alpha; - - public AlphaExampleThree() {} - - public AlphaExampleThree(String alpha) { - this.alpha = alpha; + @Test + public void twoBravoSerializationTest() throws IOException { + final String bravo = "blueberry"; + final String serialized = MAPPER.writeValueAsString(new BravoExampleTwo(bravo)); + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + assertThat(serialized, is("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}")); } /** - * @return the alpha + * The "ExampleTwo" objects test serialization of conventional classes with a JsonSubTypes + * annotation, which is the existing approach that must not break. */ - public String getAlpha() { - return alpha; - } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(AlphaExampleThree.class), + @JsonSubTypes.Type(value = BravoExampleThree.class, name = "bravo") + }) + public static class ExampleThree {} /** - * @param alpha the alpha to set + * Provide an explicit JsonTypeName here */ - public void setAlpha(String alpha) { - this.alpha = alpha; - } - - @Override - public int hashCode() { - return Objects.hash(alpha); + @JsonTypeName("alpha") + public static final class AlphaExampleThree extends ExampleThree { + private String alpha; + + public AlphaExampleThree() {} + + public AlphaExampleThree(String alpha) { + this.alpha = alpha; + } + + /** + * @return the alpha + */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + */ + public void setAlpha(String alpha) { + this.alpha = alpha; + } + + @Override + public int hashCode() { + return Objects.hash(alpha); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AlphaExampleThree other = (AlphaExampleThree) obj; + return Objects.equals(alpha, other.alpha); + } } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AlphaExampleThree other = (AlphaExampleThree) obj; - return Objects.equals(alpha, other.alpha); - } - } - - - /** - * Use the default type name here - */ - public static final class BravoExampleThree extends ExampleThree { - public String bravo; - - public BravoExampleThree() {} - - public BravoExampleThree(String bravo) { - this.bravo = bravo; - } /** - * @return the bravo + * Use the default type name here */ - public String getBravo() { - return bravo; + public static final class BravoExampleThree extends ExampleThree { + public String bravo; + + public BravoExampleThree() {} + + public BravoExampleThree(String bravo) { + this.bravo = bravo; + } + + /** + * @return the bravo + */ + public String getBravo() { + return bravo; + } + + /** + * @param bravo the bravo to set + */ + public void setBravo(String bravo) { + this.bravo = bravo; + } + + @Override + public int hashCode() { + return Objects.hash(bravo); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BravoExampleThree other = (BravoExampleThree) obj; + return Objects.equals(bravo, other.bravo); + } + } - /** - * @param bravo the bravo to set - */ - public void setBravo(String bravo) { - this.bravo = bravo; + @Test + public void threeAlphaSerializationTest() throws IOException { + final String alpha = "apple"; + final String serialized = MAPPER.writeValueAsString(new AlphaExampleThree(alpha)); + assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); } - @Override - public int hashCode() { - return Objects.hash(bravo); + @Test + public void threeAlphaDeserializationTest() throws IOException { + final String alpha = "apple"; + ExampleThree three = + MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleThree.class); + assertThat(three, is(new AlphaExampleThree(alpha))); } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - BravoExampleThree other = (BravoExampleThree) obj; - return Objects.equals(bravo, other.bravo); + @Test + public void threeBravoDeserializationTest() throws IOException { + final String bravo = "blueberry"; + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + ExampleThree three = + MAPPER.readValue("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}", ExampleThree.class); + assertThat(three, is(new BravoExampleThree(bravo))); } - } - - @Test - public void threeAlphaSerializationTest() throws IOException { - final String alpha = "apple"; - final String serialized = MAPPER.writeValueAsString(new AlphaExampleThree(alpha)); - assertThat(serialized, is("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}")); - } - - @Test - public void threeAlphaDeserializationTest() throws IOException { - final String alpha = "apple"; - ExampleThree three = - MAPPER.readValue("{\"type\":\"alpha\",\"alpha\":\"" + alpha + "\"}", ExampleThree.class); - assertThat(three, is(new AlphaExampleThree(alpha))); - } - - @Test - public void threeBravoDeserializationTest() throws IOException { - final String bravo = "blueberry"; - // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. - ExampleThree three = - MAPPER.readValue("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}", ExampleThree.class); - assertThat(three, is(new BravoExampleThree(bravo))); - } - - @Test - public void threeBravoSerializationTest() throws IOException { - final String bravo = "blueberry"; - final String serialized = MAPPER.writeValueAsString(new BravoExampleThree(bravo)); - // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. - assertThat(serialized, is("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}")); - } -} + @Test + public void threeBravoSerializationTest() throws IOException { + final String bravo = "blueberry"; + final String serialized = MAPPER.writeValueAsString(new BravoExampleThree(bravo)); + // Make sure we pick up the "bravo" name from the @@JsonSubTypes annotation. + assertThat(serialized, is("{\"type\":\"bravo\",\"bravo\":\"" + bravo + "\"}")); + } +} \ No newline at end of file From 02c36a13b6288689f76f9eab7886dd89141cff11 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Wed, 27 Jul 2022 08:50:25 -0500 Subject: [PATCH 08/14] fix imports --- .../fasterxml/jackson/databind/records/RecordBasicsTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java b/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java index 9b2ff0a212..4918ae5ddd 100644 --- a/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java +++ b/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java @@ -1,7 +1,10 @@ package com.fasterxml.jackson.databind.records; import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.*; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.util.ClassUtil; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; From 07c9df087a73f0fca1c21b87d8cfe9173e09378c Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Wed, 27 Jul 2022 13:44:05 -0500 Subject: [PATCH 09/14] fix @Deprecated compile error --- .../com/fasterxml/jackson/databind/AnnotationIntrospector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index 6187088982..79564d62d6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -533,7 +533,7 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co /** * @deprecated Use {@link #findSubtypesByAnnotations(Annotated)} instead. */ - @Deprecated(since="2.14") + @Deprecated public List findSubtypes(Annotated a) { return findSubtypesByAnnotations(a); } /** From bb3ce82b5dd699a3d76ef6c4d27605ffa72dc222 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Thu, 28 Jul 2022 13:05:11 -0500 Subject: [PATCH 10/14] tweak deprecation --- .../fasterxml/jackson/databind/AnnotationIntrospector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index 79564d62d6..f23bd69ad2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -531,9 +531,9 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co } /** - * @deprecated Use {@link #findSubtypesByAnnotations(Annotated)} instead. + * @deprecated Since 2.14, use {@link #findSubtypesByAnnotations(Annotated)} instead. */ - @Deprecated + @Deprecated // since 2.14 public List findSubtypes(Annotated a) { return findSubtypesByAnnotations(a); } /** From 821a467e51cc32dede0af31786a79b4f75426523 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Wed, 3 Aug 2022 10:26:06 -0500 Subject: [PATCH 11/14] move failing tests into failing package --- pom.xml | 1 + .../failing/RecordBasicsTestFailing.java | 47 +++++++++++++++++++ .../failing/RecordUpdate3079TestFailing.java | 42 +++++++++++++++++ .../failing/RecordWithJsonNaming3102Test.java | 1 - .../databind/records/RecordBasicsTest.java | 18 ------- .../records/RecordUpdate3079Test.java | 21 --------- 6 files changed, 90 insertions(+), 40 deletions(-) create mode 100644 src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordBasicsTestFailing.java create mode 100644 src/test-jdk14/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079TestFailing.java diff --git a/pom.xml b/pom.xml index 9f7a867634..a391aab836 100644 --- a/pom.xml +++ b/pom.xml @@ -167,6 +167,7 @@ com/fasterxml/jackson/failing/*.java + com/fasterxml/jackson/databind/failing/*.java