From b5f7a64a2681908ea02a47c034cf59e2a18631ad Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 27 Feb 2025 10:44:54 +0100 Subject: [PATCH 1/3] fix how the EL integration is tested now that the EL methods are gone from BeanManager --- api/pom.xml | 5 +++ impl/pom.xml | 10 +++++ .../java/org/jboss/cdi/tck/AbstractTest.java | 8 ++++ .../beanManager/BeanManagerTest.java | 7 ---- .../beanManager/ELAwareBeanManagerTest.java | 42 +++++++++++++++++++ .../el/WrapExpressionFactoryTest.java | 3 +- .../full/lookup/el/ResolutionByNameTest.java | 23 +++++----- pom.xml | 8 +++- 8 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/ELAwareBeanManagerTest.java diff --git a/api/pom.xml b/api/pom.xml index 8b7ea4e09..1d034e177 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,6 +24,11 @@ jakarta.el jakarta.el-api + + + jakarta.enterprise + jakarta.enterprise.cdi-el-api + diff --git a/impl/pom.xml b/impl/pom.xml index 898384bb1..afc630b73 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -18,6 +18,16 @@ jakarta.enterprise.cdi-api + + jakarta.enterprise + jakarta.enterprise.cdi-el-api + + + + jakarta.el + jakarta.el-api + + jakarta.enterprise cdi-tck-api diff --git a/impl/src/main/java/org/jboss/cdi/tck/AbstractTest.java b/impl/src/main/java/org/jboss/cdi/tck/AbstractTest.java index 72ff88b92..1cbd5f346 100644 --- a/impl/src/main/java/org/jboss/cdi/tck/AbstractTest.java +++ b/impl/src/main/java/org/jboss/cdi/tck/AbstractTest.java @@ -31,6 +31,7 @@ import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanContainer; import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.el.ELAwareBeanManager; import jakarta.enterprise.util.TypeLiteral; import jakarta.inject.Inject; @@ -74,6 +75,13 @@ protected BeanContainer getCurrentBeanContainer() { return beanManager; } + protected ELAwareBeanManager getCurrentELAwareManager() { + if (beanManager instanceof ELAwareBeanManager bm) { + return bm; + } + throw new IllegalStateException("Current BeanManager is not ELAwareBeanManager"); + } + protected byte[] passivate(Object instance) throws IOException { return getCurrentConfiguration().getBeans().passivate(instance); } diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/BeanManagerTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/BeanManagerTest.java index 1190254c2..56aa60aa3 100644 --- a/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/BeanManagerTest.java +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/BeanManagerTest.java @@ -21,7 +21,6 @@ import static org.jboss.cdi.tck.cdi.Sections.BEANMANAGER; import static org.jboss.cdi.tck.cdi.Sections.BM_DETERMINING_ANNOTATION; import static org.jboss.cdi.tck.cdi.Sections.BM_OBTAIN_ANNOTATEDTYPE; -import static org.jboss.cdi.tck.cdi.Sections.BM_OBTAIN_ELRESOLVER; import static org.jboss.cdi.tck.cdi.Sections.BM_OBTAIN_EXTENSION; import static org.jboss.cdi.tck.cdi.Sections.BM_OBTAIN_INJECTIONTARGET; import static org.jboss.cdi.tck.cdi.Sections.BM_VALIDATE_IP; @@ -137,12 +136,6 @@ public void testDetermineScopeType() { assertFalse(getCurrentManager().isPassivatingScope(DummyScoped.class)); } - @Test - @SpecAssertion(section = BM_OBTAIN_ELRESOLVER, id = "a") - public void testGetELResolver() { - assertNotNull(getCurrentManager().getELResolver()); - } - @Test @SpecAssertions({ @SpecAssertion(section = BM_OBTAIN_ANNOTATEDTYPE, id = "a") }) public void testObtainingAnnotatedType() { diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/ELAwareBeanManagerTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/ELAwareBeanManagerTest.java new file mode 100644 index 000000000..e52261b6d --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/ELAwareBeanManagerTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025, Red Hat, Inc., and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.cdi.tck.tests.full.extensions.beanManager; + +import static org.jboss.cdi.tck.TestGroups.CDI_FULL; +import static org.jboss.cdi.tck.cdi.Sections.BM_OBTAIN_ELRESOLVER; +import static org.testng.Assert.assertNotNull; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@Test(groups = CDI_FULL) +@SpecVersion(spec = "cdi", version = "5.0") +public class ELAwareBeanManagerTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder().withTestClassPackage(ELAwareBeanManagerTest.class).build(); + } + + @Test + @SpecAssertion(section = BM_OBTAIN_ELRESOLVER, id = "a") + public void testGetELResolver() { + assertNotNull(getCurrentELAwareManager().getELResolver()); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/el/WrapExpressionFactoryTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/el/WrapExpressionFactoryTest.java index cbabe276b..bf2a8676c 100644 --- a/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/el/WrapExpressionFactoryTest.java +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/extensions/beanManager/el/WrapExpressionFactoryTest.java @@ -68,7 +68,8 @@ public void testWrapping() { ActionSequence.reset(); // Wrap custom expression factory - ExpressionFactory wrappedExpressionFactory = getCurrentManager().wrapExpressionFactory(new DummyExpressionFactory()); + ExpressionFactory wrappedExpressionFactory = getCurrentELAwareManager() + .wrapExpressionFactory(new DummyExpressionFactory()); // Create method expression and invoke it with supplied EL context (porting package) MethodExpression methodExpression = wrappedExpressionFactory.createMethodExpression(null, "foo.test", String.class, diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/lookup/el/ResolutionByNameTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/lookup/el/ResolutionByNameTest.java index d8d1aed41..21f1424ca 100644 --- a/impl/src/main/java/org/jboss/cdi/tck/tests/full/lookup/el/ResolutionByNameTest.java +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/lookup/el/ResolutionByNameTest.java @@ -48,9 +48,8 @@ public static WebArchive createTestArchive() { @Test @SpecAssertion(section = DEPENDENT_SCOPE_EL, id = "a") public void testQualifiedNameLookup() { - assertTrue(getCurrentConfiguration().getEl() - .evaluateValueExpression(getCurrentManager(), - "#{(game.value == 'foo' and game.value == 'foo') ? game.value == 'foo' : false}", Boolean.class)); + assertTrue(getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), + "#{(game.value == 'foo' and game.value == 'foo') ? game.value == 'foo' : false}", Boolean.class)); assertEquals(getContextualReference(Counter.class).getCount(), 1); } @@ -61,8 +60,8 @@ public void testContextCreatesNewInstanceForInjection() { Context requestContext = getCurrentManager().getContext(RequestScoped.class); Bean tunaBean = getBeans(Tuna.class).iterator().next(); assertNull(requestContext.get(tunaBean)); - TunaFarm tunaFarm = getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), "#{tunaFarm}", - TunaFarm.class); + TunaFarm tunaFarm = getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), + "#{tunaFarm}", TunaFarm.class); assertNotNull(tunaFarm.tuna); long timestamp = tunaFarm.tuna.getTimestamp(); // Lookup once again - do not create new instance - contextual instance already exists @@ -74,9 +73,8 @@ public void testContextCreatesNewInstanceForInjection() { @Test @SpecAssertion(section = EL, id = "c") public void testUnresolvedNameReturnsNull() { - assertNull( - getCurrentManager().getELResolver().getValue( - getCurrentConfiguration().getEl().createELContext(getCurrentManager()), null, "nonExistingTuna")); + assertNull(getCurrentELAwareManager().getELResolver().getValue( + getCurrentConfiguration().getEl().createELContext(getCurrentManager()), null, "nonExistingTuna")); } @Test @@ -84,15 +82,14 @@ public void testUnresolvedNameReturnsNull() { public void testELResolverReturnsContextualInstance() { Salmon salmon = getContextualReference(Salmon.class); salmon.setAge(3); - assertEquals( - getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), "#{salmon.age}", Integer.class), - new Integer(3)); + assertEquals(getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), + "#{salmon.age}", Integer.class), 3); } @Test @SpecAssertions({ @SpecAssertion(section = NAMES, id = "a") }) public void testBeanNameWithSeparatedListOfELIdentifiers() { - assertNotNull(getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), "#{magic.golden.fish}", - GoldenFish.class)); + assertNotNull(getCurrentConfiguration().getEl().evaluateValueExpression(getCurrentManager(), + "#{magic.golden.fish}", GoldenFish.class)); } } diff --git a/pom.xml b/pom.xml index a61d63a67..a770d9084 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,7 @@ - 4.1.0 + 5.0.0.Alpha1-SNAPSHOT 17 3.0.0 @@ -149,6 +149,12 @@ ${cdi.api.version} + + jakarta.enterprise + jakarta.enterprise.cdi-el-api + ${cdi.api.version} + + jakarta.interceptor jakarta.interceptor-api From 6f20160cf4b70060d54eeab66625ae074e9983c5 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 27 Feb 2025 12:45:38 +0100 Subject: [PATCH 2/3] fix formatting in AnnotationInstances --- .../lang/model/tck/AnnotationInstances.java | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/AnnotationInstances.java b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/AnnotationInstances.java index 3fd98b73f..4b6033378 100644 --- a/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/AnnotationInstances.java +++ b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/AnnotationInstances.java @@ -22,15 +22,35 @@ public class AnnotationInstances { void defaultValues() { } - @AnnotationMembers(booleanMember = false, byteMember = -1, shortMember = -2, intMember = -3, longMember = -4, floatMember = -5.0F, doubleMember = -6.0, charMember = 'a', stringMember = "bb", classMember = AnnotationInstances.class, enumMember = SimpleEnum.NO, annotationMember = @SimpleAnnotation("ccc"), - - booleanArrayMember = { false, true }, byteArrayMember = { -1, -2 }, shortArrayMember = { -3, - -4 }, intArrayMember = { -5, -6 }, longArrayMember = { -7, -8 }, floatArrayMember = { -9.0F, - -10.0F }, doubleArrayMember = { -11.0, -12.0 }, charArrayMember = { 'a', - 'b' }, stringArrayMember = { "cc", "dd" }, classArrayMember = { AnnotationInstances.class, - AnnotationMembers.class }, enumArrayMember = { SimpleEnum.NO, - SimpleEnum.YES }, annotationArrayMember = { @SimpleAnnotation("eee"), - @SimpleAnnotation("fff") }) + //@formatter:off + @AnnotationMembers( + booleanMember = false, + byteMember = -1, + shortMember = -2, + intMember = -3, + longMember = -4, + floatMember = -5.0F, + doubleMember = -6.0, + charMember = 'a', + stringMember = "bb", + classMember = AnnotationInstances.class, + enumMember = SimpleEnum.NO, + annotationMember = @SimpleAnnotation("ccc"), + + booleanArrayMember = { false, true }, + byteArrayMember = { -1, -2 }, + shortArrayMember = { -3, -4 }, + intArrayMember = { -5, -6 }, + longArrayMember = { -7, -8 }, + floatArrayMember = { -9.0F, -10.0F }, + doubleArrayMember = { -11.0, -12.0 }, + charArrayMember = { 'a', 'b' }, + stringArrayMember = { "cc", "dd" }, + classArrayMember = { AnnotationInstances.class, AnnotationMembers.class }, + enumArrayMember = { SimpleEnum.NO, SimpleEnum.YES }, + annotationArrayMember = { @SimpleAnnotation("eee"), @SimpleAnnotation("fff") } + ) + //@formatter:on void nondefaultValues() { } From 48371429567721a60d8909083877919d0e82a1fe Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 27 Feb 2025 13:51:41 +0100 Subject: [PATCH 3/3] add Lang Model TCK coverage for sealed classes --- .../cdi/lang/model/tck/LangModelVerifier.java | 4 + .../cdi/lang/model/tck/SealedClasses.java | 153 ++++++++++++++++++ .../cdi/lang/model/tck/SealedInterfaces.java | 94 +++++++++++ 3 files changed, 251 insertions(+) create mode 100644 lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedClasses.java create mode 100644 lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedInterfaces.java diff --git a/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/LangModelVerifier.java b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/LangModelVerifier.java index 6c917280e..6af57ed14 100644 --- a/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/LangModelVerifier.java +++ b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/LangModelVerifier.java @@ -66,6 +66,8 @@ public class LangModelVerifier { RepeatableAnnotations repeatableAnnotations; DefaultConstructors defaultConstructors; Equality equality; + SealedClasses sealedClasses; + SealedInterfaces sealedInterfaces; /** * To run the language model TCK, this method must be called with a {@code ClassInfo} object @@ -101,6 +103,8 @@ public static void verify(ClassInfo clazz) { RepeatableAnnotations.verify(LangModelUtils.classOfField(clazz, "repeatableAnnotations")); DefaultConstructors.verify(LangModelUtils.classOfField(clazz, "defaultConstructors")); Equality.verify(LangModelUtils.classOfField(clazz, "equality")); + SealedClasses.verify(LangModelUtils.classOfField(clazz, "sealedClasses")); + SealedInterfaces.verify(LangModelUtils.classOfField(clazz, "sealedInterfaces")); verifyPackageAnnotation(clazz); diff --git a/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedClasses.java b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedClasses.java new file mode 100644 index 000000000..cb75602fd --- /dev/null +++ b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedClasses.java @@ -0,0 +1,153 @@ +/* + * Copyright 2025, Red Hat, Inc., and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.cdi.lang.model.tck; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; + +abstract sealed class SealedClass permits SubclassOfSealedClass1, SubclassOfSealedClass2, SubclassOfSealedClass3 { +} + +final class SubclassOfSealedClass1 extends SealedClass { +} + +non-sealed class SubclassOfSealedClass2 extends SealedClass { +} + +sealed class SubclassOfSealedClass3 extends SealedClass permits SubclassOfSubclassOfSealedClass3 { +} + +final class SubclassOfSubclassOfSealedClass3 extends SubclassOfSealedClass3 { +} + +sealed class SealedClassWithoutPermits { +} + +final class SubclassOfSealedClassWithoutPermits1 extends SealedClassWithoutPermits { +} + +non-sealed class SubclassOfSealedClassWithoutPermits2 extends SealedClassWithoutPermits { +} + +sealed class SubclassOfSealedClassWithoutPermits3 extends SealedClassWithoutPermits { +} + +final class SubclassOfSubclassOfSealedClassWithoutPermits3 extends SubclassOfSealedClassWithoutPermits3 { +} + +enum SealedEnum { + FOO { + @Override + String hello() { + return "FOO"; + } + }, + BAR, + BAZ { + @Override + String hello() { + return "BAZ"; + } + }, + QUUX, + ; + + String hello() { + return "unknown"; + } +} + +public class SealedClasses { + SealedClass sealedClass; + SealedClassWithoutPermits sealedClassWithoutPermits; + SealedEnum sealedEnum; + + public static void verify(ClassInfo clazz) { + verifySealedClass(LangModelUtils.classOfField(clazz, "sealedClass")); + verifySealedClassWithoutPermits(LangModelUtils.classOfField(clazz, "sealedClassWithoutPermits")); + verifySealedEnum(LangModelUtils.classOfField(clazz, "sealedEnum")); + } + + private static void verifySealedClass(ClassInfo clazz) { + assert clazz.isPlainClass(); + assert clazz.isAbstract(); + assert !clazz.isFinal(); + assert clazz.isSealed(); + assert clazz.permittedSubclasses().size() == 3; + for (ClassInfo subclass : clazz.permittedSubclasses()) { + switch (subclass.simpleName()) { + case "SubclassOfSealedClass1" -> { + assert subclass.isPlainClass(); + assert subclass.isFinal(); + assert !subclass.isAbstract(); + assert !subclass.isSealed(); + } + case "SubclassOfSealedClass2" -> { + assert subclass.isPlainClass(); + assert !subclass.isFinal(); + assert !subclass.isAbstract(); + assert !subclass.isSealed(); + } + case "SubclassOfSealedClass3" -> { + assert subclass.isPlainClass(); + assert !subclass.isFinal(); + assert !subclass.isAbstract(); + assert subclass.isSealed(); + assert subclass.permittedSubclasses().size() == 1; + } + default -> throw new AssertionError("Unexpected subclass " + subclass + " of " + clazz); + } + } + } + + private static void verifySealedClassWithoutPermits(ClassInfo clazz) { + assert clazz.isPlainClass(); + assert !clazz.isAbstract(); + assert !clazz.isFinal(); + assert clazz.isSealed(); + assert clazz.permittedSubclasses().size() == 3; + for (ClassInfo subclass : clazz.permittedSubclasses()) { + switch (subclass.simpleName()) { + case "SubclassOfSealedClassWithoutPermits1" -> { + assert subclass.isPlainClass(); + assert !subclass.isAbstract(); + assert subclass.isFinal(); + assert !subclass.isSealed(); + } + case "SubclassOfSealedClassWithoutPermits2" -> { + assert subclass.isPlainClass(); + assert !subclass.isAbstract(); + assert !subclass.isFinal(); + assert !subclass.isSealed(); + } + case "SubclassOfSealedClassWithoutPermits3" -> { + assert subclass.isPlainClass(); + assert !subclass.isAbstract(); + assert !subclass.isFinal(); + assert subclass.isSealed(); + assert subclass.permittedSubclasses().size() == 1; + } + default -> throw new AssertionError("Unexpected subclass " + subclass + " of " + clazz); + } + } + } + + private static void verifySealedEnum(ClassInfo clazz) { + assert clazz.isEnum(); + assert !clazz.isAbstract(); + assert !clazz.isFinal(); + assert clazz.isSealed(); + assert clazz.permittedSubclasses().size() == 2; + // permitted subclasses of an enum are anonymous + } +} diff --git a/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedInterfaces.java b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedInterfaces.java new file mode 100644 index 000000000..b4a5b3eec --- /dev/null +++ b/lang-model/src/main/java/org/jboss/cdi/lang/model/tck/SealedInterfaces.java @@ -0,0 +1,94 @@ +/* + * Copyright 2025, Red Hat, Inc., and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.cdi.lang.model.tck; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; + +sealed interface SealedInterface permits SubinterfaceOfSealedInterface, ImplementationOfSealedInterface { +} + +non-sealed interface SubinterfaceOfSealedInterface extends SealedInterface { +} + +final class ImplementationOfSealedInterface implements SealedInterface { +} + +sealed interface SealedInterfaceWithoutPermits { +} + +non-sealed interface SubinterfaceOfSealedInterfaceWithoutPermits extends SealedInterfaceWithoutPermits { +} + +final class ImplementationOfSealedInterfaceWithoutPermits implements SealedInterfaceWithoutPermits { +} + +public class SealedInterfaces { + SealedInterface sealedInterface; + SealedInterfaceWithoutPermits sealedInterfaceWithoutPermits; + + public static void verify(ClassInfo clazz) { + verifySealedInterface(LangModelUtils.classOfField(clazz, "sealedInterface")); + verifySealedInterfaceWithoutPermits(LangModelUtils.classOfField(clazz, "sealedInterfaceWithoutPermits")); + } + + private static void verifySealedInterface(ClassInfo clazz) { + assert clazz.isInterface(); + assert clazz.isAbstract(); + assert !clazz.isFinal(); + assert clazz.isSealed(); + assert clazz.permittedSubclasses().size() == 2; + for (ClassInfo subclass : clazz.permittedSubclasses()) { + switch (subclass.simpleName()) { + case "SubinterfaceOfSealedInterface" -> { + assert subclass.isInterface(); + assert !subclass.isFinal(); + assert subclass.isAbstract(); + assert !subclass.isSealed(); + } + case "ImplementationOfSealedInterface" -> { + assert subclass.isPlainClass(); + assert subclass.isFinal(); + assert !subclass.isAbstract(); + assert !subclass.isSealed(); + } + default -> throw new AssertionError("Unexpected subclass " + subclass + " of " + clazz); + } + } + } + + private static void verifySealedInterfaceWithoutPermits(ClassInfo clazz) { + assert clazz.isInterface(); + assert clazz.isAbstract(); + assert !clazz.isFinal(); + assert clazz.isSealed(); + assert clazz.permittedSubclasses().size() == 2; + for (ClassInfo subclass : clazz.permittedSubclasses()) { + switch (subclass.simpleName()) { + case "SubinterfaceOfSealedInterfaceWithoutPermits" -> { + assert subclass.isInterface(); + assert subclass.isAbstract(); + assert !subclass.isFinal(); + assert !subclass.isSealed(); + } + case "ImplementationOfSealedInterfaceWithoutPermits" -> { + assert subclass.isPlainClass(); + assert !subclass.isAbstract(); + assert subclass.isFinal(); + assert !subclass.isSealed(); + } + default -> throw new AssertionError("Unexpected subclass " + subclass + " of " + clazz); + } + } + } +}