diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java index 0dfcda70f..9269fa4a7 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/Flags.java @@ -34,6 +34,7 @@ public final class Flags { public static final int TRANSIENT = 4096; public static final int VARARGS = 8192; public static final int VOLATILE = 16384; + public static final int INHERITED_ANNOTATION = 32768; public static final int PACKAGE_PRIVATE = 0; public static final int PRIVATE = 1; diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 670797d1f..cfe4f416e 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -58,6 +59,7 @@ public class TClass extends TObject implements TAnnotatedElement, TType { String canonicalName; private PlatformClass platformClass; private TAnnotation[] annotationsCache; + private TAnnotation[] declaredAnnotationsCache; private Map, TAnnotation> annotationsByType; private TField[] declaredFields; private TField[] fields; @@ -744,14 +746,33 @@ public class TClass extends TObject implements TAnnotatedElement, TType { @Override public TAnnotation[] getAnnotations() { if (annotationsCache == null) { - annotationsCache = (TAnnotation[]) Platform.getAnnotations(getPlatformClass()); + TClass cls = this; + var initial = true; + var map = new LinkedHashMap, TAnnotation>(); + while (cls != null) { + for (var annot : cls.getDeclaredAnnotations()) { + var platformClass = ((TClass) (Object) annot.annotationType()).platformClass; + if (initial || (platformClass.getMetadata().getFlags() & Flags.INHERITED_ANNOTATION) != 0) { + map.putIfAbsent(annot.annotationType(), annot); + } + } + cls = cls.getSuperclass(); + initial = false; + } + annotationsCache = map.values().toArray(new TAnnotation[0]); } return annotationsCache.clone(); } @Override public TAnnotation[] getDeclaredAnnotations() { - return getAnnotations(); + if (declaredAnnotationsCache == null) { + declaredAnnotationsCache = (TAnnotation[]) Platform.getAnnotations(getPlatformClass()); + if (declaredAnnotationsCache == null) { + declaredAnnotationsCache = new TAnnotation[0]; + } + } + return declaredAnnotationsCache.clone(); } private void ensureAnnotationsByType() { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index d6e339133..f281068c0 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -17,6 +17,8 @@ package org.teavm.backend.javascript.rendering; import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntMap; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; @@ -517,7 +519,16 @@ public class Renderer implements RenderingManager { } writer.append("],").ws(); - writer.append(ElementModifier.pack(cls.readModifiers())).append(',').ws(); + var flags = ElementModifier.pack(cls.readModifiers()); + if (cls.hasModifier(ElementModifier.ANNOTATION)) { + var retention = cls.getAnnotations().get(Retention.class.getName()); + if (retention != null && retention.getValue("value").getEnumValue().getFieldName().equals("RUNTIME")) { + if (cls.getAnnotations().get(Inherited.class.getName()) != null) { + flags |= 32768; + } + } + } + writer.append(flags).append(',').ws(); writer.append(cls.getLevel().ordinal()).append(',').ws(); if (!requiredMetadata.enclosingClass() && !requiredMetadata.declaringClass() diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java index 55d3be7b4..fef9db1e8 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java @@ -20,11 +20,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Stream; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.SkipPlatform; @@ -182,9 +183,8 @@ public class ClassTest { @Test @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) public void annotationsExposed() { - Annotation[] annotations = A.class.getAnnotations(); - assertEquals(1, annotations.length); - assertTrue(TestAnnot.class.isAssignableFrom(annotations[0].getClass())); + var annotations = A.class.getAnnotations(); + assertTrue(Stream.of(annotations).anyMatch(a -> a instanceof TestAnnot)); } @Test @@ -225,6 +225,23 @@ public class ClassTest { Set.of(ClassWithInterfaces.class.getInterfaces())); } + @Test + @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) + public void inheritedAnnotation() { + assertTrue(A.class.isAnnotationPresent(InheritedAnnot.class)); + assertTrue(A.class.isAnnotationPresent(TestAnnot.class)); + assertTrue(ASub.class.isAnnotationPresent(InheritedAnnot.class)); + assertFalse(ASub.class.isAnnotationPresent(TestAnnot.class)); + assertTrue(TestInterface1.class.isAnnotationPresent(InheritedAnnot.class)); + assertFalse(ClassWithInterfaces.class.isAnnotationPresent(InheritedAnnot.class)); + + var annotationSet = Set.of(ASub.class.getAnnotations()); + assertTrue(annotationSet.stream().anyMatch(a -> a instanceof InheritedAnnot)); + assertFalse(annotationSet.stream().anyMatch(a -> a instanceof TestAnnot)); + + assertEquals(0, ASub.class.getDeclaredAnnotations().length); + } + private static class SuperclassWithoutInterfaces { } @@ -232,6 +249,7 @@ public class ClassTest { implements TestInterface1, TestInterface2 { } + @InheritedAnnot private interface TestInterface1 { } @@ -239,9 +257,13 @@ public class ClassTest { } @TestAnnot + @InheritedAnnot private static class A { } + private static class ASub extends A { + } + @AnnotWithDefaultField private static class B { } @@ -265,6 +287,11 @@ public class ClassTest { int x() default 2; } + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface InheritedAnnot { + } + @Retention(RetentionPolicy.RUNTIME) @interface AnnotWithVariousFields { boolean a();