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