1 /*
2 * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @bug 8042251
27 * @summary Test that inner classes have in its inner classes attribute enclosing classes and its immediate members.
28 * @library /tools/lib /tools/javac/lib ../lib
29 * @modules jdk.compiler/com.sun.tools.javac.api
30 * jdk.compiler/com.sun.tools.javac.main
31 * @build toolbox.ToolBox InMemoryFileManager TestResult TestBase
32 * @run main InnerClassesHierarchyTest
33 */
34
35 import java.io.File;
36 import java.io.FilenameFilter;
37 import java.io.IOException;
38 import java.lang.annotation.Annotation;
39 import java.util.*;
40 import java.util.stream.Collectors;
41
42 import java.lang.classfile.*;
43 import java.lang.classfile.attribute.*;
44 import java.lang.classfile.constantpool.*;
45
46 public class InnerClassesHierarchyTest extends TestResult {
47
48 private final Map<String, Set<String>> innerClasses;
49 private final String outerClassName;
50
51 public InnerClassesHierarchyTest() throws IOException, ConstantPoolException {
52 innerClasses = new HashMap<>();
53 outerClassName = InnerClassesHierarchyTest.class.getSimpleName();
54 File classDir = getClassDir();
55 FilenameFilter filter =
56 (dir, name) -> name.matches(outerClassName + ".*\\.class");
57 for (File file : Arrays.asList(classDir.listFiles(filter))) {
58 ClassModel classFile = readClassFile(file);
59 String className = classFile.thisClass().name().stringValue();
60 for (PoolEntry pe : classFile.constantPool()) {
61 if (pe instanceof ClassEntry classInfo
62 && classInfo.asSymbol().isClassOrInterface()) {
63 String cpClassName = classInfo.asInternalName();
64 if (isInnerClass(cpClassName)) {
65 get(className).add(cpClassName);
66 }
67 }
68 }
69 }
70 }
71
72 private boolean isInnerClass(String cpClassName) {
73 return cpClassName.contains("$");
74 }
75
76 private Set<String> get(String className) {
77 if (!innerClasses.containsKey(className)) {
78 innerClasses.put(className, new HashSet<>());
79 }
80 return innerClasses.get(className);
81 }
82
83 public static void main(String[] args) throws IOException, ConstantPoolException, TestFailedException {
84 new InnerClassesHierarchyTest().test();
85 }
86
87 private void test() throws TestFailedException {
88 addTestCase("Source file is InnerClassesHierarchyTest.java");
89 try {
90 Queue<String> queue = new LinkedList<>();
91 Set<String> visitedClasses = new HashSet<>();
92 queue.add(outerClassName);
93 while (!queue.isEmpty()) {
94 String currentClassName = queue.poll();
95 if (!currentClassName.startsWith(outerClassName)) {
96 continue;
97 }
98 ClassModel cf = readClassFile(currentClassName);
99 InnerClassesAttribute attr = cf.findAttribute(Attributes.innerClasses()).orElse(null);
100 checkNotNull(attr, "Class should not contain "
101 + "inner classes attribute : " + currentClassName);
102 checkTrue(innerClasses.containsKey(currentClassName),
103 "map contains class name : " + currentClassName);
104 Set<String> setClasses = innerClasses.get(currentClassName);
105 if (setClasses == null) {
106 continue;
107 }
108 checkEquals(attr.classes().size(),
109 setClasses.size(),
110 "Check number of inner classes : " + setClasses);
111 for (InnerClassInfo info : attr.classes()) {
112 if (!info.innerClass().asSymbol().isClassOrInterface()) continue;
113 String innerClassName = info
114 .innerClass().asInternalName();
115 checkTrue(setClasses.contains(innerClassName),
116 currentClassName + " contains inner class : "
117 + innerClassName);
118 if (visitedClasses.add(innerClassName)) {
119 queue.add(innerClassName);
120 }
121 }
122 }
123 Set<String> allClasses = innerClasses.entrySet().stream()
124 .flatMap(entry -> entry.getValue().stream())
125 .collect(Collectors.toSet());
126
127 Set<String> a_b = removeAll(visitedClasses, allClasses);
128 Set<String> b_a = removeAll(allClasses, visitedClasses);
129 checkEquals(visitedClasses, allClasses,
130 "All classes are found\n"
131 + "visited - all classes : " + a_b
132 + "\nall classes - visited : " + b_a);
133 } catch (Exception e) {
134 addFailure(e);
135 } finally {
136 checkStatus();
137 }
138 }
139
140 private Set<String> removeAll(Set<String> set1, Set<String> set2) {
141 Set<String> set = new HashSet<>(set1);
142 set.removeAll(set2);
143 return set;
144 }
145
146 public static class A1 {
147
148 public class B1 {
149 }
150
151 public enum B2 {
152 }
153
154 public interface B3 {
155 }
156
157 public @interface B4 {
158 }
159
160 public void f() {
161 new B1() {
162 };
163 new B3() {
164 };
165 new B4() {
166 @Override
167 public Class<? extends Annotation> annotationType() {
168 return null;
169 }
170 };
171 class B5 {
172 }
173 }
174
175 Runnable r = () -> {
176 new B1() {
177 };
178 new B3() {
179 };
180 new B4() {
181 @Override
182 public Class<? extends Annotation> annotationType() {
183 return null;
184 }
185 };
186 class B5 {
187 }
188 };
189 }
190
191 public enum A2 {;
192
193 public class B1 {
194 }
195
196 public enum B2 {
197 }
198
199 public interface B3 {
200 }
201
202 public @interface B4 {
203 }
204
205 public void a2() {
206 new B1() {
207 };
208 new B3() {
209 };
210 new B4() {
211 @Override
212 public Class<? extends Annotation> annotationType() {
213 return null;
214 }
215 };
216 class B5 {
217 }
218 }
219
220 Runnable r = () -> {
221 new B1() {
222 };
223 new B3() {
224 };
225 new B4() {
226 @Override
227 public Class<? extends Annotation> annotationType() {
228 return null;
229 }
230 };
231 class B5 {
232 }
233 };
234 }
235
236 public interface A3 {
237
238 public class B1 {
239 }
240
241 public enum B2 {
242 }
243
244 public interface B3 {
245 }
246
247 public @interface B4 {
248 }
249
250 default void a1() {
251 new B1() {
252 };
253 new B3() {
254 };
255 new B4() {
256 @Override
257 public Class<? extends Annotation> annotationType() {
258 return null;
259 }
260 };
261 class B5 {
262 }
263 }
264
265 static void a2() {
266 new B1() {
267 };
268 new B3() {
269 };
270 new B4() {
271 @Override
272 public Class<? extends Annotation> annotationType() {
273 return null;
274 }
275 };
276 class B5 {
277 }
278 }
279 }
280
281 public @interface A4 {
282
283 public class B1 {
284 }
285
286 public enum B2 {
287 }
288
289 public interface B3 {
290 }
291
292 public @interface B4 {
293 }
294 }
295
296 {
297 new A1() {
298 class B1 {
299 }
300
301 public void a2() {
302 new B1() {
303 };
304 class B5 {
305 }
306 }
307 };
308 new A3() {
309 class B1 {
310 }
311
312 public void a3() {
313 new B1() {
314 };
315 class B5 {
316 }
317 }
318 };
319 new A4() {
320 @Override
321 public Class<? extends Annotation> annotationType() {
322 return null;
323 }
324
325 class B1 {
326 }
327
328 public void a4() {
329 new B1() {
330 };
331 class B5 {
332 }
333 }
334 };
335 Runnable r = () -> {
336 new A1() {
337 };
338 new A3() {
339 };
340 new A4() {
341 @Override
342 public Class<? extends Annotation> annotationType() {
343 return null;
344 }
345 };
346 class B5 {
347 }
348 };
349 }
350
351 static {
352 new A1() {
353 class B1 {
354 }
355
356 public void a2() {
357 new B1() {
358 };
359 class B5 {
360 }
361 }
362 };
363 new A3() {
364 class B1 {
365 }
366
367 public void a3() {
368 new B1() {
369 };
370 class B5 {
371 }
372 }
373 };
374 new A4() {
375 @Override
376 public Class<? extends Annotation> annotationType() {
377 return null;
378 }
379
380 class B1 {
381 }
382
383 public void a4() {
384 new B1() {
385 };
386 class B5 {
387 }
388 }
389 };
390 Runnable r = () -> {
391 new A1() {
392 };
393 new A3() {
394 };
395 new A4() {
396 @Override
397 public Class<? extends Annotation> annotationType() {
398 return null;
399 }
400 };
401 class B5 {
402 }
403 };
404 }
405
406 public void a5() {
407 class A5 {
408
409 class B1 {
410 }
411
412 public void a5() {
413 new B1() {
414 };
415
416 class B5 {
417 }
418 }
419 }
420 Runnable r = () -> {
421 new A1() {
422 };
423 new A3() {
424 };
425 new A4() {
426 @Override
427 public Class<? extends Annotation> annotationType() {
428 return null;
429 }
430 };
431 class B5 {
432 }
433 };
434 }
435 }