From 6e0ad46c9538cf14209b9cac3d31bf03a1ab5945 Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:46:34 +0100 Subject: [PATCH 1/2] Add so that the Java Deprecated annotation is considered a processable annotation type --- .../v3/core/converter/AnnotatedType.java | 15 ++++++++----- .../v3/core/converting/AnnotatedTypeTest.java | 22 +++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java index 5136f16506..537a9d5dde 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java @@ -263,8 +263,8 @@ private List getProcessedAnnotations(Annotation[] annotations) { } return Arrays.stream(annotations) .filter(a -> { - String pkg = a.annotationType().getPackage().getName(); - return !pkg.startsWith("java.") && !pkg.startsWith("jdk.") && !pkg.startsWith("sun."); + Package pkg = a.annotationType().getPackage(); + return a.annotationType().equals(Deprecated.class) || processableAnnotationPackage(pkg); }) .sorted(Comparator.comparing(a -> a.annotationType().getName())) .collect(Collectors.toList()); @@ -275,13 +275,13 @@ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AnnotatedType)) return false; AnnotatedType that = (AnnotatedType) o; - List thisAnnotatinons = getProcessedAnnotations(this.ctxAnnotations); - List thatAnnotatinons = getProcessedAnnotations(that.ctxAnnotations); + List thisAnnotations = getProcessedAnnotations(this.ctxAnnotations); + List thatAnnotations = getProcessedAnnotations(that.ctxAnnotations); return includePropertiesWithoutJSONView == that.includePropertiesWithoutJSONView && schemaProperty == that.schemaProperty && isSubtype == that.isSubtype && Objects.equals(type, that.type) && - Objects.equals(thisAnnotatinons, thatAnnotatinons) && + Objects.equals(thisAnnotations, thatAnnotations) && Objects.equals(jsonViewAnnotation, that.jsonViewAnnotation) && (!schemaProperty || Objects.equals(propertyName, that.propertyName)); } @@ -291,4 +291,9 @@ public int hashCode() { List processedAnnotations = getProcessedAnnotations(this.ctxAnnotations); return Objects.hash(type, jsonViewAnnotation, includePropertiesWithoutJSONView, processedAnnotations, schemaProperty, isSubtype, schemaProperty ? propertyName : null); } + + private boolean processableAnnotationPackage(Package pkg) { + String pkgName = pkg.getName(); + return !pkgName.startsWith("java.") && !pkgName.startsWith("jdk.") && !pkgName.startsWith("sun."); + } } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java index 00cb6ad500..ee7e07f871 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeTest.java @@ -3,11 +3,7 @@ import io.swagger.v3.core.converter.AnnotatedType; import org.testng.annotations.Test; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; @@ -49,20 +45,22 @@ public void testEqualsAndHashCode_shouldBeOrderInsensitiveForAnnotations() { } /** - * Tests that JDK/internal annotations are filtered out for equals() and hashCode() comparison. + * Tests that the JDK Deprecated annotation is considered for equals() and hashCode() comparison. */ @Test - public void testEqualsAndHashCode_shouldIgnoreJdkInternalAnnotations() { + public void testEqualsAndHashCode_shouldIncludeJdkDeprecatedAnnotations() { Annotation annA = getAnnotationInstance(TestAnnA.class); Annotation deprecated = getAnnotationInstance(Deprecated.class); AnnotatedType typeWithUserAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annA}); - AnnotatedType typeWithJdkAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annA, deprecated}); + AnnotatedType typeWithJdkAnnAndUserAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{deprecated, annA}); + AnnotatedType typeWithUserAnnAndJdkAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{annA, deprecated}); AnnotatedType typeWithOnlyJdkAnn = new AnnotatedType(String.class).ctxAnnotations(new Annotation[]{deprecated}); AnnotatedType typeWithNoAnn = new AnnotatedType(String.class); - assertEquals(typeWithUserAnn, typeWithJdkAnn, "JDK annotations should be ignored in equality comparison."); - assertEquals(typeWithUserAnn.hashCode(), typeWithJdkAnn.hashCode(), "JDK annotations should be ignored in hashCode calculation."); - assertEquals(typeWithOnlyJdkAnn, typeWithNoAnn, "An object with only JDK annotations should be equal to one with no annotations."); - assertEquals(typeWithOnlyJdkAnn.hashCode(), typeWithNoAnn.hashCode(), "The hash code of an object with only JDK annotations should be the same as one with no annotations."); + assertNotEquals(typeWithUserAnn, typeWithJdkAnnAndUserAnn, "JDK Deprecated annotation should be included in equality comparison."); + assertNotEquals(typeWithUserAnn.hashCode(), typeWithJdkAnnAndUserAnn.hashCode(), "JDK Deprecated annotation should be included in hashCode calculation."); + assertNotEquals(typeWithOnlyJdkAnn, typeWithNoAnn, "An object with only JDK Deprecated annotation should not be equal to one with no annotations."); + assertNotEquals(typeWithOnlyJdkAnn.hashCode(), typeWithNoAnn.hashCode(), "The hash code of an object with only a JDK Deprecated annotation should not be the same as one with no annotations."); + assertEquals(typeWithJdkAnnAndUserAnn, typeWithUserAnnAndJdkAnn, "Hash codes should be equal even if annotation order is different."); } /** From 5f5664506c1d8cedf5bff61bdcad62faa069dda3 Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Thu, 26 Mar 2026 22:46:10 +0100 Subject: [PATCH 2/2] Add test showing that we get two cache entries if a field has a deprecated annotation --- .../converting/AnnotatedTypeCachingTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java index c79340ca80..7b31e6218c 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/AnnotatedTypeCachingTest.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.models.media.Schema; import org.testng.annotations.Test; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.Iterator; import java.util.Set; @@ -69,4 +70,57 @@ public void testCacheHitsForRepeatedStringTypeWithCorrectedEquals() throws Excep .count(); assertEquals(stringTypeCount, 1, "With the correct equals/hashCode, String type should be added to the cache only once."); } + + @Test + @SuppressWarnings("unchecked") + public void testNoCacheHitForAFieldThatIsMarkedAsDeprecated() throws Exception { + ModelConverterContextImpl context = new ModelConverterContextImpl(new FooBarDummyModelConverter()); + Schema fooSchema = context.resolve(new AnnotatedType(Foo.class)); + assertNotNull(fooSchema); + Field processedTypesField = ModelConverterContextImpl.class.getDeclaredField("processedTypes"); + processedTypesField.setAccessible(true); + Set processedTypes = (Set) processedTypesField.get(context); + long stringTypeCount = processedTypes.stream() + .filter(annotatedType -> annotatedType.getType().equals(String.class)) + .count(); + assertEquals(stringTypeCount, 2, "With the correct equals/hashCode, String type should be added to the cache twice, since one of them is deprecated."); + } + + private static class FooBarDummyModelConverter implements ModelConverter { + @Override + public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { + if (type.getType().equals(Foo.class)) { + context.resolve(new AnnotatedType(String.class).propertyName("fizz").ctxAnnotations(new Annotation[]{getAnnotationInstance(Deprecated.class)})); + context.resolve(new AnnotatedType(String.class).propertyName("buzz")); + context.resolve(new AnnotatedType(Bar.class).propertyName("bar")); + return new Schema(); + } + if (type.getType().equals(Bar.class)) { + context.resolve(new AnnotatedType(String.class).propertyName("fizz")); + context.resolve(new AnnotatedType(String.class).propertyName("buzz")); + return new Schema(); + } + return new Schema(); + } + } + + private static Annotation getAnnotationInstance(Class clazz) { + try { + return Foo.class.getDeclaredField("fizz").getAnnotation(clazz); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + static class Foo { + @Deprecated + public String fizz; + public String buzz; + public Bar bar; + } + + static class Bar { + public String fizz; + public String buzz; + } } \ No newline at end of file