Fri, 24 Apr 2020 03:58:51 +0100
Merge
jjg@488 | 1 | /* |
jjg@1521 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jjg@488 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jjg@488 | 4 | * |
jjg@488 | 5 | * This code is free software; you can redistribute it and/or modify it |
jjg@488 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jjg@488 | 7 | * published by the Free Software Foundation. |
jjg@488 | 8 | * |
jjg@488 | 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jjg@488 | 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jjg@488 | 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jjg@488 | 12 | * version 2 for more details (a copy is included in the LICENSE file that |
jjg@488 | 13 | * accompanied this code). |
jjg@488 | 14 | * |
jjg@488 | 15 | * You should have received a copy of the GNU General Public License version |
jjg@488 | 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
jjg@488 | 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jjg@488 | 18 | * |
ohair@554 | 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
ohair@554 | 20 | * or visit www.oracle.com if you need additional information or have any |
ohair@554 | 21 | * questions. |
jjg@488 | 22 | */ |
jjg@488 | 23 | |
jjg@488 | 24 | import java.io.*; |
jjg@488 | 25 | import java.util.*; |
jjg@488 | 26 | import javax.annotation.processing.*; |
jjg@488 | 27 | import javax.lang.model.*; |
jjg@488 | 28 | import javax.lang.model.element.*; |
jjg@488 | 29 | import javax.lang.model.util.*; |
jjg@488 | 30 | import javax.tools.*; |
jjg@488 | 31 | |
jjg@488 | 32 | import com.sun.source.util.*; |
jjg@488 | 33 | import com.sun.tools.javac.code.BoundKind; |
jjg@488 | 34 | import com.sun.tools.javac.tree.JCTree.*; |
jjg@488 | 35 | import com.sun.tools.javac.tree.TreeScanner; |
jjg@488 | 36 | import com.sun.tools.javac.tree.*; |
jjg@488 | 37 | import com.sun.tools.javac.util.List; |
jjg@488 | 38 | |
jjg@488 | 39 | /** |
jjg@488 | 40 | * Test processor used to check test programs using the @Test, @DA, and @TA |
jjg@488 | 41 | * annotations. |
jjg@488 | 42 | * |
jjg@488 | 43 | * The processor looks for elements annotated with @Test, and analyzes the |
jjg@488 | 44 | * syntax trees for those elements. Within such trees, the processor looks |
jjg@488 | 45 | * for the DA annotations on decls and TA annotations on types. |
jjg@488 | 46 | * The value of these annotations should be a simple string rendition of |
jjg@488 | 47 | * the tree node to which it is attached. |
jjg@488 | 48 | * The expected number of annotations is given by the parameter to the |
jjg@488 | 49 | * @Test annotation itself. |
jjg@488 | 50 | */ |
jjg@488 | 51 | @SupportedAnnotationTypes({"Test"}) |
jjg@488 | 52 | public class TestProcessor extends AbstractProcessor { |
jjg@488 | 53 | public SourceVersion getSupportedSourceVersion() { |
jjg@488 | 54 | return SourceVersion.latest(); |
jjg@488 | 55 | } |
jjg@488 | 56 | |
jjg@488 | 57 | /** Process trees for elements annotated with the @Test(n) annotation. */ |
jjg@488 | 58 | public boolean process(Set<? extends TypeElement> annos, RoundEnvironment renv) { |
jjg@488 | 59 | if (renv.processingOver()) |
jjg@488 | 60 | return true; |
jjg@488 | 61 | |
jjg@488 | 62 | Elements elements = processingEnv.getElementUtils(); |
jjg@488 | 63 | Trees trees = Trees.instance(processingEnv); |
jjg@488 | 64 | |
jjg@488 | 65 | TypeElement testAnno = elements.getTypeElement("Test"); |
jjg@488 | 66 | for (Element elem: renv.getElementsAnnotatedWith(testAnno)) { |
jjg@488 | 67 | System.err.println("ELEM: " + elem); |
jjg@488 | 68 | int count = getValue(getAnnoMirror(elem, testAnno), Integer.class); |
jjg@488 | 69 | System.err.println("count: " + count); |
jjg@488 | 70 | TreePath p = trees.getPath(elem); |
jjg@488 | 71 | JavaFileObject file = p.getCompilationUnit().getSourceFile(); |
jjg@488 | 72 | JCTree tree = (JCTree) p.getLeaf(); |
jjg@488 | 73 | System.err.println("tree: " + tree); |
jjg@488 | 74 | new TestScanner(file).check(tree, count); |
jjg@488 | 75 | } |
jjg@488 | 76 | return true; |
jjg@488 | 77 | } |
jjg@488 | 78 | |
jjg@488 | 79 | /** Get the AnnotationMirror on an element for a given annotation. */ |
jjg@488 | 80 | AnnotationMirror getAnnoMirror(Element e, TypeElement anno) { |
jjg@488 | 81 | Types types = processingEnv.getTypeUtils(); |
jjg@488 | 82 | for (AnnotationMirror m: e.getAnnotationMirrors()) { |
jjg@488 | 83 | if (types.isSameType(m.getAnnotationType(), anno.asType())) |
jjg@488 | 84 | return m; |
jjg@488 | 85 | } |
jjg@488 | 86 | return null; |
jjg@488 | 87 | } |
jjg@488 | 88 | |
jjg@488 | 89 | /** Get the value of the value element of an annotation mirror. */ |
jjg@488 | 90 | <T> T getValue(AnnotationMirror m, Class<T> type) { |
jjg@488 | 91 | for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> e: m.getElementValues().entrySet()) { |
jjg@488 | 92 | ExecutableElement ee = e.getKey(); |
jjg@488 | 93 | if (ee.getSimpleName().contentEquals("value")) { |
jjg@488 | 94 | AnnotationValue av = e.getValue(); |
jjg@488 | 95 | return type.cast(av.getValue()); |
jjg@488 | 96 | } |
jjg@488 | 97 | } |
jjg@488 | 98 | return null; |
jjg@488 | 99 | } |
jjg@488 | 100 | |
jjg@488 | 101 | /** Report an error to the annotation processing system. */ |
jjg@488 | 102 | void error(String msg) { |
jjg@488 | 103 | Messager messager = processingEnv.getMessager(); |
jjg@488 | 104 | messager.printMessage(Diagnostic.Kind.ERROR, msg); |
jjg@488 | 105 | } |
jjg@488 | 106 | |
jjg@488 | 107 | /** Report an error to the annotation processing system. */ |
jjg@488 | 108 | void error(JavaFileObject file, JCTree tree, String msg) { |
jjg@488 | 109 | // need better API for reporting tree position errors to the messager |
jjg@488 | 110 | Messager messager = processingEnv.getMessager(); |
jjg@488 | 111 | String text = file.getName() + ":" + getLine(file, tree) + ": " + msg; |
jjg@488 | 112 | messager.printMessage(Diagnostic.Kind.ERROR, text); |
jjg@488 | 113 | } |
jjg@488 | 114 | |
jjg@488 | 115 | /** Get the line number for the primary position for a tree. |
jjg@488 | 116 | * The code is intended to be simple, although not necessarily efficient. |
jjg@488 | 117 | * However, note that a file manager such as JavacFileManager is likely |
jjg@488 | 118 | * to cache the results of file.getCharContent, avoiding the need to read |
jjg@488 | 119 | * the bits from disk each time this method is called. |
jjg@488 | 120 | */ |
jjg@488 | 121 | int getLine(JavaFileObject file, JCTree tree) { |
jjg@488 | 122 | try { |
jjg@488 | 123 | CharSequence cs = file.getCharContent(true); |
jjg@488 | 124 | int line = 1; |
jjg@488 | 125 | for (int i = 0; i < tree.pos; i++) { |
jjg@488 | 126 | if (cs.charAt(i) == '\n') // jtreg tests always use Unix line endings |
jjg@488 | 127 | line++; |
jjg@488 | 128 | } |
jjg@488 | 129 | return line; |
jjg@488 | 130 | } catch (IOException e) { |
jjg@488 | 131 | return -1; |
jjg@488 | 132 | } |
jjg@488 | 133 | } |
jjg@488 | 134 | |
jjg@488 | 135 | /** Scan a tree, looking for @DA and @TA annotations, and verifying that such |
jjg@488 | 136 | * annotations are attached to the expected tree node matching the string |
jjg@488 | 137 | * parameter of the annotation. |
jjg@488 | 138 | */ |
jjg@488 | 139 | class TestScanner extends TreeScanner { |
jjg@488 | 140 | /** Create a scanner for a given file. */ |
jjg@488 | 141 | TestScanner(JavaFileObject file) { |
jjg@488 | 142 | this.file = file; |
jjg@488 | 143 | } |
jjg@488 | 144 | |
jjg@488 | 145 | /** Check the annotations in a given tree. */ |
jjg@488 | 146 | void check(JCTree tree, int expectCount) { |
jjg@488 | 147 | foundCount = 0; |
jjg@488 | 148 | scan(tree); |
jjg@488 | 149 | if (foundCount != expectCount) |
jjg@488 | 150 | error(file, tree, "Wrong number of annotations found: " + foundCount + ", expected: " + expectCount); |
jjg@488 | 151 | } |
jjg@488 | 152 | |
jjg@488 | 153 | /** Check @DA annotations on a class declaration. */ |
jjg@488 | 154 | @Override |
jjg@488 | 155 | public void visitClassDef(JCClassDecl tree) { |
jjg@488 | 156 | super.visitClassDef(tree); |
jjg@488 | 157 | check(tree.mods.annotations, "DA", tree); |
jjg@488 | 158 | } |
jjg@488 | 159 | |
jjg@488 | 160 | /** Check @DA annotations on a method declaration. */ |
jjg@488 | 161 | @Override |
jjg@488 | 162 | public void visitMethodDef(JCMethodDecl tree) { |
jjg@488 | 163 | super.visitMethodDef(tree); |
jjg@488 | 164 | check(tree.mods.annotations, "DA", tree); |
jjg@488 | 165 | } |
jjg@488 | 166 | |
jjg@488 | 167 | /** Check @DA annotations on a field, parameter or local variable declaration. */ |
jjg@488 | 168 | @Override |
jjg@488 | 169 | public void visitVarDef(JCVariableDecl tree) { |
jjg@488 | 170 | super.visitVarDef(tree); |
jjg@488 | 171 | check(tree.mods.annotations, "DA", tree); |
jjg@488 | 172 | } |
jjg@488 | 173 | |
jjg@488 | 174 | /** Check @TA annotations on a type. */ |
jjg@488 | 175 | public void visitAnnotatedType(JCAnnotatedType tree) { |
jjg@488 | 176 | super.visitAnnotatedType(tree); |
jjg@488 | 177 | check(tree.annotations, "TA", tree); |
jjg@488 | 178 | } |
jjg@488 | 179 | |
jjg@488 | 180 | /** Check to see if a list of annotations contains a named annotation, and |
jjg@488 | 181 | * if so, verify the annotation is expected by comparing the value of the |
jjg@488 | 182 | * annotation's argument against the string rendition of the reference tree |
jjg@488 | 183 | * node. |
jjg@488 | 184 | * @param annos the list of annotations to be checked |
jjg@488 | 185 | * @param name the name of the annotation to be checked |
jjg@488 | 186 | * @param tree the tree against which to compare the annotations's argument |
jjg@488 | 187 | */ |
jjg@488 | 188 | void check(List<? extends JCAnnotation> annos, String name, JCTree tree) { |
jjg@488 | 189 | for (List<? extends JCAnnotation> l = annos; l.nonEmpty(); l = l.tail) { |
jjg@488 | 190 | JCAnnotation anno = l.head; |
jjg@488 | 191 | if (anno.annotationType.toString().equals(name) && (anno.args.size() == 1)) { |
jjg@488 | 192 | String expect = getStringValue(anno.args.head); |
jjg@488 | 193 | foundCount++; |
jjg@488 | 194 | System.err.println("found: " + name + " " + expect); |
jjg@488 | 195 | String found = new TypePrinter().print(tree); |
jjg@488 | 196 | if (!found.equals(expect)) |
jjg@488 | 197 | error(file, anno, "Unexpected result: expected: \"" + expect + "\", found: \"" + found + "\""); |
jjg@488 | 198 | } |
jjg@488 | 199 | } |
jjg@488 | 200 | } |
jjg@488 | 201 | |
jjg@488 | 202 | /** Get the string value of an annotation argument, which is given by the |
jjg@488 | 203 | * expression <i>name</i>=<i>value</i>. |
jjg@488 | 204 | */ |
jjg@488 | 205 | String getStringValue(JCExpression e) { |
jjg@1521 | 206 | if (e.hasTag(JCTree.Tag.ASSIGN)) { |
jjg@488 | 207 | JCAssign a = (JCAssign) e; |
jjg@488 | 208 | JCExpression rhs = a.rhs; |
jjg@1521 | 209 | if (rhs.hasTag(JCTree.Tag.LITERAL)) { |
jjg@488 | 210 | JCLiteral l = (JCLiteral) rhs; |
jjg@488 | 211 | return (String) l.value; |
jjg@488 | 212 | } |
jjg@1521 | 213 | } else if (e.hasTag(JCTree.Tag.LITERAL)) { |
jjg@1521 | 214 | JCLiteral l = (JCLiteral) e; |
jjg@1521 | 215 | return (String) l.value; |
jjg@488 | 216 | } |
jjg@488 | 217 | throw new IllegalArgumentException(e.toString()); |
jjg@488 | 218 | } |
jjg@488 | 219 | |
jjg@488 | 220 | /** The file for the tree. Used to locate errors. */ |
jjg@488 | 221 | JavaFileObject file; |
jjg@488 | 222 | /** The number of annotations that have been found. @see #check */ |
jjg@488 | 223 | int foundCount; |
jjg@488 | 224 | } |
jjg@488 | 225 | |
jjg@488 | 226 | /** Convert a type or decl tree to a reference string used by the @DA and @TA annotations. */ |
jjg@488 | 227 | class TypePrinter extends Visitor { |
jjg@488 | 228 | /** Convert a type or decl tree to a string. */ |
jjg@488 | 229 | String print(JCTree tree) { |
jjg@488 | 230 | if (tree == null) |
jjg@488 | 231 | return null; |
jjg@488 | 232 | tree.accept(this); |
jjg@488 | 233 | return result; |
jjg@488 | 234 | } |
jjg@488 | 235 | |
jjg@488 | 236 | String print(List<? extends JCTree> list) { |
jjg@488 | 237 | return print(list, ", "); |
jjg@488 | 238 | } |
jjg@488 | 239 | |
jjg@488 | 240 | String print(List<? extends JCTree> list, String sep) { |
jjg@488 | 241 | StringBuilder sb = new StringBuilder(); |
jjg@488 | 242 | if (list.nonEmpty()) { |
jjg@488 | 243 | sb.append(print(list.head)); |
jjg@488 | 244 | for (List<? extends JCTree> l = list.tail; l.nonEmpty(); l = l.tail) { |
jjg@488 | 245 | sb.append(sep); |
jjg@488 | 246 | sb.append(print(l.head)); |
jjg@488 | 247 | } |
jjg@488 | 248 | } |
jjg@488 | 249 | return sb.toString(); |
jjg@488 | 250 | } |
jjg@488 | 251 | |
jjg@488 | 252 | @Override |
jjg@488 | 253 | public void visitClassDef(JCClassDecl tree) { |
jjg@488 | 254 | result = tree.name.toString(); |
jjg@488 | 255 | } |
jjg@488 | 256 | |
jjg@488 | 257 | @Override |
jjg@488 | 258 | public void visitMethodDef(JCMethodDecl tree) { |
jjg@488 | 259 | result = tree.name.toString(); |
jjg@488 | 260 | } |
jjg@488 | 261 | |
jjg@488 | 262 | @Override |
jjg@488 | 263 | public void visitVarDef(JCVariableDecl tree) { |
jjg@488 | 264 | tree.vartype.accept(this); |
jjg@488 | 265 | } |
jjg@488 | 266 | |
jjg@488 | 267 | @Override |
jjg@488 | 268 | public void visitAnnotatedType(JCAnnotatedType tree) { |
jjg@488 | 269 | tree.underlyingType.accept(this); |
jjg@488 | 270 | } |
jjg@488 | 271 | |
jjg@488 | 272 | @Override |
jjg@488 | 273 | public void visitTypeIdent(JCPrimitiveTypeTree tree) { |
jjg@488 | 274 | result = tree.toString(); |
jjg@488 | 275 | } |
jjg@488 | 276 | |
jjg@488 | 277 | @Override |
jjg@488 | 278 | public void visitTypeArray(JCArrayTypeTree tree) { |
jjg@488 | 279 | result = print(tree.elemtype) + "[]"; |
jjg@488 | 280 | } |
jjg@488 | 281 | |
jjg@488 | 282 | @Override |
jjg@488 | 283 | public void visitTypeApply(JCTypeApply tree) { |
jjg@488 | 284 | result = print(tree.clazz) + "<" + print(tree.arguments) + ">"; |
jjg@488 | 285 | } |
jjg@488 | 286 | |
jjg@488 | 287 | @Override |
jjg@488 | 288 | public void visitTypeParameter(JCTypeParameter tree) { |
jjg@488 | 289 | if (tree.bounds.isEmpty()) |
jjg@488 | 290 | result = tree.name.toString(); |
jjg@488 | 291 | else |
jjg@488 | 292 | result = tree.name + " extends " + print(tree.bounds, "&"); |
jjg@488 | 293 | } |
jjg@488 | 294 | |
jjg@488 | 295 | @Override |
jjg@488 | 296 | public void visitWildcard(JCWildcard tree) { |
jjg@488 | 297 | if (tree.kind.kind == BoundKind.UNBOUND) |
jjg@488 | 298 | result = tree.kind.toString(); |
jjg@488 | 299 | else |
jjg@488 | 300 | result = tree.kind + " " + print(tree.inner); |
jjg@488 | 301 | } |
jjg@488 | 302 | |
jjg@488 | 303 | private String result; |
jjg@488 | 304 | } |
jjg@488 | 305 | } |