EnclosingMethodTest.java revision 3294:9adfb22ff08f
1/*
2 * Copyright (c) 2014, 2015, 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 8042931
27 * @summary Checking EnclosingMethod attribute of anonymous/local class.
28 * @library /tools/lib /tools/javac/lib ../lib
29 * @modules jdk.compiler/com.sun.tools.javac.api
30 *          jdk.compiler/com.sun.tools.javac.file
31 *          jdk.compiler/com.sun.tools.javac.main
32 *          jdk.jdeps/com.sun.tools.classfile
33 *          jdk.jdeps/com.sun.tools.javap
34 * @build EnclosingMethodTest TestBase TestResult InMemoryFileManager ToolBox
35 * @run main EnclosingMethodTest
36 */
37
38import com.sun.tools.classfile.Attribute;
39import com.sun.tools.classfile.ClassFile;
40import com.sun.tools.classfile.EnclosingMethod_attribute;
41
42import java.io.File;
43import java.io.FilenameFilter;
44import java.lang.annotation.Retention;
45import java.lang.annotation.RetentionPolicy;
46import java.util.HashMap;
47import java.util.HashSet;
48import java.util.Map;
49import java.util.Set;
50import java.util.stream.Stream;
51
52/**
53 * The test checks the enclosing method attribute of anonymous/local classes.
54 * The top-level class contains the anonymous and local classes to be tested. The test examines
55 * each inner class and determine whether the class should have the EnclosingMethod attribute or not.
56 * Golden information about enclosing methods are held in annotation {@code ExpectedEnclosingMethod}.
57 *
58 * The test assumes that a class must have the EnclosingMethod attribute if the class is annotated or
59 * if its parent class is annotated in case of anonymous class. In addition, classes
60 * named {@code VariableInitializer} are introduced to test variable initializer cases. These classes
61 * must not have the enclosing method attribute, but its anonymous derived class must.
62 * After classification of classes, the test checks whether classes contain the correct enclosing
63 * method attribute in case of anonymous/local class, or checks whether classes do not contain
64 * the EnclosingMethod attribute, otherwise.
65 *
66 * Test cases:
67 *   top-level class as enclosing class:
68 *     1. anonymous and local classes in static initializer;
69 *     2. anonymous and local classes in instance initializer;
70 *     3. anonymous and local classes in lambda;
71 *     4. anonymous and local classes in constructor;
72 *     5. anonymous and local classes in method;
73 *     6. static and instance variable initializer.
74 *
75 *   inner class as enclosing class:
76 *     1. anonymous and local classes in static initializer;
77 *     2. anonymous and local classes in instance initializer;
78 *     3. anonymous and local classes in lambda;
79 *     4. anonymous and local classes in constructor;
80 *     5. anonymous and local classes in method;
81 *     6. static and instance variable initializer.
82 *
83 *   enum as enclosing class:
84 *     1. anonymous and local classes in static initializer;
85 *     2. anonymous and local classes in instance initializer;
86 *     3. anonymous and local classes in lambda;
87 *     4. anonymous and local classes in constructor;
88 *     5. anonymous and local classes in method;
89 *     6. static and instance variable initializer.
90 *
91 *   interface as enclosing class:
92 *     1. anonymous and local classes in lambda;
93 *     2. anonymous and local classes in static method;
94 *     3. anonymous and local classes in default method;
95 *     4. static variable initializer.
96 *
97 *   annotation as enclosing class:
98 *     1. anonymous and local classes in lambda;
99 *     2. static variable initializer.
100 */
101public class EnclosingMethodTest extends TestResult {
102
103    private final Map<Class<?>, ExpectedEnclosingMethod> class2EnclosingMethod = new HashMap<>();
104    private final Set<Class<?>> noEnclosingMethod = new HashSet<>();
105
106    public EnclosingMethodTest() throws ClassNotFoundException {
107        Class<EnclosingMethodTest> outerClass = EnclosingMethodTest.class;
108        String outerClassName = outerClass.getSimpleName();
109        File testClasses = getClassDir();
110        FilenameFilter filter = (dir, name) -> name.matches(outerClassName + ".*\\.class");
111
112        for (File file : testClasses.listFiles(filter)) {
113            Class<?> clazz = Class.forName(file.getName().replace(".class", ""));
114            if (clazz.isAnonymousClass()) {
115                // anonymous class cannot be annotated, information is in its parent class.
116                ExpectedEnclosingMethod declaredAnnotation =
117                        clazz.getSuperclass().getDeclaredAnnotation(ExpectedEnclosingMethod.class);
118                class2EnclosingMethod.put(clazz, declaredAnnotation);
119            } else {
120                ExpectedEnclosingMethod enclosingMethod = clazz.getDeclaredAnnotation(ExpectedEnclosingMethod.class);
121                // if class is annotated and it does not contain information for variable initializer cases,
122                // then it must have the enclosing method attribute.
123                if (enclosingMethod != null && !clazz.getSimpleName().contains("VariableInitializer")) {
124                    class2EnclosingMethod.put(clazz, enclosingMethod);
125                } else {
126                    noEnclosingMethod.add(clazz);
127                }
128            }
129        }
130    }
131
132    public void test() throws TestFailedException {
133        try {
134            testEnclosingMethodAttribute();
135            testLackOfEnclosingMethodAttribute();
136        } finally {
137            checkStatus();
138        }
139    }
140
141    private void testLackOfEnclosingMethodAttribute() {
142        for (Class<?> clazz : noEnclosingMethod) {
143            try {
144                addTestCase("Class should not have EnclosingMethod attribute : " + clazz);
145                ClassFile classFile = readClassFile(clazz);
146                checkEquals(countEnclosingMethodAttributes(classFile),
147                        0l, "number of the EnclosingMethod attribute in the class is zero : "
148                                + classFile.getName());
149            } catch (Exception e) {
150                addFailure(e);
151            }
152        }
153    }
154
155    private void testEnclosingMethodAttribute() {
156        class2EnclosingMethod.forEach((clazz, enclosingMethod) -> {
157            try {
158                String info = enclosingMethod.info() + " "
159                        + (clazz.isAnonymousClass() ? "anonymous" : "local");
160                addTestCase(info);
161                printf("Testing test case : %s\n", info);
162                ClassFile classFile = readClassFile(clazz);
163                String className = clazz.getName();
164                checkEquals(countEnclosingMethodAttributes(classFile), 1l,
165                        "number of the EnclosingMethod attribute in the class is one : "
166                                + clazz);
167                EnclosingMethod_attribute attr = (EnclosingMethod_attribute)
168                        classFile.getAttribute(Attribute.EnclosingMethod);
169
170                if (!checkNotNull(attr, "the EnclosingMethod attribute is not null : " + className)) {
171                    // stop checking, attr is null. test case failed
172                    return;
173                }
174                checkEquals(classFile.constant_pool.getUTF8Value(attr.attribute_name_index),
175                        "EnclosingMethod",
176                        "attribute_name_index of EnclosingMethod attribute in the class : " + className);
177                checkEquals(attr.attribute_length, 4,
178                        "attribute_length of EnclosingMethod attribute in the class : " + className);
179                String expectedClassName = enclosingMethod.enclosingClazz().getName();
180                checkEquals(classFile.constant_pool.getClassInfo(attr.class_index).getName(),
181                        expectedClassName, String.format(
182                        "enclosing class of EnclosingMethod attribute in the class %s is %s",
183                                className, expectedClassName));
184
185                String expectedMethodName = enclosingMethod.enclosingMethod();
186                if (expectedMethodName.isEmpty()) {
187                    // class does not have an enclosing method
188                    checkEquals(attr.method_index, 0, String.format(
189                            "enclosing method of EnclosingMethod attribute in the class %s is null", className));
190                } else {
191                    String methodName = classFile.constant_pool.getNameAndTypeInfo(attr.method_index).getName();
192                    checkTrue(methodName.startsWith(expectedMethodName), String.format(
193                            "enclosing method of EnclosingMethod attribute in the class %s" +
194                                    " is method name %s" +
195                                    ", actual method name is %s",
196                            className, expectedMethodName, methodName));
197                }
198            } catch (Exception e) {
199                addFailure(e);
200            }
201        });
202    }
203
204    private long countEnclosingMethodAttributes(ClassFile classFile) {
205        return Stream.of(classFile.attributes.attrs)
206                .filter(x -> x instanceof EnclosingMethod_attribute)
207                .count();
208    }
209
210    @Retention(RetentionPolicy.RUNTIME)
211    public @interface ExpectedEnclosingMethod {
212        String info();
213        Class<?> enclosingClazz();
214        String enclosingMethod() default "";
215    }
216
217    public static void main(String[] args) throws ClassNotFoundException, TestFailedException {
218        new EnclosingMethodTest().test();
219    }
220
221    // Test cases: enclosing class is a top-level class
222    static {
223        // anonymous and local classes in static initializer
224        @ExpectedEnclosingMethod(
225                info = "EnclosingStaticInitialization in EnclosingMethodTest",
226                enclosingClazz = EnclosingMethodTest.class
227        )
228        class EnclosingStaticInitialization {
229        }
230        new EnclosingStaticInitialization() {
231        };
232    }
233
234    {
235        // anonymous and local classes in instance initializer
236        @ExpectedEnclosingMethod(
237                info = "EnclosingInitialization in EnclosingMethodTest",
238                enclosingClazz = EnclosingMethodTest.class
239        )
240        class EnclosingInitialization {
241        }
242        new EnclosingInitialization() {
243        };
244    }
245
246    Runnable lambda = () -> {
247        // anonymous and local classes in lambda
248        @ExpectedEnclosingMethod(
249                info = "EnclosingLambda in EnclosingMethodTest",
250                enclosingMethod = "lambda",
251                enclosingClazz = EnclosingMethodTest.class
252        )
253        class EnclosingLambda {
254        }
255        new EnclosingLambda() {
256        };
257    };
258
259    EnclosingMethodTest(int i) {
260        // anonymous and local classes in constructor
261        @ExpectedEnclosingMethod(
262                info = "EnclosingConstructor in EnclosingMethodTest",
263                enclosingMethod = "<init>",
264                enclosingClazz = EnclosingMethodTest.class
265        )
266        class EnclosingConstructor {
267        }
268        new EnclosingConstructor() {
269        };
270    }
271
272    void method() {
273        // anonymous and local classes in method
274        @ExpectedEnclosingMethod(
275                info = "EnclosingMethod in EnclosingMethodTest",
276                enclosingMethod = "method",
277                enclosingClazz = EnclosingMethodTest.class
278        )
279        class EnclosingMethod {
280        }
281        new EnclosingMethod() {
282        };
283    }
284
285    @ExpectedEnclosingMethod(
286            info = "VariableInitializer in EnclosingMethodTest",
287            enclosingClazz = EnclosingMethodTest.class
288    )
289    static class VariableInitializer {
290    }
291
292    // static variable initializer
293    private static final VariableInitializer cvi = new VariableInitializer() {
294    };
295
296    // instance variable initializer
297    private final VariableInitializer ivi = new VariableInitializer() {
298    };
299
300    // Test cases: enclosing class is an inner class
301    public static class notEnclosing01 {
302        static {
303            // anonymous and local classes in static initializer
304            @ExpectedEnclosingMethod(
305                    info = "EnclosingStaticInitialization in notEnclosing01",
306                    enclosingClazz = notEnclosing01.class
307            )
308            class EnclosingStaticInitialization {
309            }
310            new EnclosingStaticInitialization() {
311            };
312        }
313
314        {
315            // anonymous and local classes in instance initializer
316            @ExpectedEnclosingMethod(
317                    info = "EnclosingInitialization in notEnclosing01",
318                    enclosingClazz = notEnclosing01.class
319            )
320            class EnclosingInitialization {
321            }
322            new EnclosingInitialization() {
323            };
324        }
325
326        Runnable lambda = () -> {
327            // anonymous and local classes in lambda
328            @ExpectedEnclosingMethod(
329                    info = "EnclosingLambda in notEnclosing01",
330                    enclosingMethod = "lambda",
331                    enclosingClazz = notEnclosing01.class
332            )
333            class EnclosingLambda {
334            }
335            new EnclosingLambda() {
336            };
337        };
338
339        notEnclosing01() {
340            // anonymous and local classes in constructor
341            @ExpectedEnclosingMethod(
342                    info = "EnclosingConstructor in notEnclosing01",
343                    enclosingMethod = "<init>",
344                    enclosingClazz = notEnclosing01.class
345            )
346            class EnclosingConstructor {
347            }
348            new EnclosingConstructor() {
349            };
350        }
351
352        void method() {
353            // anonymous and local classes in method
354            @ExpectedEnclosingMethod(
355                    info = "EnclosingMethod in notEnclosing01",
356                    enclosingMethod = "method",
357                    enclosingClazz = notEnclosing01.class
358            )
359            class EnclosingMethod {
360            }
361            new EnclosingMethod() {
362            };
363        }
364
365        @ExpectedEnclosingMethod(
366                info = "VariableInitializer in notEnclosing01",
367                enclosingClazz = notEnclosing01.class
368        )
369        static class VariableInitializer {
370        }
371
372        // static variable initializer
373        private static final VariableInitializer cvi = new VariableInitializer() {
374        };
375
376        // instance variable initializer
377        private final VariableInitializer ivi = new VariableInitializer() {
378        };
379    }
380
381    // Test cases: enclosing class is an interface
382    public interface notEnclosing02 {
383        Runnable lambda = () -> {
384            // anonymous and local classes in lambda
385            @ExpectedEnclosingMethod(
386                    info = "EnclosingLambda in notEnclosing02",
387                    enclosingMethod = "lambda",
388                    enclosingClazz = notEnclosing02.class
389            )
390            class EnclosingLambda {
391            }
392            new EnclosingLambda() {
393            };
394        };
395
396        static void staticMethod() {
397            // anonymous and local classes in static method
398            @ExpectedEnclosingMethod(
399                    info = "EnclosingMethod in notEnclosing02",
400                    enclosingMethod = "staticMethod",
401                    enclosingClazz = notEnclosing02.class
402            )
403            class EnclosingMethod {
404            }
405            new EnclosingMethod() {
406            };
407        }
408
409        default void defaultMethod() {
410            // anonymous and local classes in default method
411            @ExpectedEnclosingMethod(
412                    info = "EnclosingMethod in notEnclosing02",
413                    enclosingMethod = "defaultMethod",
414                    enclosingClazz = notEnclosing02.class
415            )
416            class EnclosingMethod {
417            }
418            new EnclosingMethod() {
419            };
420        }
421
422        @ExpectedEnclosingMethod(
423                info = "VariableInitializer in notEnclosing02",
424                enclosingClazz = notEnclosing02.class
425        )
426        static class VariableInitializer {
427        }
428
429        // static variable initializer
430        VariableInitializer cvi = new VariableInitializer() {
431        };
432    }
433
434    // Test cases: enclosing class is an enum
435    public enum notEnclosing03 {;
436
437        static {
438            // anonymous and local classes in static initializer
439            @ExpectedEnclosingMethod(
440                    info = "EnclosingStaticInitialization in notEnclosing03",
441                    enclosingClazz = notEnclosing03.class
442            )
443            class EnclosingStaticInitialization {
444            }
445            new EnclosingStaticInitialization() {
446            };
447        }
448
449        {
450            // anonymous and local classes in instance initializer
451            @ExpectedEnclosingMethod(
452                    info = "EnclosingInitialization in notEnclosing03",
453                    enclosingClazz = notEnclosing03.class
454            )
455            class EnclosingInitialization {
456            }
457            new EnclosingInitialization() {
458            };
459        }
460
461        Runnable lambda = () -> {
462            // anonymous and local classes in lambda
463            @ExpectedEnclosingMethod(
464                    info = "EnclosingLambda in notEnclosing03",
465                    enclosingMethod = "lambda",
466                    enclosingClazz = notEnclosing03.class
467            )
468            class EnclosingLambda {
469            }
470            new EnclosingLambda() {
471            };
472        };
473
474        notEnclosing03() {
475            // anonymous and local classes in constructor
476            @ExpectedEnclosingMethod(
477                    info = "EnclosingConstructor in notEnclosing03",
478                    enclosingMethod = "<init>",
479                    enclosingClazz = notEnclosing03.class
480            )
481            class EnclosingConstructor {
482            }
483            new EnclosingConstructor() {
484            };
485        }
486
487        void method() {
488            // anonymous and local classes in method
489            @ExpectedEnclosingMethod(
490                    info = "EnclosingMethod in notEnclosing03",
491                    enclosingMethod = "method",
492                    enclosingClazz = notEnclosing03.class
493            )
494            class EnclosingMethod {
495            }
496            new EnclosingMethod() {
497            };
498        }
499
500        @ExpectedEnclosingMethod(
501                info = "VariableInitializer in notEnclosing03",
502                enclosingClazz = notEnclosing03.class
503        )
504        static class VariableInitializer {
505        }
506
507        // static variable initializer
508        private static final VariableInitializer cvi = new VariableInitializer() {
509        };
510
511        // instance variable initializer
512        private final VariableInitializer ivi = new VariableInitializer() {
513        };
514    }
515
516    // Test cases: enclosing class is an annotation
517    public @interface notEnclosing04 {
518        Runnable lambda = () -> {
519            // anonymous and local classes in lambda
520            @ExpectedEnclosingMethod(
521                    info = "EnclosingLambda in notEnclosing04",
522                    enclosingMethod = "lambda",
523                    enclosingClazz = notEnclosing04.class
524            )
525            class EnclosingLambda {
526            }
527            new EnclosingLambda() {
528            };
529        };
530
531        @ExpectedEnclosingMethod(
532                info = "VariableInitializer in notEnclosing04",
533                enclosingClazz = notEnclosing04.class
534        )
535        static class VariableInitializer {
536        }
537
538        // static variable initializer
539        VariableInitializer cvi = new VariableInitializer() {
540        };
541    }
542}
543