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 dependencies through type parameters and implements/extends statements. 28 * @library /tools/lib 29 * @modules jdk.compiler/com.sun.tools.javac.api 30 * jdk.compiler/com.sun.tools.javac.main 31 * @build toolbox.ToolBox ImportDependenciesTest 32 * @run main ImportDependenciesTest 33 */ 34 35 import javax.tools.JavaCompiler; 36 import javax.tools.ToolProvider; 37 import java.io.StringWriter; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 44 import toolbox.ToolBox; 45 46 /** 47 * The test checks that code which contains dependencies through type parameters, 48 * implements/extends statements compiles properly. All combinations of 49 * import types are tested. In addition, the test checks various combinations 50 * of classes. 51 */ 52 public class ImportDependenciesTest { 53 54 private static final String sourceTemplate = 55 "package pkg;\n" + 56 "#IMPORT\n" + 57 "public class Test {\n" + 58 " static #CLASS_TYPE InnerClass#TYPE_PARAMETER #PARENT {\n" + 59 " static class Inner1 {\n" + 60 " }\n" + 61 " interface Inner2 {\n" + 62 " }\n" + 63 " interface Inner3 {\n" + 64 " }\n" + 65 " }\n" + 66 " static class InnerClass1 {\n" + 67 " static class IInner1 {\n" + 68 " }\n" + 69 " }\n" + 70 " static class InnerInterface1 {\n" + 71 " interface IInner2 {\n" + 72 " }\n" + 73 " }\n" + 74 " static class InnerInterface2 {\n" + 75 " interface IInner3 {\n" + 76 " }\n" + 77 " }\n" + 78 "}"; 79 main(String[] args)80 public static void main(String[] args) { 81 new ImportDependenciesTest().test(); 82 } 83 test()84 public void test() { 85 List<List<InnerClass>> typeParameters = InnerClass.getAllCombinationsForTypeParameter(); 86 List<List<InnerClass>> parents = InnerClass.getAllCombinationsForInheritance(); 87 int passed = 0; 88 int total = 0; 89 for (ClassType classType : ClassType.values()) { 90 for (List<InnerClass> parent : parents) { 91 if (!classType.canBeInherited(parent)) { 92 continue; 93 } 94 for (List<InnerClass> typeParameter : typeParameters) { 95 List<InnerClass> innerClasses = new ArrayList<>(typeParameter); 96 innerClasses.addAll(parent); 97 for (ImportType importType : ImportType.values()) { 98 ++total; 99 String source = sourceTemplate 100 .replace("#IMPORT", importType.generateImports(innerClasses)) 101 .replace("#CLASS_TYPE", classType.getClassType()) 102 .replace("#TYPE_PARAMETER", generateTypeParameter(typeParameter)) 103 .replace("#PARENT", classType.generateInheritanceString(parent)); 104 CompilationResult result = compile(new ToolBox.JavaSource("pkg/Test.java", source)); 105 if (!result.isSuccessful) { 106 echo("Compilation failed!"); 107 echo(source); 108 echo(result.message); 109 echo(); 110 } else { 111 ++passed; 112 } 113 } 114 } 115 } 116 } 117 String message = String.format( 118 "Total test cases run: %d, passed: %d, failed: %d.", 119 total, passed, total - passed); 120 if (passed != total) { 121 throw new RuntimeException(message); 122 } 123 echo(message); 124 } 125 generateTypeParameter(List<InnerClass> typeParameters)126 private String generateTypeParameter(List<InnerClass> typeParameters) { 127 if (typeParameters.isEmpty()) { 128 return ""; 129 } 130 return String.format("<T extends %s>", typeParameters.stream() 131 .map(InnerClass::getSimpleName) 132 .collect(Collectors.joining(" & "))); 133 } 134 135 private static class CompilationResult { 136 public final boolean isSuccessful; 137 public final String message; 138 CompilationResult(boolean isSuccessful, String message)139 public CompilationResult(boolean isSuccessful, String message) { 140 this.isSuccessful = isSuccessful; 141 this.message = message; 142 } 143 } 144 compile(ToolBox.JavaSource...sources)145 private CompilationResult compile(ToolBox.JavaSource...sources) { 146 StringWriter writer = new StringWriter(); 147 JavaCompiler jc = ToolProvider.getSystemJavaCompiler(); 148 Boolean call = jc.getTask(writer, null, null, null, null, Arrays.asList(sources)).call(); 149 return new CompilationResult(call, writer.toString().replace(ToolBox.lineSeparator, "\n")); 150 } 151 echo()152 public void echo() { 153 echo(""); 154 } 155 echo(String output)156 public void echo(String output) { 157 printf(output + "\n"); 158 } 159 printf(String template, Object...args)160 public void printf(String template, Object...args) { 161 System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator)); 162 } 163 164 enum ImportType { 165 IMPORT("import"), STATIC_IMPORT("import static"), 166 IMPORT_ON_DEMAND("import"), STATIC_IMPORT_ON_DEMAND("import static"); 167 168 private final String importType; ImportType(String importType)169 private ImportType(String importType) { 170 this.importType = importType; 171 } 172 isOnDemand()173 private boolean isOnDemand() { 174 return this == IMPORT_ON_DEMAND || this == STATIC_IMPORT_ON_DEMAND; 175 } 176 generateImports(List<InnerClass> innerClasses)177 public String generateImports(List<InnerClass> innerClasses) { 178 return innerClasses.stream() 179 .map(i -> isOnDemand() ? i.getPackageName() + ".*" : i.getCanonicalName()) 180 .distinct() 181 .map(s -> String.format("%s %s;", importType, s)) 182 .collect(Collectors.joining("\n")); 183 } 184 } 185 186 enum ClassType { 187 CLASS("class") { 188 @Override canBeInherited(List<InnerClass> innerClasses)189 public boolean canBeInherited(List<InnerClass> innerClasses) { 190 return true; 191 } 192 193 @Override generateInheritanceString(List<InnerClass> innerClasses)194 public String generateInheritanceString(List<InnerClass> innerClasses) { 195 if (innerClasses.isEmpty()) { 196 return ""; 197 } 198 StringBuilder sb = new StringBuilder(); 199 InnerClass firstClass = innerClasses.get(0); 200 if (firstClass.isClass()) { 201 sb.append("extends ").append(firstClass.getSimpleName()).append(" "); 202 } 203 String str = innerClasses.stream() 204 .filter(x -> !x.isClass()) 205 .map(InnerClass::getSimpleName) 206 .collect(Collectors.joining(", ")); 207 if (!str.isEmpty()) { 208 sb.append("implements ").append(str); 209 } 210 return sb.toString(); 211 } 212 }, INTERFACE("interface") { 213 @Override canBeInherited(List<InnerClass> innerClasses)214 public boolean canBeInherited(List<InnerClass> innerClasses) { 215 return !innerClasses.stream().anyMatch(InnerClass::isClass); 216 } 217 218 @Override generateInheritanceString(List<InnerClass> innerClasses)219 public String generateInheritanceString(List<InnerClass> innerClasses) { 220 if (innerClasses.isEmpty()) { 221 return ""; 222 } 223 return "extends " + innerClasses.stream() 224 .map(InnerClass::getSimpleName) 225 .collect(Collectors.joining(", ")); 226 } 227 }; 228 229 private final String classType; ClassType(String classType)230 private ClassType(String classType) { 231 this.classType = classType; 232 } 233 getClassType()234 public String getClassType() { 235 return classType; 236 } 237 canBeInherited(List<InnerClass> innerClasses)238 public abstract boolean canBeInherited(List<InnerClass> innerClasses); 239 generateInheritanceString(List<InnerClass> innerClasses)240 public abstract String generateInheritanceString(List<InnerClass> innerClasses); 241 } 242 243 enum InnerClass { 244 INNER_1("pkg.Test.InnerClass.Inner1", true), 245 INNER_2("pkg.Test.InnerClass.Inner2", true), 246 INNER_3("pkg.Test.InnerClass.Inner3", true), 247 IINNER_1("pkg.Test.InnerClass1.IInner1", false), 248 IINNER_2("pkg.Test.InnerInterface1.IInner2", false), 249 IINNER_3("pkg.Test.InnerInterface2.IInner3", false); 250 251 private final String canonicalName; 252 private final boolean isForTypeParameter; 253 InnerClass(String canonicalName, boolean isForTypeParameter)254 private InnerClass(String canonicalName, boolean isForTypeParameter) { 255 this.canonicalName = canonicalName; 256 this.isForTypeParameter = isForTypeParameter; 257 } 258 getAllCombinations(boolean isTypeParameter)259 private static List<List<InnerClass>> getAllCombinations(boolean isTypeParameter) { 260 List<List<InnerClass>> result = new ArrayList<>(); 261 List<InnerClass> tmpl = Stream.of(InnerClass.values()) 262 .filter(i -> i.isForTypeParameter() == isTypeParameter) 263 .collect(Collectors.toCollection(ArrayList::new)); 264 result.add(Arrays.asList()); 265 for (int i = 0; i < tmpl.size(); ++i) { 266 result.add(Arrays.asList(tmpl.get(i))); 267 for (int j = i + 1; j < tmpl.size(); ++j) { 268 result.add(Arrays.asList(tmpl.get(i), tmpl.get(j))); 269 } 270 } 271 result.add(tmpl); 272 return result; 273 } 274 getAllCombinationsForTypeParameter()275 public static List<List<InnerClass>> getAllCombinationsForTypeParameter() { 276 return getAllCombinations(true); 277 } 278 getAllCombinationsForInheritance()279 public static List<List<InnerClass>> getAllCombinationsForInheritance() { 280 return getAllCombinations(false); 281 } 282 getCanonicalName()283 public String getCanonicalName() { 284 return canonicalName; 285 } 286 getSimpleName()287 public String getSimpleName() { 288 String cName = getCanonicalName(); 289 return cName.substring(cName.lastIndexOf('.') + 1); 290 } 291 getPackageName()292 public String getPackageName() { 293 String cName = getCanonicalName(); 294 int dotIndex = cName.lastIndexOf('.'); 295 return dotIndex == -1 ? "" : cName.substring(0, dotIndex); 296 } 297 isClass()298 public boolean isClass() { 299 return this == INNER_1 || this == IINNER_1; 300 } isForTypeParameter()301 private boolean isForTypeParameter() { 302 return isForTypeParameter; 303 } 304 } 305 } 306