1 /*
   2  * Copyright (c) 2019, 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  * @summary checking type annotations on records
  27  * @library /tools/javac/lib
  28  * @modules java.compiler
  29  *          jdk.compiler
  30  * @modules jdk.compiler/com.sun.tools.javac.code
  31  *          jdk.compiler/com.sun.tools.javac.util
  32  * @build JavacTestingAbstractProcessor CheckingTypeAnnotationsOnRecords
  33  * @compile -XDaccessInternalAPI -processor CheckingTypeAnnotationsOnRecords -proc:only CheckingTypeAnnotationsOnRecords.java
  34  */
  35 
  36 import java.lang.annotation.*;
  37 import java.util.*;
  38 import javax.annotation.processing.*;
  39 import javax.lang.model.element.*;
  40 import javax.lang.model.type.*;
  41 import javax.tools.Diagnostic.Kind;
  42 
  43 import com.sun.tools.javac.util.Assert;
  44 
  45 @SupportedAnnotationTypes("*")
  46 public class CheckingTypeAnnotationsOnRecords extends JavacTestingAbstractProcessor {
  47 
  48     static private final Map<String, String> recordNameExpectedAnnotationMap = new HashMap<>();
  49     static private final Map<String, Integer> recordNameExpectedAnnotationNumberMap = new HashMap<>();
  50     static {
  51         recordNameExpectedAnnotationMap.put("CheckingTypeAnnotationsOnRecords.R1", "@CheckingTypeAnnotationsOnRecords.TypeUse");
  52         recordNameExpectedAnnotationMap.put("CheckingTypeAnnotationsOnRecords.R2", "@CheckingTypeAnnotationsOnRecords.TypeParameter");
  53 
  54         recordNameExpectedAnnotationNumberMap.put("CheckingTypeAnnotationsOnRecords.R1", 3);
  55         recordNameExpectedAnnotationNumberMap.put("CheckingTypeAnnotationsOnRecords.R2", 1);
  56     }
  57 
  58     @Retention(RetentionPolicy.RUNTIME)
  59     @Target({ ElementType.TYPE_USE })
  60     @interface TypeUse {}
  61 
  62     record R1(@TypeUse int annotated) {}
  63 
  64     @Retention(RetentionPolicy.RUNTIME)
  65     @Target({ ElementType.TYPE_PARAMETER })
  66     @interface TypeParameter {}
  67 
  68     record R2<@TypeParameter T>(T t) {}
  69 
  70     @Override
  71     public boolean process(Set<? extends TypeElement> annotations,
  72                            RoundEnvironment roundEnv) {
  73         if (roundEnv.processingOver()) {
  74             for (String key : recordNameExpectedAnnotationMap.keySet()) {
  75                 Element element = processingEnv.getElementUtils().getTypeElement(key);
  76                 numberOfAnnotations = 0;
  77                 verifyReferredTypesAcceptable(element, recordNameExpectedAnnotationMap.get(key));
  78                 Assert.check(numberOfAnnotations == recordNameExpectedAnnotationNumberMap.get(key));
  79             }
  80         }
  81         return true;
  82     }
  83 
  84     int numberOfAnnotations = 0;
  85 
  86     private void verifyReferredTypesAcceptable(Element rootElement, String expectedAnnotationName) {
  87         new ElementScanner<Void, Void>() {
  88             @Override public Void visitType(TypeElement e, Void p) {
  89                 scan(e.getTypeParameters(), p);
  90                 scan(e.getEnclosedElements(), p);
  91                 verifyAnnotations(e.getAnnotationMirrors(), expectedAnnotationName);
  92                 return null;
  93             }
  94             @Override public Void visitTypeParameter(TypeParameterElement e, Void p) {
  95                 verifyTypesAcceptable(e.getBounds(), expectedAnnotationName);
  96                 scan(e.getEnclosedElements(), p);
  97                 verifyAnnotations(e.getAnnotationMirrors(), expectedAnnotationName);
  98                 return null;
  99             }
 100             @Override public Void visitVariable(VariableElement e, Void p) {
 101                 verifyTypeAcceptable(e.asType(), expectedAnnotationName);
 102                 scan(e.getEnclosedElements(), p);
 103                 verifyAnnotations(e.getAnnotationMirrors(), expectedAnnotationName);
 104                 return null;
 105             }
 106             @Override
 107             public Void visitExecutable(ExecutableElement e, Void p) {
 108                 scan(e.getTypeParameters(), p);
 109                 verifyTypeAcceptable(e.getReturnType(), expectedAnnotationName);
 110                 scan(e.getParameters(), p);
 111                 verifyTypesAcceptable(e.getThrownTypes(), expectedAnnotationName);
 112                 scan(e.getEnclosedElements(), p);
 113                 verifyAnnotations(e.getAnnotationMirrors(), expectedAnnotationName);
 114                 return null;
 115             }
 116         }.scan(rootElement, null);
 117     }
 118 
 119     private void verifyAnnotations(Iterable<? extends AnnotationMirror> annotations, String expectedName) {
 120         for (AnnotationMirror mirror : annotations) {
 121             Assert.check(mirror.toString().equals(expectedName));
 122             numberOfAnnotations++;
 123         }
 124     }
 125 
 126     private void verifyTypesAcceptable(Iterable<? extends TypeMirror> types, String expectedAnnotationName) {
 127         if (types == null) return ;
 128 
 129         for (TypeMirror type : types) {
 130             verifyTypeAcceptable(type, expectedAnnotationName);
 131         }
 132     }
 133 
 134 
 135     private void verifyTypeAcceptable(TypeMirror type, String expectedAnnotationName) {
 136         if (type == null) return ;
 137 
 138         verifyAnnotations(type.getAnnotationMirrors(), expectedAnnotationName);
 139 
 140         switch (type.getKind()) {
 141             case BOOLEAN: case BYTE: case CHAR: case DOUBLE: case FLOAT:
 142             case INT: case LONG: case SHORT: case VOID: case NONE: case NULL:
 143                 return ;
 144             case DECLARED:
 145                 DeclaredType dt = (DeclaredType) type;
 146                 TypeElement outermostTypeElement = outermostTypeElement(dt.asElement());
 147                 String outermostType = outermostTypeElement.getQualifiedName().toString();
 148 
 149                 for (TypeMirror bound : dt.getTypeArguments()) {
 150                     verifyTypeAcceptable(bound, expectedAnnotationName);
 151                 }
 152                 break;
 153             case ARRAY:
 154                 verifyTypeAcceptable(((ArrayType) type).getComponentType(), expectedAnnotationName);
 155                 break;
 156             case INTERSECTION:
 157                 for (TypeMirror element : ((IntersectionType) type).getBounds()) {
 158                     verifyTypeAcceptable(element, expectedAnnotationName);
 159                 }
 160                 break;
 161             case TYPEVAR:
 162                 verifyTypeAcceptable(((TypeVariable) type).getLowerBound(), expectedAnnotationName);
 163                 verifyTypeAcceptable(((TypeVariable) type).getUpperBound(), expectedAnnotationName);
 164                 break;
 165             case WILDCARD:
 166                 verifyTypeAcceptable(((WildcardType) type).getExtendsBound(), expectedAnnotationName);
 167                 verifyTypeAcceptable(((WildcardType) type).getSuperBound(), expectedAnnotationName);
 168                 break;
 169             default:
 170                 error("Type not acceptable for this API: " + type.toString());
 171                 break;
 172 
 173         }
 174     }
 175 
 176 
 177     private TypeElement outermostTypeElement(Element el) {
 178         while (el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
 179             el = el.getEnclosingElement();
 180         }
 181 
 182         return (TypeElement) el;
 183     }
 184 
 185     private void error(String text) {
 186         processingEnv.getMessager().printMessage(Kind.ERROR, text);
 187     }
 188 }