KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > incava > doctorj > ExceptionDocAnalyzer


1 package org.incava.doctorj;
2
3 import java.util.*;
4 import net.sourceforge.pmd.ast.*;
5 import org.incava.analysis.Report;
6 import org.incava.java.*;
7 import org.incava.javadoc.*;
8 import org.incava.text.SpellChecker;
9
10
11 /**
12  * Checks for violations of rules applying to exceptions.
13  */

14 public class ExceptionDocAnalyzer extends DocAnalyzer
15 {
16     public final static String JavaDoc MSG_EXCEPTION_WITHOUT_CLASS_NAME = "Exception without class name";
17
18     public final static String JavaDoc MSG_EXCEPTION_WITHOUT_DESCRIPTION = "Exception without description";
19
20     public final static String JavaDoc MSG_EXCEPTIONS_NOT_ALPHABETICAL = "Exceptions not alphabetical";
21
22     public final static String JavaDoc MSG_EXCEPTION_NOT_IN_THROWS_LIST = "Exception not in throws list";
23
24     public final static String JavaDoc MSG_EXCEPTION_MISSPELLED = "Exception misspelled";
25
26     public final static String JavaDoc MSG_EXCEPTION_NOT_DOCUMENTED = "Exception not documented";
27
28     protected final static int CHKLVL_EXCEPTIONS_ALPHABETICAL = 2;
29
30     protected final static int CHKLVL_EXCEPTION_DOC_EXISTS = 1;
31     
32     private static Map excToRuntime = new HashMap();
33
34     private static Collection reportedExceptions = new ArrayList();
35
36     protected final static String JavaDoc[] KNOWN_RUNTIME_EXCEPTIONS = new String JavaDoc[] {
37         "java.awt.color.CMMException",
38         "java.awt.color.ProfileDataException",
39
40         "java.awt.geom.IllegalPathStateException",
41         
42         "java.awt.image.ImagingOpException",
43         "java.awt.image.RasterFormatException",
44
45         "java.lang.ArithmeticException",
46         "java.lang.ArrayStoreException",
47         "java.lang.ClassCastException",
48         "java.lang.EnumConstantNotPresentException",
49         "java.lang.IllegalArgumentException",
50         "java.lang.IllegalMonitorStateException",
51         "java.lang.IllegalStateException",
52         "java.lang.IndexOutOfBoundsException",
53         "java.lang.NegativeArraySizeException",
54         "java.lang.NullPointerException",
55         "java.lang.SecurityException",
56         "java.lang.TypeNotPresentException",
57         "java.lang.UnsupportedOperationException",
58
59         "java.lang.annotation.AnnotationTypeMismatchException",
60         "java.lang.annotation.IncompleteAnnotationException",
61         
62         "java.lang.reflect.MalformedParameterizedTypeException",
63         "java.lang.reflect.UndeclaredThrowableException",
64
65         "java.nio.BufferOverflowException",
66         "java.nio.BufferUnderflowException",
67
68         "java.security.ProviderException",
69
70         "java.util.ConcurrentModificationException",
71         "java.util.EmptyStackException",
72         "java.util.MissingResourceException",
73         "java.util.NoSuchElementException",
74         
75         "java.util.concurrent.RejectedExecutionException",
76
77         "javax.management.JMRuntimeException",
78         
79         "javax.print.attribute.UnmodifiableSetException",
80         
81         "javax.swing.undo.CannotRedoException",
82         "javax.swing.undo.CannotUndoException",
83         
84         "org.omg.CORBA.SystemException",
85         
86         "org.w3c.dom.DOMException",
87         
88         "org.w3c.dom.events.EventException",
89
90         "org.w3c.dom.ls.LSException",
91     };
92
93     static {
94         for (int ki = 0; ki < KNOWN_RUNTIME_EXCEPTIONS.length; ++ki) {
95             String JavaDoc ke = KNOWN_RUNTIME_EXCEPTIONS[ki];
96             excToRuntime.put(ke, Boolean.TRUE);
97         }
98     }
99
100     private JavadocNode _javadoc;
101
102     private ASTNameList _throwsList;
103
104     private SimpleNode _function;
105
106     private List _documentedExceptions = new ArrayList();
107
108     private int _nodeLevel;
109
110     private Map _importMap;
111
112     /**
113      * Creates and runs the exception documentation analyzer.
114      *
115      * @param report The report to which to send violations.
116      * @param javadoc The javadoc for the function. Should not be null.
117      * @param function The constructor or method.
118      */

119     public ExceptionDocAnalyzer(Report report, JavadocNode javadoc, SimpleNode function, int nodeLevel)
120     {
121         super(report);
122
123         _javadoc = javadoc;
124         _throwsList = FunctionUtil.getThrowsList(function);
125         _function = function;
126         _nodeLevel = nodeLevel;
127         _importMap = null;
128     }
129     
130     public void run()
131     {
132         // foreach @throws / @exception tag:
133
// - check for target
134
// - check for description
135
// - check that target is declared, or is subclass of RuntimeException
136
// - in alphabetical order
137

138         boolean alphabeticalReported = false;
139         String JavaDoc previousException = null;
140         
141         SimpleNode node = _function;
142         while (node != null && !(node instanceof ASTCompilationUnit)) {
143             node = SimpleNodeUtil.getParent(node);
144         }
145         
146         ASTCompilationUnit cu = (ASTCompilationUnit)node;
147         ASTImportDeclaration[] imports = CompilationUnitUtil.getImports(cu);
148         _importMap = makeImportMap(imports);
149
150         JavadocTaggedNode[] taggedComments = _javadoc.getTaggedComments();
151         for (int ti = 0; ti < taggedComments.length; ++ti) {
152             JavadocTaggedNode jtn = taggedComments[ti];
153             JavadocTag tag = jtn.getTag();
154
155             if (tag.text.equals(JavadocTags.EXCEPTION) || tag.text.equals(JavadocTags.THROWS)) {
156                 JavadocElement tgt = jtn.getTarget();
157
158                 if (tgt == null) {
159                     if (Options.warningLevel >= CHKLVL_TAG_CONTENT + _nodeLevel) {
160                         addViolation(MSG_EXCEPTION_WITHOUT_CLASS_NAME, tag.start, tag.end);
161                     }
162                 }
163                 else {
164                     if (jtn.getDescriptionNonTarget() == null && Options.warningLevel >= CHKLVL_TAG_CONTENT + _nodeLevel) {
165                         addViolation(MSG_EXCEPTION_WITHOUT_DESCRIPTION, tgt.start, tgt.end);
166                     }
167                     
168                     String JavaDoc shortName = getShortName(tgt.text);
169                     String JavaDoc fullName = tgt.text;
170                     Class JavaDoc cls = null;
171                     
172                     if (fullName.indexOf('.') >= 0) {
173                         cls = loadClass(fullName);
174                     }
175                     else {
176                         fullName = getExactMatch(fullName);
177                         
178                         if (fullName == null) {
179                             Iterator iit = _importMap.keySet().iterator();
180                             while (cls == null && iit.hasNext()) {
181                                 String JavaDoc impName = (String JavaDoc)iit.next();
182                                 String JavaDoc shImpName = getShortName(impName);
183                                 if (shImpName.equals("*")) {
184                                     // try to load pkg.name
185
fullName = impName.substring(0, impName.indexOf("*")) + shortName;
186                                     cls = loadClass(fullName);
187                                 }
188                                 else {
189                                     // skip it.
190
}
191                             }
192                             
193                             if (cls == null) {
194                                 // maybe java.lang....
195
fullName = "java.lang." + shortName;
196                                 cls = loadClass(fullName);
197                             }
198                         }
199                         else {
200                             cls = loadClass(fullName);
201                         }
202                     }
203                     
204                     checkAgainstCode(tag, tgt, shortName, fullName, cls);
205
206                     if (!alphabeticalReported &&
207                         Options.warningLevel >= CHKLVL_EXCEPTIONS_ALPHABETICAL + _nodeLevel &&
208                         previousException != null && previousException.compareTo(shortName) > 0) {
209                         
210                         addViolation(MSG_EXCEPTIONS_NOT_ALPHABETICAL, tgt.start, tgt.end);
211                         alphabeticalReported = true;
212                     }
213                         
214                     previousException = shortName;
215                 }
216             }
217         }
218
219         if (_throwsList != null && Options.warningLevel >= CHKLVL_EXCEPTION_DOC_EXISTS + _nodeLevel) {
220             reportUndocumentedExceptions();
221         }
222     }
223
224     protected Map makeImportMap(ASTImportDeclaration[] imports)
225     {
226         Map namesToImp = new HashMap();
227
228         for (int ii = 0; ii < imports.length; ++ii) {
229             ASTImportDeclaration imp = imports[ii];
230             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
231             Token tk = imp.getFirstToken().next;
232             
233             while (tk != null) {
234                 if (tk == imp.getLastToken()) {
235                     break;
236                 }
237                 else {
238                     buf.append(tk.image);
239                     tk = tk.next;
240                 }
241             }
242
243             namesToImp.put(buf.toString(), imp);
244         }
245         
246         return namesToImp;
247     }
248
249     /**
250      * Returns the short name of the class, e.g., Integer instead of
251      * java.lang.Integer.
252      */

253     protected String JavaDoc getShortName(String JavaDoc name)
254     {
255         int lastDot = name.lastIndexOf('.');
256         String JavaDoc shName = lastDot == -1 ? name : name.substring(lastDot + 1);
257         return shName;
258     }
259
260     protected String JavaDoc getExactMatch(String JavaDoc name)
261     {
262         Iterator iit = _importMap.keySet().iterator();
263         while (iit.hasNext()) {
264             String JavaDoc impName = (String JavaDoc)iit.next();
265             String JavaDoc shImpName = getShortName(impName);
266             if (shImpName.equals("*")) {
267                 // skip it.
268
}
269             else if (shImpName.equals(name)) {
270                 return impName;
271             }
272         }
273
274         return null;
275     }
276     
277     protected Class JavaDoc loadClass(String JavaDoc clsName)
278     {
279         try {
280             Class JavaDoc cls = Class.forName(clsName);
281             return cls;
282         }
283         catch (Exception JavaDoc e) {
284             // e.printStackTrace();
285
return null;
286         }
287     }
288
289     /**
290      * Returns whether the given class is derived from Runtimeexception or
291      * Error.
292      */

293     protected boolean isRuntimeException(Class JavaDoc excClass)
294     {
295         if (excClass == null) {
296             return false;
297         }
298         else {
299             String JavaDoc excName = excClass.getName();
300             Boolean JavaDoc val = (Boolean JavaDoc)excToRuntime.get(excName);
301             if (val == null) {
302                 val = new Boolean JavaDoc(RuntimeException JavaDoc.class.isAssignableFrom(excClass) ||
303                                   Error JavaDoc.class.isAssignableFrom(excClass));
304                 excToRuntime.put(excName, val);
305             }
306             return val.booleanValue();
307         }
308     }
309
310     protected void checkAgainstCode(JavadocTag tag, JavadocElement tgt, String JavaDoc shortExcName, String JavaDoc fullExcName, Class JavaDoc excClass)
311     {
312         ASTName name = getMatchingException(shortExcName);
313         if (name == null) {
314             name = getClosestMatchingException(shortExcName);
315             if (name == null) {
316                 if (isRuntimeException(excClass)) {
317                     // don't report it.
318
}
319                 else if (excClass != null || !reportedExceptions.contains(fullExcName)) {
320                     // this violation is an error, not a warning:
321
addViolation(MSG_EXCEPTION_NOT_IN_THROWS_LIST, tgt.start, tgt.end);
322                     
323                     // report it only once.
324
reportedExceptions.add(fullExcName);
325                 }
326                 else {
327                     // we don't report exceptions when we don't know if they're
328
// run-time exceptions, or when they've already been
329
// reported.
330
}
331             }
332             else {
333                 // this violation is an error:
334
addViolation(MSG_EXCEPTION_MISSPELLED, tgt.start, tgt.end);
335                 _documentedExceptions.add(name.getLastToken().image);
336             }
337         }
338         else {
339             _documentedExceptions.add(shortExcName);
340         }
341     }
342     
343     protected void reportUndocumentedExceptions()
344     {
345         ASTName[] names = ThrowsUtil.getNames(_throwsList);
346         
347         for (int ni = 0; ni < names.length; ++ni) {
348             ASTName name = names[ni];
349
350             // by using the last token, we disregard the package:
351
Token nameToken = name.getLastToken();
352             
353             tr.Ace.log("considering name: " + name + " (" + nameToken + ")");
354             if (!_documentedExceptions.contains(nameToken.image)) {
355                 addViolation(MSG_EXCEPTION_NOT_DOCUMENTED,
356                              nameToken.beginLine, nameToken.beginColumn,
357                              nameToken.beginLine, nameToken.beginColumn + nameToken.image.length() - 1);
358             }
359         }
360     }
361
362     /**
363      * Returns the first name in the list that matches the given string.
364      */

365     protected ASTName getMatchingException(String JavaDoc str)
366     {
367         if (_throwsList == null) {
368             return null;
369         }
370         else {
371             ASTName[] names = ThrowsUtil.getNames(_throwsList);
372             
373             for (int ni = 0; ni < names.length; ++ni) {
374                 ASTName name = names[ni];
375                 // (again) by using the last token, we disregard the package:
376
Token nameToken = name.getLastToken();
377                 tr.Ace.log("considering name: " + name + " (" + nameToken + ")");
378                 if (nameToken.image.equals(str)) {
379                     return name;
380                 }
381             }
382             tr.Ace.log("no exact match for '" + str + "'");
383             return null;
384         }
385     }
386
387     /**
388      * Returns the name in the list that most closely matches the given string.
389      */

390     protected ASTName getClosestMatchingException(String JavaDoc str)
391     {
392         if (_throwsList == null) {
393             return null;
394         }
395         else {
396             SpellChecker spellChecker = new SpellChecker();
397             int bestDistance = -1;
398             ASTName bestName = null;
399             ASTName[] names = ThrowsUtil.getNames(_throwsList);
400             
401             for (int ni = 0; ni < names.length; ++ni) {
402                 ASTName name = names[ni];
403                 Token nameToken = name.getLastToken();
404                 int dist = spellChecker.editDistance(nameToken.image, str);
405             
406                 if (dist >= 0 && dist <= SpellChecker.DEFAULT_MAX_DISTANCE && (bestDistance == -1 || dist < bestDistance)) {
407                     bestDistance = dist;
408                     bestName = name;
409                 }
410             }
411
412             return bestName;
413         }
414     }
415
416 }
417
Popular Tags