1 /*
  2  * Copyright (c) 2014, 2024, 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 }