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