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