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 }