8322878: Including sealing information Class.toGenericString()

Co-authored-by: Pavel Rappo <prappo@openjdk.org>
Reviewed-by: rriggs
This commit is contained in:
Joe Darcy 2024-01-10 18:46:56 +00:00
parent c1282b57f5
commit 525063be90
2 changed files with 204 additions and 19 deletions
src/java.base/share/classes/java/lang
test/jdk/java/lang/Class

@ -1,5 +1,5 @@
/*
* Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -261,7 +261,8 @@ public final class Class<T> implements java.io.Serializable,
/**
* Returns a string describing this {@code Class}, including
* information about modifiers and type parameters.
* information about modifiers, {@link #isSealed() sealed}/{@code
* non-sealed} status, and type parameters.
*
* The string is formatted as a list of type modifiers, if any,
* followed by the kind of type (empty string for primitive types
@ -314,6 +315,11 @@ public final class Class<T> implements java.io.Serializable,
sb.append(' ');
}
// A class cannot be strictfp and sealed/non-sealed so
// it is sufficient to check for sealed-ness after all
// modifiers are printed.
addSealingInfo(modifiers, sb);
if (isAnnotation()) {
sb.append('@');
}
@ -344,6 +350,49 @@ public final class Class<T> implements java.io.Serializable,
}
}
private void addSealingInfo(int modifiers, StringBuilder sb) {
// A class can be final XOR sealed XOR non-sealed.
if (Modifier.isFinal(modifiers)) {
return; // no-op
} else {
if (isSealed()) {
sb.append("sealed ");
return;
} else {
// Check for sealed ancestor, which implies this class
// is non-sealed.
if (hasSealedAncestor(this)) {
sb.append("non-sealed ");
}
}
}
}
private boolean hasSealedAncestor(Class<?> clazz) {
// From JLS 8.1.1.2:
// "It is a compile-time error if a class has a sealed direct
// superclass or a sealed direct superinterface, and is not
// declared final, sealed, or non-sealed either explicitly or
// implicitly.
// Thus, an effect of the sealed keyword is to force all
// direct subclasses to explicitly declare whether they are
// final, sealed, or non-sealed. This avoids accidentally
// exposing a sealed class hierarchy to unwanted subclassing."
// Therefore, will just check direct superclass and
// superinterfaces.
var superclass = clazz.getSuperclass();
if (superclass != null && superclass.isSealed()) {
return true;
}
for (var superinterface : clazz.getInterfaces()) {
if (superinterface.isSealed()) {
return true;
}
}
return false;
}
static String typeVarBounds(TypeVariable<?> typeVar) {
Type[] bounds = typeVar.getBounds();
if (bounds.length == 1 && bounds[0].equals(Object.class)) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,9 +23,8 @@
/*
* @test
* @bug 6298888 6992705 8161500 6304578
* @bug 6298888 6992705 8161500 6304578 8322878
* @summary Check Class.toGenericString()
* @author Joseph D. Darcy
*/
import java.lang.reflect.*;
@ -37,25 +36,41 @@ public class GenericStringTest {
public Map<String, Integer>[] mixed = null;
public Map<String, Integer>[][] mixed2 = null;
private static record PlatformTestCase(Class<?> clazz, String expected) {}
public static void main(String... args) throws ReflectiveOperationException {
int failures = 0;
String[][] nested = {{""}};
int[][] intArray = {{1}};
Map<Class<?>, String> testCases =
Map.of(int.class, "int",
void.class, "void",
args.getClass(), "java.lang.String[]",
nested.getClass(), "java.lang.String[][]",
intArray.getClass(), "int[][]",
java.lang.Enum.class, "public abstract class java.lang.Enum<E extends java.lang.Enum<E>>",
java.util.Map.class, "public abstract interface java.util.Map<K,V>",
java.util.EnumMap.class, "public class java.util.EnumMap<K extends java.lang.Enum<K>,V>",
java.util.EventListenerProxy.class, "public abstract class java.util.EventListenerProxy<T extends java.util.EventListener>");
List<PlatformTestCase> platformTestCases =
List.of(new PlatformTestCase(int.class, "int"),
new PlatformTestCase(void.class, "void"),
new PlatformTestCase(args.getClass(), "java.lang.String[]"),
new PlatformTestCase(nested.getClass(), "java.lang.String[][]"),
new PlatformTestCase(intArray.getClass(), "int[][]"),
for (Map.Entry<Class<?>, String> testCase : testCases.entrySet()) {
failures += checkToGenericString(testCase.getKey(), testCase.getValue());
new PlatformTestCase(java.lang.Enum.class,
"public abstract class java.lang.Enum<E extends java.lang.Enum<E>>"),
new PlatformTestCase(java.util.Map.class,
"public abstract interface java.util.Map<K,V>"),
new PlatformTestCase(java.util.EnumMap.class,
"public class java.util.EnumMap<K extends java.lang.Enum<K>,V>"),
new PlatformTestCase(java.util.EventListenerProxy.class,
"public abstract class java.util.EventListenerProxy<T extends java.util.EventListener>"),
// Sealed class
new PlatformTestCase(java.lang.ref.Reference.class,
"public abstract sealed class java.lang.ref.Reference<T>"),
// non-sealed class
new PlatformTestCase(java.lang.ref.WeakReference.class,
"public non-sealed class java.lang.ref.WeakReference<T>")
);
for (PlatformTestCase platformTestCase : platformTestCases) {
failures += checkToGenericString(platformTestCase.clazz,
platformTestCase.expected);
}
Field f = GenericStringTest.class.getDeclaredField("mixed");
@ -70,7 +85,33 @@ public class GenericStringTest {
AnInterface.class,
LocalMap.class,
AnEnum.class,
AnotherEnum.class)) {
AnotherEnum.class,
SealedRootClass.class,
SealedRootClass.ChildA.class,
SealedRootClass.ChildB.class,
SealedRootClass.ChildB.GrandChildAB.class,
SealedRootClass.ChildC.class,
SealedRootClass.ChildC.GrandChildACA.class,
SealedRootClass.ChildC.GrandChildACB.class,
SealedRootClass.ChildC.GrandChildACC.class,
SealedRootClass.ChildC.GrandChildACC.GreatGrandChildACCA.class,
SealedRootClass.ChildC.GrandChildACC.GreatGrandChildACCB.class,
SealedRootIntf.class,
SealedRootIntf.ChildA.class,
SealedRootIntf.ChildB.class,
SealedRootIntf.ChildB.GrandChildAB.class,
SealedRootIntf.ChildC.class,
SealedRootIntf.ChildC.GrandChildACA.class,
SealedRootIntf.ChildC.GrandChildACB.class,
SealedRootIntf.ChildC.GrandChildACC.class,
SealedRootIntf.ChildC.GrandChildACC.GreatGrandChildACCA.class,
SealedRootIntf.ChildC.GrandChildACC.GreatGrandChildACCB.class,
SealedRootIntf.IntfA.class,
SealedRootIntf.IntfA.IntfAImpl.class,
SealedRootIntf.IntfB.class,
SealedRootIntf.IntfB.IntfAImpl.class)) {
failures += checkToGenericString(clazz, clazz.getAnnotation(ExpectedGenericString.class).value());
}
@ -107,7 +148,102 @@ enum AnEnum {
FOO;
}
@ExpectedGenericString("enum AnotherEnum")
// If an enum class has a specialized enum constant, that is compiled
// by having the enum class as being sealed rather than final. See JLS
// 8.9 Enum Classes.
@ExpectedGenericString("sealed enum AnotherEnum")
enum AnotherEnum {
BAR{};
}
// Test cases for sealed/non-sealed _class_ hierarchy.
@ExpectedGenericString("sealed class SealedRootClass")
sealed class SealedRootClass
permits
SealedRootClass.ChildA,
SealedRootClass.ChildB,
SealedRootClass.ChildC {
@ExpectedGenericString("final class SealedRootClass$ChildA")
final class ChildA extends SealedRootClass {}
@ExpectedGenericString("sealed class SealedRootClass$ChildB")
sealed class ChildB extends SealedRootClass permits SealedRootClass.ChildB.GrandChildAB {
@ExpectedGenericString("final class SealedRootClass$ChildB$GrandChildAB")
final class GrandChildAB extends ChildB {}
}
@ExpectedGenericString("non-sealed class SealedRootClass$ChildC")
non-sealed class ChildC extends SealedRootClass {
// The subclasses of ChildC do not themselves have to be
// sealed, non-sealed, or final.
@ExpectedGenericString("class SealedRootClass$ChildC$GrandChildACA")
class GrandChildACA extends ChildC {}
@ExpectedGenericString("final class SealedRootClass$ChildC$GrandChildACB")
final class GrandChildACB extends ChildC {}
@ExpectedGenericString("sealed class SealedRootClass$ChildC$GrandChildACC")
sealed class GrandChildACC extends ChildC {
@ExpectedGenericString("final class SealedRootClass$ChildC$GrandChildACC$GreatGrandChildACCA")
final class GreatGrandChildACCA extends GrandChildACC {}
@ExpectedGenericString("non-sealed class SealedRootClass$ChildC$GrandChildACC$GreatGrandChildACCB")
non-sealed class GreatGrandChildACCB extends GrandChildACC {}
}
}
}
// Test cases for sealed/non-sealed _interface_ hierarchy.
@ExpectedGenericString("abstract sealed interface SealedRootIntf")
sealed interface SealedRootIntf
permits
SealedRootIntf.ChildA,
SealedRootIntf.ChildB,
SealedRootIntf.ChildC,
SealedRootIntf.IntfA,
SealedRootIntf.IntfB {
@ExpectedGenericString("public static final class SealedRootIntf$ChildA")
final class ChildA implements SealedRootIntf {}
@ExpectedGenericString("public static sealed class SealedRootIntf$ChildB")
sealed class ChildB implements SealedRootIntf permits SealedRootIntf.ChildB.GrandChildAB {
@ExpectedGenericString("final class SealedRootIntf$ChildB$GrandChildAB")
final class GrandChildAB extends ChildB {}
}
@ExpectedGenericString("public static non-sealed class SealedRootIntf$ChildC")
non-sealed class ChildC implements SealedRootIntf {
// The subclasses of ChildC do not themselves have to be
// sealed, non-sealed, or final.
@ExpectedGenericString("class SealedRootIntf$ChildC$GrandChildACA")
class GrandChildACA extends ChildC {}
@ExpectedGenericString("final class SealedRootIntf$ChildC$GrandChildACB")
final class GrandChildACB extends ChildC {}
@ExpectedGenericString("sealed class SealedRootIntf$ChildC$GrandChildACC")
sealed class GrandChildACC extends ChildC {
@ExpectedGenericString("final class SealedRootIntf$ChildC$GrandChildACC$GreatGrandChildACCA")
final class GreatGrandChildACCA extends GrandChildACC {}
@ExpectedGenericString("non-sealed class SealedRootIntf$ChildC$GrandChildACC$GreatGrandChildACCB")
non-sealed class GreatGrandChildACCB extends GrandChildACC {}
}
}
@ExpectedGenericString("public abstract static sealed interface SealedRootIntf$IntfA")
sealed interface IntfA extends SealedRootIntf {
@ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfA$IntfAImpl")
non-sealed class IntfAImpl implements IntfA {}
}
@ExpectedGenericString("public abstract static non-sealed interface SealedRootIntf$IntfB")
non-sealed interface IntfB extends SealedRootIntf {
// Check that non-sealing can be allowed with a second superinterface being sealed.
@ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfB$IntfAImpl")
non-sealed class IntfAImpl implements IntfB, IntfA {}
}
}