8322878: Including sealing information Class.toGenericString()
Co-authored-by: Pavel Rappo <prappo@openjdk.org> Reviewed-by: rriggs
This commit is contained in:
parent
c1282b57f5
commit
525063be90
@ -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 {}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user