/* * @(#)CheckNamesProcessor.java 1.2 06/09/28 * * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ import java.util.Set; import java.util.EnumSet; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.lang.model.type.*; import javax.lang.model.util.*; import static javax.lang.model.SourceVersion.*; import static javax.lang.model.element.Modifier.*; import static javax.lang.model.element.ElementKind.*; import static javax.lang.model.type.TypeKind.*; import static javax.lang.model.util.ElementFilter.*; import static javax.tools.Diagnostic.Kind.*; /** * A sample processor to check naming conventions are being followed. * *

How to run this processor from the command line

*
    *
  1. Compile this file; for example
    * {@code javac -d procdir CheckNamesProcessor.java} *
  2. Use {@code javac} to run the annotation processor on itself:
    * {@code javac -processorpath procdir -processor CheckNamesProcessor -proc:only CheckNamesProcessor.java} *
* *

Another way to run this processor from the command line

*
    *
  1. Compile the processor as before * *
  2. Create a UTF-8 encoded text file named {@code * javax.annotation.processing.Processor} in the {@code * META-INF/services} directory. The contents of the file are a list * of the binary names of the concrete processor classes, one per * line. This provider-configuration file is used by {@linkplain * java.util.ServiceLoader service-loader} style lookup. * *
  3. Create a {@code jar} file with the processor classes and * {@code META-INF} information. * *
  4. Such a {@code jar} file can now be used with the discovery * process without explicitly naming the processor to run:
    * {@code javac -processorpath procdir -proc:only CheckNamesProcessor.java} * *
* * For some notes on how to run an annotation processor inside * NetBeans, see http://wiki.java.net/bin/view/Netbeans/FaqApt. * *

Possible Enhancements

* * * @author Joseph D. Darcy */ @SupportedAnnotationTypes("*") // Process (check) everything public class CheckNamesProcessor extends AbstractProcessor { private NameChecker nameChecker; /** * Check that the names of the root elements (and their enclosed * elements) follow the appropriate naming conventions. This * processor examines all files regardless of whether or not * annotations are present; no new source or class files are * generated. * *

Processors that actually process specific annotations should * not report supporting {@code *}; this could cause * performance degradations and other undesirable outcomes. */ @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (!roundEnv.processingOver()) { for (Element element : roundEnv.getRootElements() ) nameChecker.checkNames(element); } return false; // Allow other processors to examine files too. } @Override public void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); nameChecker = new NameChecker(processingEnv); } @Override public SourceVersion getSupportedSourceVersion() { /* * Return latest source version instead of a fixed version * like RELEASE_6. To return a fixed version, this class * could be annotated with a SupportedSourceVersion * annotation. * * Warnings will be issued if any unknown language constructs * are encountered. */ return SourceVersion.latest(); } /** * Provide checks that an element and its enclosed elements follow * the usual naming conventions. * *

Conventions from JLSv3 section 6.8: * *

*/ private static class NameChecker { private final Messager messager; private final Types typeUtils; NameCheckScanner nameCheckScanner = new NameCheckScanner(); NameChecker(ProcessingEnvironment processsingEnv) { this.messager = processsingEnv.getMessager(); this.typeUtils = processsingEnv.getTypeUtils(); } /** * If the name of the argument or its enclosed elements * violates the naming conventions, report a warning. */ public void checkNames(Element element) { // Implement name checks with a visitor, but expose that // functionality through this method instead. nameCheckScanner.scan(element); } /** * Visitor to implement name checks. */ private class NameCheckScanner extends ElementScanner6 { // The visitor could be enhanced to return true/false if // there were warnings reported or a count of the number // of warnings. This could be facilitated by using // Boolean or Integer instead of Void for the actual type // arguments. In more detail, one way to tally the number // of warnings would be for each method to return the sum // of the warnings it and the methods it called issued, a // bottom-up computation. In that case, the first type // argument would be Integer and the second type argument // would still be Void. Alternatively, the current count // could be passed along in Integer parameter p and each // method could return the Integer sum of p and the // warnings the method issued. Some computations are more // naturally expressed in one form instead of the other. // If greater control is needed over traversal order, a // SimpleElementVisitor can be extended instead of an // ElementScanner. /** * Check the name of a type and its enclosed elements and * type parameters. */ @Override public Void visitType(TypeElement e, Void p) { scan(e.getTypeParameters(), p); // Check the names of any type parameters checkCamelCase(e, true); // Check the name of the class or interface super.visitType(e, p); // Check the names of any enclosed elements return null; } /** * Check the name of an executable (method, constructor, * etc.) and its type parameters. */ @Override public Void visitExecutable(ExecutableElement e, Void p) { scan(e.getTypeParameters(), p); // Check the names of any type parameters // Check the name of the executable if (e.getKind() == METHOD) { // Make sure that a method does not have the same // name as its class or interface. Name name = e.getSimpleName(); if (name.contentEquals(e.getEnclosingElement().getSimpleName())) messager.printMessage(WARNING, "A method should not have the same name as its enclosing type, ``" + name + "''." , e); checkCamelCase(e, false); } // else constructors and initializers don't have user-defined names // At this point, could use the Tree API, // com.sun.source, to examine the names of entities // inside a method. super.visitExecutable(e, p); return null; } /** * Check the name of a field, parameter, etc. */ @Override public Void visitVariable(VariableElement e, Void p) { if (!checkForSerial(e)) { // serialVersionUID checks // Is the variable a constant? if (e.getKind() == ENUM_CONSTANT || e.getConstantValue() != null || heuristicallyConstant(e) ) checkAllCaps(e); // includes enum constants else checkCamelCase(e, false); } // A call to super can be elided with the current language definition. // super.visitVariable(e, p); return null; } /** * Check the name of a type parameter. */ @Override public Void visitTypeParameter(TypeParameterElement e, Void p) { checkAllCaps(e); // A call to super can be elided with the current language definition. // super.visitTypeParameter(e, p); return null; } /** * Check the name of a package. */ @Override public Void visitPackage(PackageElement e, Void p) { /* * Implementing the checks of package names is left * as an exercise for the reader, see JLSv3 section * 7.7 for conventions. */ // Whether or not this method should call // super.visitPackage, to visit the packages enclosed // elements, is a design decision based on what a // PackageElemement is used to mean in this context. // A PackageElement can represent a whole package, so // it can provide a concise way to indicate many // user-defined types should be visited. However, a // PackageElement can also represent a // package-info.java file, as would be in the case if // the PackageElement came from // RoundEnvironment.getRootElements. In that case, // the package-info file and other files in that // package could be passed in. Therefore, without // further checks, types in a package could be visited // more than once if a package's elements were visited // too. return null; } @Override public Void visitUnknown(Element e, Void p) { // This method will be called if a kind of element // added after JDK 6 is visited. Since as of this // writing the conventions for such constructs aren't // known, issue a warning. messager.printMessage(WARNING, "Unknown kind of element, " + e.getKind() + ", no name checking performed.", e); return null; } // All the name checking methods assume the examined names // are syntactically well-formed identifiers. /** * Return {@code true} if this variable is a field named * "serialVersionUID"; false otherwise. A true * serialVersionUID of a class has type {@code long} and * is static and final. * *

To check that a Serializable class defines a proper * serialVersionUID, run javac with -Xlint:serial. * * @return true if this variable is a serialVersionUID field and false otherwise */ private boolean checkForSerial(VariableElement e) { // If a field is named "serialVersionUID" ... if (e.getKind() == FIELD && e.getSimpleName().contentEquals("serialVersionUID")) { // ... issue a warning if it does not act as a serialVersionUID if (!(e.getModifiers().containsAll(EnumSet.of(STATIC, FINAL)) && typeUtils.isSameType(e.asType(), typeUtils.getPrimitiveType(LONG)) && e.getEnclosingElement().getKind() == CLASS )) // could check that class implements Serializable messager.printMessage(WARNING, "Field named ``serialVersionUID'' is not acting as such.", e); return true; } return false; } /** * Using heuristics, return {@code true} is the variable * should follow the naming conventions for constants and * {@code false} otherwise. For example, the public * static final fields ZERO, ONE, and TEN in * java.math.BigDecimal are logically constants (and named * as constants) even though BigDecimal values are not * regarded as constants by the language specification. * However, some final fields may not act as constants * since the field may be a reference to a mutable object. * *

These heuristics could be tweaked to provide better * fidelity. * * @return true if the current heuristics regard the * variable as a constant and false otherwise. */ private boolean heuristicallyConstant(VariableElement e) { // Fields declared in interfaces are logically // constants, JLSv3 section 9.3. if (e.getEnclosingElement().getKind() == INTERFACE) return true; else if (e.getKind() == FIELD && e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL))) return true; else { // A parameter declared final should not be named like // a constant, neither should exception parameters. return false; } } /** * Print a warning if an element's simple name is not in * camel case. If there are two adjacent uppercase * characters, the name is considered to violate the * camel case naming convention. * * @param e the element whose name will be checked * @param initialCaps whether or not the first character should be uppercase */ private void checkCamelCase(Element e, boolean initialCaps) { String name = e.getSimpleName().toString(); boolean previousUpper = false; boolean conventional = true; int firstCodePoint = name.codePointAt(0); if (Character.isUpperCase(firstCodePoint)) { previousUpper = true; if (!initialCaps) { messager.printMessage(WARNING, "Name, ``" + name + "'', should start in lowercase.", e); return; } } else if (Character.isLowerCase(firstCodePoint)) { if (initialCaps) { messager.printMessage(WARNING, "Name, ``" + name + "'', should start in uppercase.", e); return; } } else // underscore, etc. conventional = false; if (conventional) { int cp = firstCodePoint; for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) { cp = name.codePointAt(i); if (Character.isUpperCase(cp)){ if (previousUpper) { conventional = false; break; } previousUpper = true; } else previousUpper = false; } } if (!conventional) messager.printMessage(WARNING, "Name, ``" + name + "'', should be in camel case.", e); } /** * Print a warning if the element's name is not a sequence * of uppercase letters separated by underscores ("_"). * * @param e the element whose name will be checked */ private void checkAllCaps(Element e) { String name = e.getSimpleName().toString(); if (e.getKind() == TYPE_PARAMETER) { // Should be one character if (name.codePointCount(0, name.length()) > 1 || // Assume names are non-empty !Character.isUpperCase(name.codePointAt(0))) messager.printMessage(WARNING, "A type variable's name,``" + name + "'', should be a single uppercace character.", e); } else { boolean conventional = true; int firstCodePoint = name.codePointAt(0); // Starting with an underscore is not conventional if (!Character.isUpperCase(firstCodePoint)) conventional = false; else { // Was the previous character an underscore? boolean previousUnderscore = false; int cp = firstCodePoint; for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) { cp = name.codePointAt(i); if (cp == (int) '_') { if (previousUnderscore) { conventional = false; break; } previousUnderscore = true; } else { previousUnderscore = false; if (!Character.isUpperCase(cp) && !Character.isDigit(cp) ) { conventional = false; break; } } } } if (!conventional) messager.printMessage(WARNING, "A constant's name, ``" + name + "'', should be ALL_CAPS.", e); } } } } } /** * Lots of bad names. Don't write code like this! */ class BADLY_NAMED_CODE { enum colors { red, blue, green; } // Don't start the name of a constant with an underscore static final int _FORTY_TWO = 42; // Non-constants shouldn't use ALL_CAPS public static int NOT_A_CONSTANT = _FORTY_TWO; // *Not* a serialVersionUID private static final int serialVersionUID = _FORTY_TWO; // Not a constructor protected void BADLY_NAMED_CODE() { return; } public void NOTcamelCASEmethodNAME() { return; } }