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 }