1 /* 2 * Copyright (c) 2012, 2014, 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 8002099 8010822 27 * @summary Add support for intersection types in cast expression 28 */ 29 30 import com.sun.source.util.JavacTask; 31 import com.sun.tools.javac.util.ListBuffer; 32 import java.net.URI; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.List; 36 import javax.tools.Diagnostic; 37 import javax.tools.JavaCompiler; 38 import javax.tools.JavaFileObject; 39 import javax.tools.SimpleJavaFileObject; 40 import javax.tools.StandardJavaFileManager; 41 import javax.tools.ToolProvider; 42 43 public class IntersectionTargetTypeTest { 44 45 static int checkCount = 0; 46 47 enum BoundKind { 48 INTF, 49 CLASS; 50 } 51 52 enum MethodKind { 53 NONE(false), 54 ABSTRACT_M(true), 55 DEFAULT_M(false), 56 ABSTRACT_G(true), 57 DEFAULT_G(false); 58 59 boolean isAbstract; 60 MethodKind(boolean isAbstract)61 MethodKind(boolean isAbstract) { 62 this.isAbstract = isAbstract; 63 } 64 } 65 66 enum TypeKind { 67 A("interface A { }\n", "A", BoundKind.INTF, MethodKind.NONE), 68 B("interface B { default void m() { } }\n", "B", BoundKind.INTF, MethodKind.DEFAULT_M), 69 C("interface C { void m(); }\n", "C", BoundKind.INTF, MethodKind.ABSTRACT_M), 70 D("interface D extends B { }\n", "D", BoundKind.INTF, MethodKind.DEFAULT_M), 71 E("interface E extends C { }\n", "E", BoundKind.INTF, MethodKind.ABSTRACT_M), 72 F("interface F extends C { void g(); }\n", "F", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.ABSTRACT_M), 73 G("interface G extends B { void g(); }\n", "G", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.DEFAULT_M), 74 H("interface H extends A { void g(); }\n", "H", BoundKind.INTF, MethodKind.ABSTRACT_G), 75 OBJECT("", "Object", BoundKind.CLASS), 76 STRING("", "String", BoundKind.CLASS); 77 78 String declStr; 79 String typeStr; 80 BoundKind boundKind; 81 MethodKind[] methodKinds; 82 TypeKind(String declStr, String typeStr, BoundKind boundKind, MethodKind... methodKinds)83 private TypeKind(String declStr, String typeStr, BoundKind boundKind, MethodKind... methodKinds) { 84 this.declStr = declStr; 85 this.typeStr = typeStr; 86 this.boundKind = boundKind; 87 this.methodKinds = methodKinds; 88 } 89 compatibleSupertype(TypeKind tk)90 boolean compatibleSupertype(TypeKind tk) { 91 if (tk == this) return true; 92 switch (tk) { 93 case B: 94 return this != C && this != E && this != F; 95 case C: 96 return this != B && this != C && this != D && this != G; 97 case D: return compatibleSupertype(B); 98 case E: 99 case F: return compatibleSupertype(C); 100 case G: return compatibleSupertype(B); 101 case H: return compatibleSupertype(A); 102 default: 103 return true; 104 } 105 } 106 } 107 108 enum CastKind { 109 ONE_ARY("(#B0)", 1), 110 TWO_ARY("(#B0 & #B1)", 2), 111 THREE_ARY("(#B0 & #B1 & #B2)", 3); 112 113 String castTemplate; 114 int nbounds; 115 CastKind(String castTemplate, int nbounds)116 CastKind(String castTemplate, int nbounds) { 117 this.castTemplate = castTemplate; 118 this.nbounds = nbounds; 119 } 120 } 121 122 enum ExpressionKind { 123 LAMBDA("()->{}", true), 124 MREF("this::m", true), 125 //COND_LAMBDA("(true ? ()->{} : ()->{})", true), re-enable if spec allows this 126 //COND_MREF("(true ? this::m : this::m)", true), 127 STANDALONE("null", false); 128 129 String exprString; 130 boolean isFunctional; 131 ExpressionKind(String exprString, boolean isFunctional)132 private ExpressionKind(String exprString, boolean isFunctional) { 133 this.exprString = exprString; 134 this.isFunctional = isFunctional; 135 } 136 } 137 138 static class CastInfo { 139 CastKind kind; 140 TypeKind[] types; 141 CastInfo(CastKind kind, TypeKind... types)142 CastInfo(CastKind kind, TypeKind... types) { 143 this.kind = kind; 144 this.types = types; 145 } 146 getCast()147 String getCast() { 148 String temp = kind.castTemplate; 149 for (int i = 0; i < kind.nbounds ; i++) { 150 temp = temp.replace(String.format("#B%d", i), types[i].typeStr); 151 } 152 return temp; 153 } 154 wellFormed()155 boolean wellFormed() { 156 //check for duplicate types 157 for (int i = 0 ; i < types.length ; i++) { 158 for (int j = 0 ; j < types.length ; j++) { 159 if (i != j && types[i] == types[j]) { 160 return false; 161 } 162 } 163 } 164 //check that classes only appear as first bound 165 boolean classOk = true; 166 for (int i = 0 ; i < types.length ; i++) { 167 if (types[i].boundKind == BoundKind.CLASS && 168 !classOk) { 169 return false; 170 } 171 classOk = false; 172 } 173 //check that supertypes are mutually compatible 174 for (int i = 0 ; i < types.length ; i++) { 175 for (int j = 0 ; j < types.length ; j++) { 176 if (!types[i].compatibleSupertype(types[j]) && i != j) { 177 return false; 178 } 179 } 180 } 181 return true; 182 } 183 } 184 main(String... args)185 public static void main(String... args) throws Exception { 186 //create default shared JavaCompiler - reused across multiple compilations 187 JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); 188 StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null); 189 190 for (CastInfo cInfo : allCastInfo()) { 191 for (ExpressionKind ek : ExpressionKind.values()) { 192 new IntersectionTargetTypeTest(cInfo, ek).run(comp, fm); 193 } 194 } 195 System.out.println("Total check executed: " + checkCount); 196 } 197 allCastInfo()198 static List<CastInfo> allCastInfo() { 199 ListBuffer<CastInfo> buf = new ListBuffer<>(); 200 for (CastKind kind : CastKind.values()) { 201 for (TypeKind b1 : TypeKind.values()) { 202 if (kind.nbounds == 1) { 203 buf.append(new CastInfo(kind, b1)); 204 continue; 205 } else { 206 for (TypeKind b2 : TypeKind.values()) { 207 if (kind.nbounds == 2) { 208 buf.append(new CastInfo(kind, b1, b2)); 209 continue; 210 } else { 211 for (TypeKind b3 : TypeKind.values()) { 212 buf.append(new CastInfo(kind, b1, b2, b3)); 213 } 214 } 215 } 216 } 217 } 218 } 219 return buf.toList(); 220 } 221 222 CastInfo cInfo; 223 ExpressionKind ek; 224 JavaSource source; 225 DiagnosticChecker diagChecker; 226 IntersectionTargetTypeTest(CastInfo cInfo, ExpressionKind ek)227 IntersectionTargetTypeTest(CastInfo cInfo, ExpressionKind ek) { 228 this.cInfo = cInfo; 229 this.ek = ek; 230 this.source = new JavaSource(); 231 this.diagChecker = new DiagnosticChecker(); 232 } 233 234 class JavaSource extends SimpleJavaFileObject { 235 236 String bodyTemplate = "class Test {\n" + 237 " void m() { }\n" + 238 " void test() {\n" + 239 " Object o = #C#E;\n" + 240 " } }"; 241 242 String source = ""; 243 JavaSource()244 public JavaSource() { 245 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 246 for (TypeKind tk : TypeKind.values()) { 247 source += tk.declStr; 248 } 249 source += bodyTemplate.replaceAll("#C", cInfo.getCast()).replaceAll("#E", ek.exprString); 250 } 251 252 @Override getCharContent(boolean ignoreEncodingErrors)253 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 254 return source; 255 } 256 } 257 run(JavaCompiler tool, StandardJavaFileManager fm)258 void run(JavaCompiler tool, StandardJavaFileManager fm) throws Exception { 259 JavacTask ct = (JavacTask)tool.getTask(null, fm, diagChecker, 260 null, null, Arrays.asList(source)); 261 try { 262 ct.analyze(); 263 } catch (Throwable ex) { 264 throw new AssertionError("Error thrown when compiling the following code:\n" + source.getCharContent(true)); 265 } 266 check(); 267 } 268 check()269 void check() { 270 checkCount++; 271 272 boolean errorExpected = !cInfo.wellFormed(); 273 274 if (ek.isFunctional) { 275 List<MethodKind> mks = new ArrayList<>(); 276 for (TypeKind tk : cInfo.types) { 277 if (tk.boundKind == BoundKind.CLASS) { 278 errorExpected = true; 279 break; 280 } else { 281 mks = mergeMethods(mks, Arrays.asList(tk.methodKinds)); 282 } 283 } 284 int abstractCount = 0; 285 for (MethodKind mk : mks) { 286 if (mk.isAbstract) { 287 abstractCount++; 288 } 289 } 290 errorExpected |= abstractCount != 1; 291 } 292 293 if (errorExpected != diagChecker.errorFound) { 294 throw new Error("invalid diagnostics for source:\n" + 295 source.getCharContent(true) + 296 "\nFound error: " + diagChecker.errorFound + 297 "\nExpected error: " + errorExpected); 298 } 299 } 300 mergeMethods(List<MethodKind> l1, List<MethodKind> l2)301 List<MethodKind> mergeMethods(List<MethodKind> l1, List<MethodKind> l2) { 302 List<MethodKind> mergedMethods = new ArrayList<>(l1); 303 for (MethodKind mk2 : l2) { 304 boolean add = !mergedMethods.contains(mk2); 305 switch (mk2) { 306 case ABSTRACT_G: 307 add = add && !mergedMethods.contains(MethodKind.DEFAULT_G); 308 break; 309 case ABSTRACT_M: 310 add = add && !mergedMethods.contains(MethodKind.DEFAULT_M); 311 break; 312 case DEFAULT_G: 313 mergedMethods.remove(MethodKind.ABSTRACT_G); 314 case DEFAULT_M: 315 mergedMethods.remove(MethodKind.ABSTRACT_M); 316 case NONE: 317 add = false; 318 break; 319 } 320 if (add) { 321 mergedMethods.add(mk2); 322 } 323 } 324 return mergedMethods; 325 } 326 327 static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> { 328 329 boolean errorFound; 330 report(Diagnostic<? extends JavaFileObject> diagnostic)331 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 332 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { 333 errorFound = true; 334 } 335 } 336 } 337 } 338