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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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
|
* 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,
|
* The string is formatted as a list of type modifiers, if any,
|
||||||
* followed by the kind of type (empty string for primitive types
|
* 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(' ');
|
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()) {
|
if (isAnnotation()) {
|
||||||
sb.append('@');
|
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) {
|
static String typeVarBounds(TypeVariable<?> typeVar) {
|
||||||
Type[] bounds = typeVar.getBounds();
|
Type[] bounds = typeVar.getBounds();
|
||||||
if (bounds.length == 1 && bounds[0].equals(Object.class)) {
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -23,9 +23,8 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @bug 6298888 6992705 8161500 6304578
|
* @bug 6298888 6992705 8161500 6304578 8322878
|
||||||
* @summary Check Class.toGenericString()
|
* @summary Check Class.toGenericString()
|
||||||
* @author Joseph D. Darcy
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.*;
|
||||||
@ -37,25 +36,41 @@ public class GenericStringTest {
|
|||||||
public Map<String, Integer>[] mixed = null;
|
public Map<String, Integer>[] mixed = null;
|
||||||
public Map<String, Integer>[][] mixed2 = null;
|
public Map<String, Integer>[][] mixed2 = null;
|
||||||
|
|
||||||
|
private static record PlatformTestCase(Class<?> clazz, String expected) {}
|
||||||
|
|
||||||
public static void main(String... args) throws ReflectiveOperationException {
|
public static void main(String... args) throws ReflectiveOperationException {
|
||||||
int failures = 0;
|
int failures = 0;
|
||||||
|
|
||||||
String[][] nested = {{""}};
|
String[][] nested = {{""}};
|
||||||
int[][] intArray = {{1}};
|
int[][] intArray = {{1}};
|
||||||
|
|
||||||
Map<Class<?>, String> testCases =
|
List<PlatformTestCase> platformTestCases =
|
||||||
Map.of(int.class, "int",
|
List.of(new PlatformTestCase(int.class, "int"),
|
||||||
void.class, "void",
|
new PlatformTestCase(void.class, "void"),
|
||||||
args.getClass(), "java.lang.String[]",
|
new PlatformTestCase(args.getClass(), "java.lang.String[]"),
|
||||||
nested.getClass(), "java.lang.String[][]",
|
new PlatformTestCase(nested.getClass(), "java.lang.String[][]"),
|
||||||
intArray.getClass(), "int[][]",
|
new PlatformTestCase(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>");
|
|
||||||
|
|
||||||
for (Map.Entry<Class<?>, String> testCase : testCases.entrySet()) {
|
new PlatformTestCase(java.lang.Enum.class,
|
||||||
failures += checkToGenericString(testCase.getKey(), testCase.getValue());
|
"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");
|
Field f = GenericStringTest.class.getDeclaredField("mixed");
|
||||||
@ -70,7 +85,33 @@ public class GenericStringTest {
|
|||||||
AnInterface.class,
|
AnInterface.class,
|
||||||
LocalMap.class,
|
LocalMap.class,
|
||||||
AnEnum.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());
|
failures += checkToGenericString(clazz, clazz.getAnnotation(ExpectedGenericString.class).value());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +148,102 @@ enum AnEnum {
|
|||||||
FOO;
|
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 {
|
enum AnotherEnum {
|
||||||
BAR{};
|
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