ImportMembersTest.java revision 3294:9adfb22ff08f
1/*
2 * Copyright (c) 2014, 2016, 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 8065360
27 * @summary The test checks possibility of class members to be imported.
28 * @library /tools/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.javap
33 * @build ToolBox ImportMembersTest
34 * @run main ImportMembersTest
35 */
36
37import javax.tools.JavaCompiler;
38import javax.tools.ToolProvider;
39import java.io.StringWriter;
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.List;
43
44/**
45 * The test checks that members of a class, an enum, an interface or annotation
46 * can be imported with help of a static import or an import statement.
47 * The tests generates a code, compiles it and checks whether it can be compiled
48 * successfully or fails with a proper message.
49 * The following is the example of a test case:
50 * package pkg;
51 * class ChildA extends A {}
52 *
53 * package pkg;
54 * class A {
55 *     static class Inner {}
56 *     static Object field;
57 *     static void method() {}
58 * }
59 *
60 * package pkg;
61 * import static pkg.ChildA.method;
62 * public class Test {{
63 *     method();
64 * }}
65 *
66 */
67public class ImportMembersTest {
68
69    private static final String[] expectedErrorMessages = {
70            "Test.java:\\d+:\\d+: compiler.err.cant.resolve.location: .*\n1 error\n",
71            "Test.java:\\d+:\\d+: compiler.err.import.requires.canonical: .*\n1 error\n"
72    };
73
74    private static final String sourceTemplate =
75            "package pkg;\n" +
76            "#IMPORT\n" +
77            "public class Test {{\n" +
78            "    #STATEMENT\n" +
79            "}}\n";
80
81    public static void main(String[] args) {
82        new ImportMembersTest().test();
83    }
84
85    public void test() {
86        int passed = 0;
87        int total = 0;
88        for (ClassType classType : ClassType.values()) {
89            for (ImportType importType : ImportType.values()) {
90                for (MemberType memberType : MemberType.values()) {
91                    ++total;
92                    List<ToolBox.JavaSource> sources = classType.getSources();
93                    sources.add(new ToolBox.JavaSource("Test.java",
94                            generateSource(classType, memberType, importType)));
95
96                    CompilationResult compilationResult = compile(sources);
97                    boolean isErrorExpected = importType.hasError(classType, memberType);
98                    if (!compilationResult.isSuccessful) {
99                        if (isErrorExpected) {
100                            String expectedErrorMessage =
101                                    getExpectedErrorMessage(classType, importType, memberType);
102                            if (compilationResult.message.matches(expectedErrorMessage)) {
103                                ++passed;
104                            } else {
105                                reportFailure(sources, String.format("Expected compilation failure message:\n" +
106                                                "%s\ngot message:\n%s",
107                                        expectedErrorMessage, compilationResult.message));
108                            }
109                        } else {
110                            reportFailure(sources, String.format("Unexpected compilation failure:\n%s",
111                                    compilationResult.message));
112                        }
113                    } else {
114                        if (isErrorExpected) {
115                            reportFailure(sources, "Expected compilation failure.");
116                        } else {
117                            ++passed;
118                        }
119                    }
120                }
121            }
122        }
123        String message = String.format(
124                "Total test cases run: %d, passed: %d, failed: %d.",
125                total, passed, total - passed);
126        if (passed != total) {
127            throw new RuntimeException(message);
128        }
129        echo(message);
130    }
131
132    private String getExpectedErrorMessage(ClassType classType, ImportType importType, MemberType memberType) {
133        String expectedErrorMessage;
134        if (importType == ImportType.IMPORT && classType == ClassType.CHILD_A &&
135                memberType == MemberType.CLASS) {
136            expectedErrorMessage = expectedErrorMessages[1];
137        } else {
138            expectedErrorMessage = expectedErrorMessages[0];
139        }
140        return expectedErrorMessage;
141    }
142
143    private void reportFailure(List<ToolBox.JavaSource> sources, String message) {
144        echo("Test case failed!");
145        printSources(sources);
146        echo(message);
147        echo();
148    }
149
150    private String generateSource(ClassType classType, MemberType memberType, ImportType importType) {
151        String importString = importType.generateImport(classType.getClassName(), memberType.getMemberType());
152        String statement;
153        if (importType.hasError(classType, memberType)) {
154            // if the source code has a compilation error, nothing is added.
155            // just to prevent the compiler from appending additional
156            // compilation errors to output
157            statement = "";
158        } else if (memberType == MemberType.STAR) {
159            // in case of import-on-demand, every class member is used
160            if (importType == ImportType.STATIC_IMPORT) {
161                statement = MemberType.CLASS.getStatement() + "\n    "
162                        + MemberType.FIELD.getStatement();
163                // an annotation does not have a static method.
164                if (classType != ClassType.D) {
165                    statement += "\n    " + MemberType.METHOD.getStatement() + "\n";
166                }
167            } else {
168                statement = classType != ClassType.CHILD_A
169                        ? MemberType.CLASS.getStatement() : "";
170            }
171        } else {
172            statement = memberType.getStatement();
173        }
174        return sourceTemplate
175                .replace("#IMPORT", importString)
176                .replace("#STATEMENT", statement);
177    }
178
179    private static class CompilationResult {
180        public final boolean isSuccessful;
181        public final String message;
182
183        public CompilationResult(boolean isSuccessful, String message) {
184            this.isSuccessful = isSuccessful;
185            this.message = message;
186        }
187    }
188
189    private CompilationResult compile(List<ToolBox.JavaSource> sources) {
190        StringWriter writer = new StringWriter();
191        JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
192        Boolean call = jc.getTask(writer, null, null, Arrays.asList("-XDrawDiagnostics"), null, sources).call();
193        return new CompilationResult(call, writer.toString().replace(ToolBox.lineSeparator, "\n"));
194    }
195
196    public void printSources(List<ToolBox.JavaSource> sources) {
197        for (ToolBox.JavaSource javaSource : sources) {
198            echo(javaSource.getCharContent(true).toString());
199        }
200    }
201
202    public void echo() {
203        echo("");
204    }
205
206    public void echo(String output) {
207        printf(output + "\n");
208    }
209
210    public void printf(String template, Object...args) {
211        System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator));
212    }
213
214    enum ClassType {
215        A("A",
216        "package pkg;\n" +
217        "class A {\n" +
218        "    static class Inner {}\n" +
219        "    static Object field;\n" +
220        "    static void method() {}\n" +
221        "}\n"
222        ),
223        B("B",
224        "package pkg;\n" +
225        "interface B {\n" +
226        "    static class Inner {}\n" +
227        "    static Object field = null;\n" +
228        "    static void method() {}\n" +
229        "}\n"
230        ),
231        C("C",
232        "package pkg;\n" +
233        "enum C {field;\n" +
234        "    static class Inner {}\n" +
235        "    static void method() {}\n" +
236        "}\n"
237        ),
238        D("D",
239        "package pkg;\n" +
240        "@interface D {\n" +
241        "    static class Inner {}\n" +
242        "    static Object field = null;\n" +
243        "}\n"
244        ),
245        CHILD_A("ChildA",
246        "package pkg;\n" +
247        "class ChildA extends A {}\n",
248        A);
249
250        private final String className;
251        private final String source;
252        private final ClassType parentType;
253
254        private ClassType(String className, String source) {
255            this(className, source, null);
256        }
257
258        private ClassType(String className, String source, ClassType classType) {
259            this.className = className;
260            this.source = source;
261            this.parentType = classType;
262        }
263
264        public String getClassName() {
265            return className;
266        }
267
268        public List<ToolBox.JavaSource> getSources() {
269            List<ToolBox.JavaSource> sourceList = new ArrayList<>();
270            ClassType current = this;
271            while (current != null) {
272                sourceList.add(new ToolBox.JavaSource(current.className, current.source));
273                current = current.parentType;
274            }
275            return sourceList;
276        }
277    }
278
279    enum MemberType {
280        CLASS("Inner", "Inner inner = null;"),
281        FIELD("field", "Object o = field;"),
282        METHOD("method", "method();"),
283        STAR("*", ""),
284        NOT_EXIST("NotExist", "");
285
286        private final String memberType;
287        private final String statement;
288
289        private MemberType(String memberType, String statement) {
290            this.memberType = memberType;
291            this.statement = statement;
292        }
293
294        public String getStatement() {
295            return statement;
296        }
297
298        public String getMemberType() {
299            return memberType;
300        }
301    }
302
303    enum ImportType {
304        IMPORT("import pkg.#CLASS_NAME.#MEMBER_NAME;"),
305        STATIC_IMPORT("import static pkg.#CLASS_NAME.#MEMBER_NAME;");
306
307        private final String importType;
308
309        private ImportType(String importType) {
310            this.importType = importType;
311        }
312
313        public String generateImport(String className, String memberName) {
314            return importType
315                    .replace("#CLASS_NAME", className)
316                    .replace("#MEMBER_NAME", memberName);
317        }
318
319        public boolean hasError(ClassType classType, MemberType memberType) {
320            switch (memberType) {
321                case FIELD:
322                    return this != ImportType.STATIC_IMPORT;
323                case METHOD:
324                    return this != ImportType.STATIC_IMPORT || classType == ClassType.D;
325                case NOT_EXIST:
326                    return true;
327                case CLASS:
328                    return classType.parentType != null && this != STATIC_IMPORT;
329                default:
330                    return false;
331            }
332        }
333    }
334}
335