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