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