1 /* 2 * Copyright (c) 2014, 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 import com.sun.tools.classfile.Attribute; 25 import com.sun.tools.classfile.ClassFile; 26 import com.sun.tools.classfile.InnerClasses_attribute; 27 import com.sun.tools.classfile.InnerClasses_attribute.Info; 28 29 import java.nio.file.Paths; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 import java.util.stream.Collectors; 39 40 /** 41 * Base class for tests of inner classes attribute. 42 * The scenario of tests: 43 * 1. set possible values of class modifiers. 44 * 2. according to set class modifiers, a test generates sources 45 * and golden data with {@code generateTestCases}. 46 * 3. a test loops through all test cases and checks InnerClasses 47 * attribute with {@code test}. 48 * 49 * Example, possible flags for outer class are {@code Modifier.PRIVATE and Modifier.PUBLIC}, 50 * possible flags for inner class are {@code Modifier.EMPTY}. 51 * At the second step the test generates two test cases: 52 * 1. public class A { 53 * public class B { 54 * class C {} 55 * } 56 * } 57 * 2. public class A { 58 * private class B { 59 * class C {} 60 * } 61 * } 62 */ 63 public abstract class InnerClassesTestBase extends TestResult { 64 65 private Modifier[] outerAccessModifiers = {Modifier.EMPTY, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC}; 66 private Modifier[] outerOtherModifiers = {Modifier.EMPTY, Modifier.STATIC, Modifier.FINAL, Modifier.ABSTRACT}; 67 private Modifier[] innerAccessModifiers = outerAccessModifiers; 68 private Modifier[] innerOtherModifiers = outerOtherModifiers; 69 private boolean isForbiddenWithoutStaticInOuterMods = false; 70 71 private ClassType outerClassType; 72 private ClassType innerClassType; 73 private boolean hasSyntheticClass; 74 private String prefix = ""; 75 private String suffix = ""; 76 77 /** 78 * Sets properties. 79 * 80 * Returns generated list of test cases. Method is called in {@code test()}. 81 */ 82 public abstract void setProperties(); 83 84 /** 85 * Runs the test. 86 * 87 * @param classToTest expected name of outer class 88 * @param skipClasses classes that names should not be checked 89 */ 90 public void test(String classToTest, String...skipClasses) throws TestFailedException { 91 try { 92 String testName = getClass().getName(); 93 List<TestCase> testCases = generateTestCases(); 94 for (int i = 0; i < testCases.size(); ++i) { 95 TestCase test = testCases.get(i); 96 String testCaseName = testName + i + ".java"; 97 addTestCase(testCaseName); 98 writeToFileIfEnabled(Paths.get(testCaseName), test.getSource()); 99 test(classToTest, test, skipClasses); 100 } 101 } catch (Exception e) { 102 addFailure(e); 103 } finally { 104 checkStatus(); 105 } 106 } 107 108 /** 109 * If {@code flag} is {@code true} an outer class can not have static modifier. 110 * 111 * @param flag if {@code true} the outer class can not have static modifier 112 */ 113 public void setForbiddenWithoutStaticInOuterMods(boolean flag) { 114 isForbiddenWithoutStaticInOuterMods = flag; 115 } 116 117 /** 118 * Sets the possible access flags of an outer class. 119 * 120 * @param mods the possible access flags of an outer class 121 */ 122 public void setOuterAccessModifiers(Modifier...mods) { 123 outerAccessModifiers = mods; 124 } 125 126 /** 127 * Sets the possible flags of an outer class. 128 * 129 * @param mods the possible flags of an outer class 130 */ 131 public void setOuterOtherModifiers(Modifier...mods) { 132 outerOtherModifiers = mods; 133 } 134 135 /** 136 * Sets the possible access flags of an inner class. 137 * 138 * @param mods the possible access flags of an inner class 139 */ 140 public void setInnerAccessModifiers(Modifier...mods) { 141 innerAccessModifiers = mods; 142 } 143 144 /** 145 * Sets the possible flags of an inner class. 146 * 147 * @param mods the possible flags of an inner class 148 */ 149 public void setInnerOtherModifiers(Modifier...mods) { 150 innerOtherModifiers = mods; 151 } 152 153 /** 154 * Sets the suffix for the generated source. 155 * 156 * @param suffix a suffix 157 */ 158 public void setSuffix(String suffix) { 159 this.suffix = suffix; 160 } 161 162 /** 163 * Sets the prefix for the generated source. 164 * 165 * @param prefix a prefix 166 */ 167 public void setPrefix(String prefix) { 168 this.prefix = prefix; 169 } 170 171 /** 172 * If {@code true} synthetic class is generated. 173 * 174 * @param hasSyntheticClass if {@code true} synthetic class is generated 175 */ 176 public void setHasSyntheticClass(boolean hasSyntheticClass) { 177 this.hasSyntheticClass = hasSyntheticClass; 178 } 179 180 /** 181 * Sets the inner class type. 182 * 183 * @param innerClassType the inner class type 184 */ 185 public void setInnerClassType(ClassType innerClassType) { 186 this.innerClassType = innerClassType; 187 } 188 189 /** 190 * Sets the outer class type. 191 * 192 * @param outerClassType the outer class type 193 */ 194 public void setOuterClassType(ClassType outerClassType) { 195 this.outerClassType = outerClassType; 196 } 197 198 private void test(String classToTest, TestCase test, String...skipClasses) { 199 printf("Testing :\n%s\n", test.getSource()); 200 try { 201 Map<String, Set<String>> class2Flags = test.getFlags(); 202 ClassFile cf = readClassFile(compile(getCompileOptions(), test.getSource()) 203 .getClasses().get(classToTest)); 204 InnerClasses_attribute innerClasses = (InnerClasses_attribute) 205 cf.getAttribute(Attribute.InnerClasses); 206 int count = 0; 207 for (Attribute a : cf.attributes.attrs) { 208 if (a instanceof InnerClasses_attribute) { 209 ++count; 210 } 211 } 212 checkEquals(1, count, "Number of inner classes attribute"); 213 if (!checkNotNull(innerClasses, "InnerClasses attribute should not be null")) { 214 return; 215 } 216 checkEquals(cf.constant_pool. 217 getUTF8Info(innerClasses.attribute_name_index).value, "InnerClasses", 218 "innerClasses.attribute_name_index"); 219 // Inner Classes attribute consists of length (2 bytes) 220 // and 8 bytes for each inner class's entry. 221 checkEquals(innerClasses.attribute_length, 222 2 + 8 * class2Flags.size(), "innerClasses.attribute_length"); 223 checkEquals(innerClasses.number_of_classes, 224 class2Flags.size(), "innerClasses.number_of_classes"); 225 Set<String> visitedClasses = new HashSet<>(); 226 for (Info e : innerClasses.classes) { 227 String baseName = cf.constant_pool.getClassInfo( 228 e.inner_class_info_index).getBaseName(); 229 if (cf.major_version >= 51 && e.inner_name_index == 0) { 230 checkEquals(e.outer_class_info_index, 0, 231 "outer_class_info_index " 232 + "in case of inner_name_index is zero : " 233 + baseName); 234 } 235 String className = baseName.replaceFirst(".*\\$", ""); 236 checkTrue(class2Flags.containsKey(className), 237 className); 238 checkTrue(visitedClasses.add(className), 239 "there are no duplicates in attribute : " + className); 240 checkEquals(e.inner_class_access_flags.getInnerClassFlags(), 241 class2Flags.get(className), 242 "inner_class_access_flags " + className); 243 if (!Arrays.asList(skipClasses).contains(className)) { 244 checkEquals( 245 cf.constant_pool.getClassInfo(e.inner_class_info_index).getBaseName(), 246 classToTest + "$" + className, 247 "inner_class_info_index of " + className); 248 if (e.outer_class_info_index > 0) { 249 checkEquals( 250 cf.constant_pool.getClassInfo(e.outer_class_info_index).getName(), 251 classToTest, 252 "outer_class_info_index of " + className); 253 } 254 } 255 } 256 } catch (Exception e) { 257 addFailure(e); 258 } 259 } 260 261 /** 262 * Methods generates list of test cases. Method generates all possible combinations 263 * of acceptable flags for nested inner classes. 264 * 265 * @return generated list of test cases 266 */ 267 protected List<TestCase> generateTestCases() { 268 setProperties(); 269 List<TestCase> list = new ArrayList<>(); 270 271 List<List<Modifier>> outerMods = getAllCombinations(outerAccessModifiers, outerOtherModifiers); 272 List<List<Modifier>> innerMods = getAllCombinations(innerAccessModifiers, innerOtherModifiers); 273 274 for (List<Modifier> outerMod : outerMods) { 275 if (isForbiddenWithoutStaticInOuterMods && !outerMod.contains(Modifier.STATIC)) { 276 continue; 277 } 278 StringBuilder sb = new StringBuilder(); 279 sb.append("public class InnerClassesSrc {") 280 .append(toString(outerMod)).append(' ') 281 .append(outerClassType).append(' ') 282 .append(prefix).append(' ').append('\n'); 283 int count = 0; 284 Map<String, Set<String>> class2Flags = new HashMap<>(); 285 List<String> syntheticClasses = new ArrayList<>(); 286 for (List<Modifier> innerMod : innerMods) { 287 ++count; 288 String privateConstructor = ""; 289 if (hasSyntheticClass && !innerMod.contains(Modifier.ABSTRACT)) { 290 privateConstructor = "private A" + count + "() {}"; 291 syntheticClasses.add("new A" + count + "();"); 292 } 293 sb.append(toString(innerMod)).append(' '); 294 sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor)); 295 Set<String> flags = getFlags(innerClassType, innerMod); 296 class2Flags.put("A" + count, flags); 297 } 298 if (hasSyntheticClass) { 299 // Source to generate synthetic classes 300 sb.append(syntheticClasses.stream().collect(Collectors.joining(" ", "{", "}"))); 301 class2Flags.put("1", new HashSet<>(Arrays.asList("ACC_STATIC", "ACC_SYNTHETIC"))); 302 } 303 sb.append(suffix).append("\n}"); 304 getAdditionalFlags(class2Flags, outerClassType, outerMod.toArray(new Modifier[outerMod.size()])); 305 list.add(new TestCase(sb.toString(), class2Flags)); 306 } 307 return list; 308 } 309 310 /** 311 * Methods returns flags which must have type. 312 * 313 * @param type class, interface, enum or annotation 314 * @param mods modifiers 315 * @return set of access flags 316 */ 317 protected Set<String> getFlags(ClassType type, List<Modifier> mods) { 318 Set<String> flags = mods.stream() 319 .map(Modifier::getString) 320 .filter(str -> !str.isEmpty()) 321 .map(str -> "ACC_" + str.toUpperCase()) 322 .collect(Collectors.toSet()); 323 type.addSpecificFlags(flags); 324 return flags; 325 } 326 327 protected List<String> getCompileOptions() { 328 return Collections.emptyList(); 329 } 330 331 private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) { 332 List<List<Modifier>> list = new ArrayList<>(); 333 for (Modifier access : accessModifiers) { 334 for (int i = 0; i < otherModifiers.length; ++i) { 335 Modifier mod1 = otherModifiers[i]; 336 for (int j = i + 1; j < otherModifiers.length; ++j) { 337 Modifier mod2 = otherModifiers[j]; 338 if (isForbidden(mod1, mod2)) { 339 continue; 340 } 341 list.add(Arrays.asList(access, mod1, mod2)); 342 } 343 if (mod1 == Modifier.EMPTY) { 344 list.add(Collections.singletonList(access)); 345 } 346 } 347 } 348 return list; 349 } 350 351 private boolean isForbidden(Modifier mod1, Modifier mod2) { 352 return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT 353 || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL; 354 } 355 356 private String toString(List<Modifier> mods) { 357 return mods.stream() 358 .map(Modifier::getString) 359 .filter(s -> !s.isEmpty()) 360 .collect(Collectors.joining(" ")); 361 } 362 363 /** 364 * Method is called in generateTestCases(). 365 * If you need to add additional access flags, you should override this method. 366 * 367 * 368 * @param class2Flags map with flags 369 * @param type class, interface, enum or @annotation 370 * @param mods modifiers 371 */ 372 public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) { 373 class2Flags.values().forEach(type::addFlags); 374 } 375 376 public enum ClassType { 377 CLASS("class") { 378 @Override 379 public void addSpecificFlags(Set<String> flags) { 380 } 381 }, 382 INTERFACE("interface") { 383 @Override 384 public void addFlags(Set<String> flags) { 385 flags.add("ACC_STATIC"); 386 flags.add("ACC_PUBLIC"); 387 } 388 389 @Override 390 public void addSpecificFlags(Set<String> flags) { 391 flags.add("ACC_INTERFACE"); 392 flags.add("ACC_ABSTRACT"); 393 flags.add("ACC_STATIC"); 394 } 395 }, 396 ANNOTATION("@interface") { 397 @Override 398 public void addFlags(Set<String> flags) { 399 flags.add("ACC_STATIC"); 400 flags.add("ACC_PUBLIC"); 401 } 402 403 @Override 404 public void addSpecificFlags(Set<String> flags) { 405 flags.add("ACC_INTERFACE"); 406 flags.add("ACC_ABSTRACT"); 407 flags.add("ACC_STATIC"); 408 flags.add("ACC_ANNOTATION"); 409 } 410 }, 411 ENUM("enum") { 412 @Override 413 public void addSpecificFlags(Set<String> flags) { 414 flags.add("ACC_ENUM"); 415 flags.add("ACC_FINAL"); 416 flags.add("ACC_STATIC"); 417 } 418 }, 419 OTHER("") { 420 @Override 421 public void addSpecificFlags(Set<String> flags) { 422 } 423 }; 424 425 private final String classType; 426 427 ClassType(String clazz) { 428 this.classType = clazz; 429 } 430 431 public abstract void addSpecificFlags(Set<String> flags); 432 433 public String toString() { 434 return classType; 435 } 436 437 public void addFlags(Set<String> set) { 438 } 439 } 440 441 public enum Modifier { 442 PUBLIC("public"), PRIVATE("private"), 443 PROTECTED("protected"), DEFAULT("default"), 444 FINAL("final"), ABSTRACT("abstract"), 445 STATIC("static"), EMPTY(""); 446 447 private final String str; 448 449 Modifier(String str) { 450 this.str = str; 451 } 452 453 public String getString() { 454 return str; 455 } 456 } 457 }