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