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