1// © 2016 and later: Unicode, Inc. and others. 2// License & terms of use: http://www.unicode.org/copyright.html#License 3/** 4******************************************************************************* 5* Copyright (C) 2002-2010, International Business Machines Corporation and * 6* others. All Rights Reserved. * 7******************************************************************************* 8*/ 9/** 10 * This is a tool to check the tags on ICU4J files. In particular, we're looking for: 11 * 12 * - methods that have no tags 13 * - custom tags: @draft, @stable, @internal? 14 * - standard tags: @since, @deprecated 15 * 16 * Syntax of tags: 17 * '@draft ICU X.X.X' 18 * '@stable ICU X.X.X' 19 * '@internal' 20 * '@since (don't use)' 21 * '@obsolete ICU X.X.X' 22 * '@deprecated to be removed in ICU X.X. [Use ...]' 23 * 24 * flags names of classes and their members that have no tags or incorrect syntax. 25 * 26 * Requires JDK 1.4 or later 27 * 28 * Use build.xml 'checktags' ant target, or 29 * run from directory containing CheckTags.class as follows: 30 * javadoc -classpath ${JAVA_HOME}/lib/tools.jar -doclet CheckTags -sourcepath ${ICU4J_src} [packagenames] 31 */ 32 33package com.ibm.icu.dev.tool.docs; 34 35import com.sun.javadoc.ClassDoc; 36import com.sun.javadoc.ConstructorDoc; 37import com.sun.javadoc.ExecutableMemberDoc; 38import com.sun.javadoc.ProgramElementDoc; 39import com.sun.javadoc.RootDoc; 40import com.sun.javadoc.Tag; 41 42public class CheckTags { 43 RootDoc root; 44 boolean log; 45 boolean brief; 46 boolean isShort; 47 DocStack stack = new DocStack(); 48 49 class DocNode { 50 private String header; 51 private boolean printed; 52 private boolean reportError; 53 private int errorCount; 54 55 public void reset(String header, boolean reportError) { 56 this.header = header; 57 this.printed = false; 58 this.errorCount = 0; 59 this.reportError = reportError; 60 } 61 public String toString() { 62 return header + 63 " printed: " + printed + 64 " reportError: " + reportError + 65 " errorCount: " + errorCount; 66 } 67 } 68 69 class DocStack { 70 private DocNode[] stack; 71 private int index; 72 private boolean newline; 73 74 public void push(String header, boolean reportError) { 75 if (stack == null) { 76 stack = new DocNode[5]; 77 } else { 78 if (index == stack.length) { 79 DocNode[] temp = new DocNode[stack.length * 2]; 80 System.arraycopy(stack, 0, temp, 0, index); 81 stack = temp; 82 } 83 } 84 if (stack[index] == null) { 85 stack[index] = new DocNode(); 86 } 87 // System.out.println("reset [" + index + "] header: " + header + " report: " + reportError); 88 stack[index++].reset(header, reportError); 89 } 90 91 public void pop() { 92 if (index == 0) { 93 throw new IndexOutOfBoundsException(); 94 } 95 --index; 96 97 int ec = stack[index].errorCount; // index already decremented 98 if (ec > 0 || index == 0) { // always report for outermost element 99 if (stack[index].reportError) { 100 output("(" + ec + (ec == 1 ? " error" : " errors") + ")", false, true, index); 101 } 102 103 // propagate to parent 104 if (index > 0) { 105 stack[index-1].errorCount += ec; 106 } 107 } 108 if (index == 0) { 109 System.out.println(); // always since we always report number of errors 110 } 111 } 112 113 public void output(String msg, boolean error, boolean newline) { 114 output(msg, error, newline, index-1); 115 } 116 117 void output(String msg, boolean error, boolean newline, int ix) { 118 DocNode last = stack[ix]; 119 if (error) { 120 last.errorCount += 1; 121 } 122 123 boolean show = !brief || last.reportError; 124 // boolean nomsg = show && brief && error; 125 // System.out.println(">>> " + last + " error: " + error + " show: " + show + " nomsg: " + nomsg); 126 127 if (show) { 128 if (isShort || (brief && error)) { 129 msg = null; // nuke error messages if we're brief, just report headers and totals 130 } 131 for (int i = 0; i <= ix;) { 132 DocNode n = stack[i]; 133 if (n.printed) { 134 if (msg != null || !last.printed) { // since index > 0 last is not null 135 if (this.newline && i == 0) { 136 System.out.println(); 137 this.newline = false; 138 } 139 System.out.print(" "); 140 } 141 ++i; 142 } else { 143 System.out.print(n.header); 144 n.printed = true; 145 this.newline = true; 146 i = 0; 147 } 148 } 149 150 if (msg != null) { 151 if (index == 0 && this.newline) { 152 System.out.println(); 153 } 154 if (error) { 155 System.out.print("*** "); 156 } 157 System.out.print(msg); 158 } 159 } 160 161 this.newline = newline; 162 } 163 } 164 165 public static boolean start(RootDoc root) { 166 return new CheckTags(root).run(); 167 } 168 169 public static int optionLength(String option) { 170 if (option.equals("-log")) { 171 return 1; 172 } else if (option.equals("-brief")) { 173 return 1; 174 } else if (option.equals("-short")) { 175 return 1; 176 } 177 return 0; 178 } 179 180 CheckTags(RootDoc root) { 181 this.root = root; 182 183 String[][] options = root.options(); 184 for (int i = 0; i < options.length; ++i) { 185 String opt = options[i][0]; 186 if (opt.equals("-log")) { 187 this.log = true; 188 } else if (opt.equals("-brief")) { 189 this.brief = true; 190 } else if (opt.equals("-short")) { 191 this.isShort = true; 192 } 193 } 194 } 195 196 boolean run() { 197 doDocs(root.classes(), "Package", true); 198 return false; 199 } 200 201 static final String[] tagKinds = { 202 "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", "@version", 203 "@param", "@return", "@throws", "@obsolete", "@exception", "@serial", "@provisional" 204 }; 205 206 static final int UNKNOWN = -1; 207 static final int INTERNAL = 0; 208 static final int DRAFT = 1; 209 static final int STABLE = 2; 210 static final int SINCE = 3; 211 static final int DEPRECATED = 4; 212 static final int AUTHOR = 5; 213 static final int SEE = 6; 214 static final int VERSION = 7; 215 static final int PARAM = 8; 216 static final int RETURN = 9; 217 static final int THROWS = 10; 218 static final int OBSOLETE = 11; 219 static final int EXCEPTION = 12; 220 static final int SERIAL = 13; 221 static final int PROVISIONAL = 14; 222 223 static int tagKindIndex(String kind) { 224 for (int i = 0; i < tagKinds.length; ++i) { 225 if (kind.equals(tagKinds[i])) { 226 return i; 227 } 228 } 229 return UNKNOWN; 230 } 231 232 static final String[] icuTagNames = { 233 "@icu", "@icunote", "@icuenhanced" 234 }; 235 static final int ICU = 0; 236 static final int ICUNOTE = 1; 237 static final int ICUENHANCED = 2; 238 static int icuTagIndex(String name) { 239 for (int i = 0; i < icuTagNames.length; ++i) { 240 if (icuTagNames[i].equals(name)) { 241 return i; 242 } 243 } 244 return UNKNOWN; 245 } 246 247 boolean newline = false; 248 249 void output(String msg, boolean error, boolean newline) { 250 stack.output(msg, error, newline); 251 } 252 253 void log() { 254 output(null, false, false); 255 } 256 257 void logln() { 258 output(null, false, true); 259 } 260 261 void log(String msg) { 262 output(msg, false, false); 263 } 264 265 void logln(String msg) { 266 output(msg, false, true); 267 } 268 269 void err(String msg) { 270 output(msg, true, false); 271 } 272 273 void errln(String msg) { 274 output(msg, true, true); 275 } 276 277 void tagErr(String msg, Tag tag) { 278 // Tag.position() requires JDK 1.4, build.xml tests for this 279 if (msg.length() > 0) { 280 msg += ": "; 281 } 282 errln(msg + tag.toString() + " [" + tag.position() + "]"); 283 }; 284 285 void tagErr(Tag tag) { 286 tagErr("", tag); 287 } 288 289 void doDocs(ProgramElementDoc[] docs, String header, boolean reportError) { 290 if (docs != null && docs.length > 0) { 291 stack.push(header, reportError); 292 for (int i = 0; i < docs.length; ++i) { 293 doDoc(docs[i]); 294 } 295 stack.pop(); 296 } 297 } 298 299 void doDoc(ProgramElementDoc doc) { 300 if (doc != null && (doc.isPublic() || doc.isProtected()) 301 && !(doc instanceof ConstructorDoc && ((ConstructorDoc)doc).isSynthetic())) { 302 303 // unfortunately, in JDK 1.4.1 MemberDoc.isSynthetic is not properly implemented for 304 // synthetic constructors. So you'll have to live with spurious errors or 'implement' 305 // the synthetic constructors... 306 307 boolean isClass = doc.isClass() || doc.isInterface(); 308 String header; 309 if (!isShort || isClass) { 310 header = "--- "; 311 } else { 312 header = ""; 313 } 314 header += (isClass ? doc.qualifiedName() : doc.name()); 315 if (doc instanceof ExecutableMemberDoc) { 316 header += ((ExecutableMemberDoc)doc).flatSignature(); 317 } 318 if (!isShort || isClass) { 319 header += " ---"; 320 } 321 stack.push(header, isClass); 322 if (log) { 323 logln(); 324 } 325 boolean recurse = doTags(doc); 326 if (recurse && isClass) { 327 ClassDoc cdoc = (ClassDoc)doc; 328 doDocs(cdoc.fields(), "Fields", !brief); 329 doDocs(cdoc.constructors(), "Constructors", !brief); 330 doDocs(cdoc.methods(), "Methods", !brief); 331 } 332 stack.pop(); 333 } 334 } 335 336 /** Return true if subelements of this doc should be checked */ 337 boolean doTags(ProgramElementDoc doc) { 338 boolean foundRequiredTag = false; 339 boolean foundDraftTag = false; 340 boolean foundProvisionalTag = false; 341 boolean foundDeprecatedTag = false; 342 boolean foundObsoleteTag = false; 343 boolean foundInternalTag = false; 344 boolean foundStableTag = false; 345 boolean retainAll = false; 346 347 // first check inline tags 348 for (Tag tag : doc.inlineTags()) { 349 int index = icuTagIndex(tag.name()); 350 if (index >= 0) { 351 String text = tag.text().trim(); 352 switch (index) { 353 case ICU: { 354 if (doc.isClass() || doc.isInterface()) { 355 tagErr("tag should appear only in member docs", tag); 356 } 357 } break; 358 case ICUNOTE: { 359 if (text.length() > 0) { 360 tagErr("tag should not contain text", tag); 361 } 362 } break; 363 case ICUENHANCED: { 364 if (text.length() == 0) { 365 tagErr("text should name related jdk class", tag); 366 } 367 if (!(doc.isClass() || doc.isInterface())) { 368 tagErr("tag should appear only in class/interface docs", tag); 369 } 370 } break; 371 default: 372 tagErr("unrecognized tag index for tag", tag); 373 break; 374 } 375 } 376 } 377 378 // next check regular tags 379 for (Tag tag : doc.tags()) { 380 String kind = tag.kind(); 381 int ix = tagKindIndex(kind); 382 383 switch (ix) { 384 case UNKNOWN: 385 errln("unknown kind: " + kind); 386 break; 387 388 case INTERNAL: 389 foundRequiredTag = true; 390 foundInternalTag = true; 391 break; 392 393 case DRAFT: 394 foundRequiredTag = true; 395 foundDraftTag = true; 396 if (tag.text().indexOf("ICU 2.8") != -1 && 397 tag.text().indexOf("(retain") == -1) { // catch both retain and retainAll 398 tagErr(tag); 399 break; 400 } 401 if (tag.text().indexOf("ICU") != 0) { 402 tagErr(tag); 403 break; 404 } 405 retainAll |= (tag.text().indexOf("(retainAll)") != -1); 406 break; 407 408 case PROVISIONAL: 409 foundProvisionalTag = true; 410 break; 411 412 case DEPRECATED: 413 foundDeprecatedTag = true; 414 if (tag.text().indexOf("ICU") == 0) { 415 foundRequiredTag = true; 416 } 417 break; 418 419 case OBSOLETE: 420 if (tag.text().indexOf("ICU") != 0) { 421 tagErr(tag); 422 } 423 foundObsoleteTag = true; 424 foundRequiredTag = true; 425 break; 426 427 case STABLE: 428 { 429 String text = tag.text(); 430 if (text.length() != 0 && text.indexOf("ICU") != 0) { 431 tagErr(tag); 432 } 433 foundRequiredTag = true; 434 foundStableTag = true; 435 } 436 break; 437 438 case SINCE: 439 tagErr(tag); 440 break; 441 442 case EXCEPTION: 443 logln("You really ought to use @throws, you know... :-)"); 444 445 case AUTHOR: 446 case SEE: 447 case PARAM: 448 case RETURN: 449 case THROWS: 450 case SERIAL: 451 break; 452 453 case VERSION: 454 tagErr(tag); 455 break; 456 457 default: 458 errln("unknown index: " + ix); 459 } 460 } 461 if (!foundRequiredTag) { 462 errln("missing required tag [" + doc.position() + "]"); 463 } 464 if (foundInternalTag && !foundDeprecatedTag) { 465 errln("internal tag missing deprecated"); 466 } 467 if (foundDraftTag && !(foundDeprecatedTag || foundProvisionalTag)) { 468 errln("draft tag missing deprecated or provisional"); 469 } 470 if (foundObsoleteTag && !foundDeprecatedTag) { 471 errln("obsolete tag missing deprecated"); 472 } 473 if (foundStableTag && foundDeprecatedTag) { 474 logln("stable deprecated"); 475 } 476 477 return !retainAll; 478 } 479} 480