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