Skip to content

Commit 525063b

Browse files
jddarcypavelrappo
andcommitted
8322878: Including sealing information Class.toGenericString()
Co-authored-by: Pavel Rappo <[email protected]> Reviewed-by: rriggs
1 parent c1282b5 commit 525063b

File tree

2 files changed

+205
-20
lines changed

2 files changed

+205
-20
lines changed

src/java.base/share/classes/java/lang/Class.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -261,7 +261,8 @@ public String toString() {
261261

262262
/**
263263
* Returns a string describing this {@code Class}, including
264-
* information about modifiers and type parameters.
264+
* information about modifiers, {@link #isSealed() sealed}/{@code
265+
* non-sealed} status, and type parameters.
265266
*
266267
* The string is formatted as a list of type modifiers, if any,
267268
* followed by the kind of type (empty string for primitive types
@@ -314,6 +315,11 @@ public String toGenericString() {
314315
sb.append(' ');
315316
}
316317

318+
// A class cannot be strictfp and sealed/non-sealed so
319+
// it is sufficient to check for sealed-ness after all
320+
// modifiers are printed.
321+
addSealingInfo(modifiers, sb);
322+
317323
if (isAnnotation()) {
318324
sb.append('@');
319325
}
@@ -344,6 +350,49 @@ else if (isRecord())
344350
}
345351
}
346352

353+
private void addSealingInfo(int modifiers, StringBuilder sb) {
354+
// A class can be final XOR sealed XOR non-sealed.
355+
if (Modifier.isFinal(modifiers)) {
356+
return; // no-op
357+
} else {
358+
if (isSealed()) {
359+
sb.append("sealed ");
360+
return;
361+
} else {
362+
// Check for sealed ancestor, which implies this class
363+
// is non-sealed.
364+
if (hasSealedAncestor(this)) {
365+
sb.append("non-sealed ");
366+
}
367+
}
368+
}
369+
}
370+
371+
private boolean hasSealedAncestor(Class<?> clazz) {
372+
// From JLS 8.1.1.2:
373+
// "It is a compile-time error if a class has a sealed direct
374+
// superclass or a sealed direct superinterface, and is not
375+
// declared final, sealed, or non-sealed either explicitly or
376+
// implicitly.
377+
// Thus, an effect of the sealed keyword is to force all
378+
// direct subclasses to explicitly declare whether they are
379+
// final, sealed, or non-sealed. This avoids accidentally
380+
// exposing a sealed class hierarchy to unwanted subclassing."
381+
382+
// Therefore, will just check direct superclass and
383+
// superinterfaces.
384+
var superclass = clazz.getSuperclass();
385+
if (superclass != null && superclass.isSealed()) {
386+
return true;
387+
}
388+
for (var superinterface : clazz.getInterfaces()) {
389+
if (superinterface.isSealed()) {
390+
return true;
391+
}
392+
}
393+
return false;
394+
}
395+
347396
static String typeVarBounds(TypeVariable<?> typeVar) {
348397
Type[] bounds = typeVar.getBounds();
349398
if (bounds.length == 1 && bounds[0].equals(Object.class)) {

test/jdk/java/lang/Class/GenericStringTest.java

Lines changed: 154 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -23,9 +23,8 @@
2323

2424
/*
2525
* @test
26-
* @bug 6298888 6992705 8161500 6304578
26+
* @bug 6298888 6992705 8161500 6304578 8322878
2727
* @summary Check Class.toGenericString()
28-
* @author Joseph D. Darcy
2928
*/
3029

3130
import java.lang.reflect.*;
@@ -37,25 +36,41 @@ public class GenericStringTest {
3736
public Map<String, Integer>[] mixed = null;
3837
public Map<String, Integer>[][] mixed2 = null;
3938

39+
private static record PlatformTestCase(Class<?> clazz, String expected) {}
40+
4041
public static void main(String... args) throws ReflectiveOperationException {
4142
int failures = 0;
4243

4344
String[][] nested = {{""}};
4445
int[][] intArray = {{1}};
4546

46-
Map<Class<?>, String> testCases =
47-
Map.of(int.class, "int",
48-
void.class, "void",
49-
args.getClass(), "java.lang.String[]",
50-
nested.getClass(), "java.lang.String[][]",
51-
intArray.getClass(), "int[][]",
52-
java.lang.Enum.class, "public abstract class java.lang.Enum<E extends java.lang.Enum<E>>",
53-
java.util.Map.class, "public abstract interface java.util.Map<K,V>",
54-
java.util.EnumMap.class, "public class java.util.EnumMap<K extends java.lang.Enum<K>,V>",
55-
java.util.EventListenerProxy.class, "public abstract class java.util.EventListenerProxy<T extends java.util.EventListener>");
56-
57-
for (Map.Entry<Class<?>, String> testCase : testCases.entrySet()) {
58-
failures += checkToGenericString(testCase.getKey(), testCase.getValue());
47+
List<PlatformTestCase> platformTestCases =
48+
List.of(new PlatformTestCase(int.class, "int"),
49+
new PlatformTestCase(void.class, "void"),
50+
new PlatformTestCase(args.getClass(), "java.lang.String[]"),
51+
new PlatformTestCase(nested.getClass(), "java.lang.String[][]"),
52+
new PlatformTestCase(intArray.getClass(), "int[][]"),
53+
54+
new PlatformTestCase(java.lang.Enum.class,
55+
"public abstract class java.lang.Enum<E extends java.lang.Enum<E>>"),
56+
new PlatformTestCase(java.util.Map.class,
57+
"public abstract interface java.util.Map<K,V>"),
58+
new PlatformTestCase(java.util.EnumMap.class,
59+
"public class java.util.EnumMap<K extends java.lang.Enum<K>,V>"),
60+
new PlatformTestCase(java.util.EventListenerProxy.class,
61+
"public abstract class java.util.EventListenerProxy<T extends java.util.EventListener>"),
62+
63+
// Sealed class
64+
new PlatformTestCase(java.lang.ref.Reference.class,
65+
"public abstract sealed class java.lang.ref.Reference<T>"),
66+
// non-sealed class
67+
new PlatformTestCase(java.lang.ref.WeakReference.class,
68+
"public non-sealed class java.lang.ref.WeakReference<T>")
69+
);
70+
71+
for (PlatformTestCase platformTestCase : platformTestCases) {
72+
failures += checkToGenericString(platformTestCase.clazz,
73+
platformTestCase.expected);
5974
}
6075

6176
Field f = GenericStringTest.class.getDeclaredField("mixed");
@@ -70,7 +85,33 @@ public static void main(String... args) throws ReflectiveOperationException {
7085
AnInterface.class,
7186
LocalMap.class,
7287
AnEnum.class,
73-
AnotherEnum.class)) {
88+
AnotherEnum.class,
89+
90+
SealedRootClass.class,
91+
SealedRootClass.ChildA.class,
92+
SealedRootClass.ChildB.class,
93+
SealedRootClass.ChildB.GrandChildAB.class,
94+
SealedRootClass.ChildC.class,
95+
SealedRootClass.ChildC.GrandChildACA.class,
96+
SealedRootClass.ChildC.GrandChildACB.class,
97+
SealedRootClass.ChildC.GrandChildACC.class,
98+
SealedRootClass.ChildC.GrandChildACC.GreatGrandChildACCA.class,
99+
SealedRootClass.ChildC.GrandChildACC.GreatGrandChildACCB.class,
100+
101+
SealedRootIntf.class,
102+
SealedRootIntf.ChildA.class,
103+
SealedRootIntf.ChildB.class,
104+
SealedRootIntf.ChildB.GrandChildAB.class,
105+
SealedRootIntf.ChildC.class,
106+
SealedRootIntf.ChildC.GrandChildACA.class,
107+
SealedRootIntf.ChildC.GrandChildACB.class,
108+
SealedRootIntf.ChildC.GrandChildACC.class,
109+
SealedRootIntf.ChildC.GrandChildACC.GreatGrandChildACCA.class,
110+
SealedRootIntf.ChildC.GrandChildACC.GreatGrandChildACCB.class,
111+
SealedRootIntf.IntfA.class,
112+
SealedRootIntf.IntfA.IntfAImpl.class,
113+
SealedRootIntf.IntfB.class,
114+
SealedRootIntf.IntfB.IntfAImpl.class)) {
74115
failures += checkToGenericString(clazz, clazz.getAnnotation(ExpectedGenericString.class).value());
75116
}
76117

@@ -107,7 +148,102 @@ enum AnEnum {
107148
FOO;
108149
}
109150

110-
@ExpectedGenericString("enum AnotherEnum")
151+
// If an enum class has a specialized enum constant, that is compiled
152+
// by having the enum class as being sealed rather than final. See JLS
153+
// 8.9 Enum Classes.
154+
@ExpectedGenericString("sealed enum AnotherEnum")
111155
enum AnotherEnum {
112156
BAR{};
113157
}
158+
159+
// Test cases for sealed/non-sealed _class_ hierarchy.
160+
@ExpectedGenericString("sealed class SealedRootClass")
161+
sealed class SealedRootClass
162+
permits
163+
SealedRootClass.ChildA,
164+
SealedRootClass.ChildB,
165+
SealedRootClass.ChildC {
166+
167+
@ExpectedGenericString("final class SealedRootClass$ChildA")
168+
final class ChildA extends SealedRootClass {}
169+
170+
@ExpectedGenericString("sealed class SealedRootClass$ChildB")
171+
sealed class ChildB extends SealedRootClass permits SealedRootClass.ChildB.GrandChildAB {
172+
@ExpectedGenericString("final class SealedRootClass$ChildB$GrandChildAB")
173+
final class GrandChildAB extends ChildB {}
174+
}
175+
176+
@ExpectedGenericString("non-sealed class SealedRootClass$ChildC")
177+
non-sealed class ChildC extends SealedRootClass {
178+
// The subclasses of ChildC do not themselves have to be
179+
// sealed, non-sealed, or final.
180+
@ExpectedGenericString("class SealedRootClass$ChildC$GrandChildACA")
181+
class GrandChildACA extends ChildC {}
182+
183+
@ExpectedGenericString("final class SealedRootClass$ChildC$GrandChildACB")
184+
final class GrandChildACB extends ChildC {}
185+
186+
@ExpectedGenericString("sealed class SealedRootClass$ChildC$GrandChildACC")
187+
sealed class GrandChildACC extends ChildC {
188+
@ExpectedGenericString("final class SealedRootClass$ChildC$GrandChildACC$GreatGrandChildACCA")
189+
final class GreatGrandChildACCA extends GrandChildACC {}
190+
191+
@ExpectedGenericString("non-sealed class SealedRootClass$ChildC$GrandChildACC$GreatGrandChildACCB")
192+
non-sealed class GreatGrandChildACCB extends GrandChildACC {}
193+
}
194+
}
195+
}
196+
197+
// Test cases for sealed/non-sealed _interface_ hierarchy.
198+
@ExpectedGenericString("abstract sealed interface SealedRootIntf")
199+
sealed interface SealedRootIntf
200+
permits
201+
SealedRootIntf.ChildA,
202+
SealedRootIntf.ChildB,
203+
SealedRootIntf.ChildC,
204+
205+
SealedRootIntf.IntfA,
206+
SealedRootIntf.IntfB {
207+
208+
@ExpectedGenericString("public static final class SealedRootIntf$ChildA")
209+
final class ChildA implements SealedRootIntf {}
210+
211+
@ExpectedGenericString("public static sealed class SealedRootIntf$ChildB")
212+
sealed class ChildB implements SealedRootIntf permits SealedRootIntf.ChildB.GrandChildAB {
213+
@ExpectedGenericString("final class SealedRootIntf$ChildB$GrandChildAB")
214+
final class GrandChildAB extends ChildB {}
215+
}
216+
217+
@ExpectedGenericString("public static non-sealed class SealedRootIntf$ChildC")
218+
non-sealed class ChildC implements SealedRootIntf {
219+
// The subclasses of ChildC do not themselves have to be
220+
// sealed, non-sealed, or final.
221+
@ExpectedGenericString("class SealedRootIntf$ChildC$GrandChildACA")
222+
class GrandChildACA extends ChildC {}
223+
224+
@ExpectedGenericString("final class SealedRootIntf$ChildC$GrandChildACB")
225+
final class GrandChildACB extends ChildC {}
226+
227+
@ExpectedGenericString("sealed class SealedRootIntf$ChildC$GrandChildACC")
228+
sealed class GrandChildACC extends ChildC {
229+
@ExpectedGenericString("final class SealedRootIntf$ChildC$GrandChildACC$GreatGrandChildACCA")
230+
final class GreatGrandChildACCA extends GrandChildACC {}
231+
232+
@ExpectedGenericString("non-sealed class SealedRootIntf$ChildC$GrandChildACC$GreatGrandChildACCB")
233+
non-sealed class GreatGrandChildACCB extends GrandChildACC {}
234+
}
235+
}
236+
237+
@ExpectedGenericString("public abstract static sealed interface SealedRootIntf$IntfA")
238+
sealed interface IntfA extends SealedRootIntf {
239+
@ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfA$IntfAImpl")
240+
non-sealed class IntfAImpl implements IntfA {}
241+
}
242+
243+
@ExpectedGenericString("public abstract static non-sealed interface SealedRootIntf$IntfB")
244+
non-sealed interface IntfB extends SealedRootIntf {
245+
// Check that non-sealing can be allowed with a second superinterface being sealed.
246+
@ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfB$IntfAImpl")
247+
non-sealed class IntfAImpl implements IntfB, IntfA {}
248+
}
249+
}

0 commit comments

Comments
 (0)