KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > CheckNamesProcessor


1 /*
2  * @(#)CheckNamesProcessor.java 1.2 06/09/28
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * -Redistribution of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  *
12  * -Redistribution in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of contributors may
17  * be used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
22  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
24  * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
25  * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
26  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
27  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
28  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
29  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31  *
32  * You acknowledge that this software is not designed, licensed or intended
33  * for use in the design, construction, operation or maintenance of any
34  * nuclear facility.
35  */

36
37 import java.util.Set JavaDoc;
38 import java.util.EnumSet JavaDoc;
39
40 import javax.annotation.processing.*;
41 import javax.lang.model.SourceVersion;
42 import javax.lang.model.element.*;
43 import javax.lang.model.type.*;
44 import javax.lang.model.util.*;
45 import static javax.lang.model.SourceVersion.*;
46 import static javax.lang.model.element.Modifier.*;
47 import static javax.lang.model.element.ElementKind.*;
48 import static javax.lang.model.type.TypeKind.*;
49 import static javax.lang.model.util.ElementFilter.*;
50 import static javax.tools.Diagnostic.Kind.*;
51
52 /**
53  * A sample processor to check naming conventions are being followed.
54  *
55  * <h3>How to run this processor from the command line</h3>
56  * <ol>
57  * <li> Compile this file; for example<br>
58  * {@code javac -d procdir CheckNamesProcessor.java}
59  * <li> Use {@code javac} to run the annotation processor on itself:<br>
60  * {@code javac -processorpath procdir -processor CheckNamesProcessor -proc:only CheckNamesProcessor.java}
61  * </ol>
62  *
63  * <h3>Another way to run this processor from the command line</h3>
64  * <ol>
65  * <li> Compile the processor as before
66  *
67  * <li> Create a UTF-8 encoded text file named {@code
68  * javax.annotation.processing.Processor} in the {@code
69  * META-INF/services} directory. The contents of the file are a list
70  * of the binary names of the concrete processor classes, one per
71  * line. This provider-configuration file is used by {@linkplain
72  * java.util.ServiceLoader service-loader} style lookup.
73  *
74  * <li> Create a {@code jar} file with the processor classes and
75  * {@code META-INF} information.
76  *
77  * <li> Such a {@code jar} file can now be used with the <i>discovery
78  * process</i> without explicitly naming the processor to run:<br>
79  * {@code javac -processorpath procdir -proc:only CheckNamesProcessor.java}
80  *
81  * </ol>
82  *
83  * For some notes on how to run an annotation processor inside
84  * NetBeans, see http://wiki.java.net/bin/view/Netbeans/FaqApt.
85  *
86  * <h3>Possible Enhancements</h3>
87  * <ul>
88  *
89  * <li> Support an annotation processor option to control checking
90  * exported API elements ({@code public} and {@code protected} ones)
91  * or all elements
92  *
93  * <li> Print out warnings that are more informative
94  *
95  * <li> Return a true/false status if any warnings were printed or
96  * compute and return name warning count
97  *
98  * <li> Implement checks of package names
99  *
100  * <li> Use the Tree API, com.sun.source, to examine names within method bodies
101  *
102  * <li> Define an annotation type whose presence can indicate a
103  * different naming convention is being followed
104  *
105  * <li> Implement customized checks on elements in chosen packages
106  *
107  * </ul>
108  *
109  * @author Joseph D. Darcy
110  */

111 @SupportedAnnotationTypes("*") // Process (check) everything
112
public class CheckNamesProcessor extends AbstractProcessor {
113     private NameChecker nameChecker;
114     
115     /**
116      * Check that the names of the root elements (and their enclosed
117      * elements) follow the appropriate naming conventions. This
118      * processor examines all files regardless of whether or not
119      * annotations are present; no new source or class files are
120      * generated.
121      *
122      * <p>Processors that actually process specific annotations should
123      * <em>not</em> report supporting {@code *}; this could cause
124      * performance degradations and other undesirable outcomes.
125      */

126     @Override JavaDoc
127     public boolean process(Set JavaDoc<? extends TypeElement> annotations,
128             RoundEnvironment roundEnv) {
129         if (!roundEnv.processingOver()) {
130             for (Element element : roundEnv.getRootElements() )
131                 nameChecker.checkNames(element);
132         }
133         return false; // Allow other processors to examine files too.
134
}
135     
136     @Override JavaDoc
137     public void init(ProcessingEnvironment processingEnv) {
138         super.init(processingEnv);
139         nameChecker = new NameChecker(processingEnv);
140     }
141     
142     @Override JavaDoc
143     public SourceVersion getSupportedSourceVersion() {
144         /*
145          * Return latest source version instead of a fixed version
146          * like RELEASE_6. To return a fixed version, this class
147          * could be annotated with a SupportedSourceVersion
148          * annotation.
149          *
150          * Warnings will be issued if any unknown language constructs
151          * are encountered.
152          */

153         return SourceVersion.latest();
154     }
155     
156     /**
157      * Provide checks that an element and its enclosed elements follow
158      * the usual naming conventions.
159      *
160      * <p> Conventions from JLSv3 section 6.8:
161      *
162      * <ul>
163      * <li> Classes and interfaces: camel case, first letter is uppercase
164      * <li> Methods: camel case, first letter is lowercase
165      * <li> Type variables: one uppercase letter
166      * <li> Fields
167      * <ul>
168      * <li> non-final: camel case, initial lowercase
169      * <li> constant: uppercase separated by underscores
170      * </ul>
171      * <li> Packages: checks left as exercise for the reader, see JLSv3 section 7.7
172      * </ul>
173      */

174     private static class NameChecker {
175         private final Messager messager;
176         private final Types typeUtils;
177         
178         NameCheckScanner nameCheckScanner = new NameCheckScanner();
179         
180         NameChecker(ProcessingEnvironment processsingEnv) {
181             this.messager = processsingEnv.getMessager();
182             this.typeUtils = processsingEnv.getTypeUtils();
183         }
184         
185         /**
186          * If the name of the argument or its enclosed elements
187          * violates the naming conventions, report a warning.
188          */

189         public void checkNames(Element element) {
190             // Implement name checks with a visitor, but expose that
191
// functionality through this method instead.
192
nameCheckScanner.scan(element);
193         }
194         
195         /**
196          * Visitor to implement name checks.
197          */

198         private class NameCheckScanner extends ElementScanner6<Void JavaDoc, Void JavaDoc> {
199             // The visitor could be enhanced to return true/false if
200
// there were warnings reported or a count of the number
201
// of warnings. This could be facilitated by using
202
// Boolean or Integer instead of Void for the actual type
203
// arguments. In more detail, one way to tally the number
204
// of warnings would be for each method to return the sum
205
// of the warnings it and the methods it called issued, a
206
// bottom-up computation. In that case, the first type
207
// argument would be Integer and the second type argument
208
// would still be Void. Alternatively, the current count
209
// could be passed along in Integer parameter p and each
210
// method could return the Integer sum of p and the
211
// warnings the method issued. Some computations are more
212
// naturally expressed in one form instead of the other.
213
// If greater control is needed over traversal order, a
214
// SimpleElementVisitor can be extended instead of an
215
// ElementScanner.
216

217             /**
218              * Check the name of a type and its enclosed elements and
219              * type parameters.
220              */

221             @Override JavaDoc
222             public Void JavaDoc visitType(TypeElement e, Void JavaDoc p) {
223                 scan(e.getTypeParameters(), p); // Check the names of any type parameters
224
checkCamelCase(e, true); // Check the name of the class or interface
225
super.visitType(e, p); // Check the names of any enclosed elements
226
return null;
227             }
228             
229             /**
230              * Check the name of an executable (method, constructor,
231              * etc.) and its type parameters.
232              */

233             @Override JavaDoc
234             public Void JavaDoc visitExecutable(ExecutableElement e, Void JavaDoc p) {
235                 scan(e.getTypeParameters(), p); // Check the names of any type parameters
236

237                 // Check the name of the executable
238
if (e.getKind() == METHOD) {
239                     // Make sure that a method does not have the same
240
// name as its class or interface.
241
Name name = e.getSimpleName();
242                     if (name.contentEquals(e.getEnclosingElement().getSimpleName()))
243                         messager.printMessage(WARNING,
244                                               "A method should not have the same name as its enclosing type, ``" +
245                                               name + "''." , e);
246                     checkCamelCase(e, false);
247                 }
248                 // else constructors and initializers don't have user-defined names
249

250                 // At this point, could use the Tree API,
251
// com.sun.source, to examine the names of entities
252
// inside a method.
253
super.visitExecutable(e, p);
254                 return null;
255             }
256             
257             /**
258              * Check the name of a field, parameter, etc.
259              */

260             @Override JavaDoc
261             public Void JavaDoc visitVariable(VariableElement e, Void JavaDoc p) {
262                 if (!checkForSerial(e)) { // serialVersionUID checks
263
// Is the variable a constant?
264
if (e.getKind() == ENUM_CONSTANT ||
265                         e.getConstantValue() != null ||
266             heuristicallyConstant(e) )
267                         checkAllCaps(e); // includes enum constants
268
else
269                         checkCamelCase(e, false);
270                 }
271                 // A call to super can be elided with the current language definition.
272
// super.visitVariable(e, p);
273
return null;
274             }
275
276             /**
277              * Check the name of a type parameter.
278              */

279             @Override JavaDoc
280             public Void JavaDoc visitTypeParameter(TypeParameterElement e, Void JavaDoc p) {
281                 checkAllCaps(e);
282                 // A call to super can be elided with the current language definition.
283
// super.visitTypeParameter(e, p);
284
return null;
285             }
286             
287             /**
288              * Check the name of a package.
289              */

290             @Override JavaDoc
291             public Void JavaDoc visitPackage(PackageElement e, Void JavaDoc p) {
292                 /*
293                  * Implementing the checks of package names is left
294                  * as an exercise for the reader, see JLSv3 section
295                  * 7.7 for conventions.
296                  */

297                 
298                 // Whether or not this method should call
299
// super.visitPackage, to visit the packages enclosed
300
// elements, is a design decision based on what a
301
// PackageElemement is used to mean in this context.
302
// A PackageElement can represent a whole package, so
303
// it can provide a concise way to indicate many
304
// user-defined types should be visited. However, a
305
// PackageElement can also represent a
306
// package-info.java file, as would be in the case if
307
// the PackageElement came from
308
// RoundEnvironment.getRootElements. In that case,
309
// the package-info file and other files in that
310
// package could be passed in. Therefore, without
311
// further checks, types in a package could be visited
312
// more than once if a package's elements were visited
313
// too.
314
return null;
315             }
316             
317             @Override JavaDoc
318             public Void JavaDoc visitUnknown(Element e, Void JavaDoc p) {
319                 // This method will be called if a kind of element
320
// added after JDK 6 is visited. Since as of this
321
// writing the conventions for such constructs aren't
322
// known, issue a warning.
323
messager.printMessage(WARNING,
324                                       "Unknown kind of element, " + e.getKind() +
325                                       ", no name checking performed.", e);
326                 return null;
327             }
328             
329             // All the name checking methods assume the examined names
330
// are syntactically well-formed identifiers.
331

332             /**
333              * Return {@code true} if this variable is a field named
334              * "serialVersionUID"; false otherwise. A true
335              * serialVersionUID of a class has type {@code long} and
336              * is static and final.
337              *
338              * <p>To check that a Serializable class defines a proper
339              * serialVersionUID, run javac with -Xlint:serial.
340          *
341          * @return true if this variable is a serialVersionUID field and false otherwise
342              */

343             private boolean checkForSerial(VariableElement e) {
344                 // If a field is named "serialVersionUID" ...
345
if (e.getKind() == FIELD &&
346             e.getSimpleName().contentEquals("serialVersionUID")) {
347                     // ... issue a warning if it does not act as a serialVersionUID
348
if (!(e.getModifiers().containsAll(EnumSet.of(STATIC, FINAL)) &&
349                             typeUtils.isSameType(e.asType(), typeUtils.getPrimitiveType(LONG)) &&
350                             e.getEnclosingElement().getKind() == CLASS )) // could check that class implements Serializable
351
messager.printMessage(WARNING,
352                           "Field named ``serialVersionUID'' is not acting as such.", e);
353                     return true;
354                 }
355                 return false;
356             }
357             
358         /**
359          * Using heuristics, return {@code true} is the variable
360          * should follow the naming conventions for constants and
361          * {@code false} otherwise. For example, the public
362          * static final fields ZERO, ONE, and TEN in
363          * java.math.BigDecimal are logically constants (and named
364          * as constants) even though BigDecimal values are not
365          * regarded as constants by the language specification.
366          * However, some final fields may not act as constants
367          * since the field may be a reference to a mutable object.
368          *
369          * <p> These heuristics could be tweaked to provide better
370          * fidelity.
371          *
372          * @return true if the current heuristics regard the
373          * variable as a constant and false otherwise.
374          */

375         private boolean heuristicallyConstant(VariableElement e) {
376         // Fields declared in interfaces are logically
377
// constants, JLSv3 section 9.3.
378
if (e.getEnclosingElement().getKind() == INTERFACE)
379             return true;
380         else if (e.getKind() == FIELD &&
381              e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL)))
382             return true;
383         else {
384             // A parameter declared final should not be named like
385
// a constant, neither should exception parameters.
386
return false;
387         }
388         }
389             
390             /**
391              * Print a warning if an element's simple name is not in
392              * camel case. If there are two adjacent uppercase
393              * characters, the name is considered to violate the
394              * camel case naming convention.
395              *
396              * @param e the element whose name will be checked
397              * @param initialCaps whether or not the first character should be uppercase
398              */

399             private void checkCamelCase(Element e, boolean initialCaps) {
400                 String JavaDoc name = e.getSimpleName().toString();
401                 boolean previousUpper = false;
402                 boolean conventional = true;
403         int firstCodePoint = name.codePointAt(0);
404                 
405                 if (Character.isUpperCase(firstCodePoint)) {
406                     previousUpper = true;
407                     if (!initialCaps) {
408                         messager.printMessage(WARNING,
409                                               "Name, ``" + name + "'', should start in lowercase.", e);
410                         return;
411                     }
412                 } else if (Character.isLowerCase(firstCodePoint)) {
413                     if (initialCaps) {
414                         messager.printMessage(WARNING,
415                                               "Name, ``" + name + "'', should start in uppercase.", e);
416                         return;
417                     }
418                 } else // underscore, etc.
419
conventional = false;
420                 
421                 if (conventional) {
422             int cp = firstCodePoint;
423             for (int i = Character.charCount(cp);
424              i < name.length();
425              i += Character.charCount(cp)) {
426             cp = name.codePointAt(i);
427                         if (Character.isUpperCase(cp)){
428                             if (previousUpper) {
429                                 conventional = false;
430                                 break;
431                             }
432                             previousUpper = true;
433                         } else
434                             previousUpper = false;
435                     }
436                 }
437                 
438                 if (!conventional)
439                     messager.printMessage(WARNING,
440                                           "Name, ``" + name + "'', should be in camel case.", e);
441             }
442             
443             /**
444              * Print a warning if the element's name is not a sequence
445              * of uppercase letters separated by underscores ("_").
446              *
447              * @param e the element whose name will be checked
448              */

449             private void checkAllCaps(Element e) {
450                 String JavaDoc name = e.getSimpleName().toString();
451                 if (e.getKind() == TYPE_PARAMETER) { // Should be one character
452
if (name.codePointCount(0, name.length()) > 1 ||
453                         // Assume names are non-empty
454
!Character.isUpperCase(name.codePointAt(0)))
455                         messager.printMessage(WARNING,
456                                               "A type variable's name,``" + name +
457                                               "'', should be a single uppercace character.",
458                                               e);
459                 } else {
460                     boolean conventional = true;
461             int firstCodePoint = name.codePointAt(0);
462
463                     // Starting with an underscore is not conventional
464
if (!Character.isUpperCase(firstCodePoint))
465                         conventional = false;
466                     else {
467                         // Was the previous character an underscore?
468
boolean previousUnderscore = false;
469             int cp = firstCodePoint;
470             for (int i = Character.charCount(cp);
471                  i < name.length();
472                  i += Character.charCount(cp)) {
473                 cp = name.codePointAt(i);
474                             if (cp == (int) '_') {
475                                 if (previousUnderscore) {
476                                     conventional = false;
477                                     break;
478                                 }
479                                 previousUnderscore = true;
480                             } else {
481                                 previousUnderscore = false;
482                                 if (!Character.isUpperCase(cp) && !Character.isDigit(cp) ) {
483                                     conventional = false;
484                                     break;
485                                 }
486                             }
487                         }
488                     }
489                     
490                     if (!conventional)
491                         messager.printMessage(WARNING,
492                                               "A constant's name, ``" + name + "'', should be ALL_CAPS.",
493                                               e);
494                 }
495             }
496             
497         }
498     }
499 }
500
501 /**
502  * Lots of bad names. Don't write code like this!
503  */

504 class BADLY_NAMED_CODE {
505     enum colors {
506         red,
507         blue,
508         green;
509     }
510     
511     // Don't start the name of a constant with an underscore
512
static final int _FORTY_TWO = 42;
513     
514     // Non-constants shouldn't use ALL_CAPS
515
public static int NOT_A_CONSTANT = _FORTY_TWO;
516     
517     // *Not* a serialVersionUID
518
private static final int serialVersionUID = _FORTY_TWO;
519     
520     // Not a constructor
521
protected void BADLY_NAMED_CODE() {
522         return;
523     }
524     
525     public void NOTcamelCASEmethodNAME() {
526         return;
527     }
528 }
529
Popular Tags