1 /* 2 * Copyright (c) 2006, 2014, Oracle and/or its affiliates. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * - Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * - Neither the name of Oracle nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 import java.util.Set; 33 import java.util.EnumSet; 34 35 import javax.annotation.processing.*; 36 import javax.lang.model.SourceVersion; 37 import javax.lang.model.element.*; 38 import javax.lang.model.type.*; 39 import javax.lang.model.util.*; 40 import static javax.lang.model.SourceVersion.*; 41 import static javax.lang.model.element.Modifier.*; 42 import static javax.lang.model.element.ElementKind.*; 43 import static javax.lang.model.type.TypeKind.*; 44 import static javax.lang.model.util.ElementFilter.*; 45 import static javax.tools.Diagnostic.Kind.*; 46 47 /** 48 * A sample processor to check naming conventions are being followed. 49 * 50 * <h3>How to run this processor from the command line</h3> 51 * <ol> 52 * <li> Compile this file; for example<br> 53 * {@code javac -d procdir CheckNamesProcessor.java} 54 * <li> Use {@code javac} to run the annotation processor on itself:<br> 55 * {@code javac -processorpath procdir -processor CheckNamesProcessor -proc:only CheckNamesProcessor.java} 56 * </ol> 57 * 58 * <h3>Another way to run this processor from the command line</h3> 59 * <ol> 60 * <li> Compile the processor as before 61 * 62 * <li> Create a UTF-8 encoded text file named {@code 63 * javax.annotation.processing.Processor} in the {@code 64 * META-INF/services} directory. The contents of the file are a list 65 * of the binary names of the concrete processor classes, one per 66 * line. This provider-configuration file is used by {@linkplain 67 * java.util.ServiceLoader service-loader} style lookup. 68 * 69 * <li> Create a {@code jar} file with the processor classes and 70 * {@code META-INF} information. 71 * 72 * <li> Such a {@code jar} file can now be used with the <i>discovery 73 * process</i> without explicitly naming the processor to run:<br> 74 * {@code javac -processorpath procdir -proc:only CheckNamesProcessor.java} 75 * 76 * </ol> 77 * 78 * For some notes on how to run an annotation processor inside 79 * NetBeans, see http://wiki.java.net/bin/view/Netbeans/FaqApt. 80 * 81 * <h3>Possible Enhancements</h3> 82 * <ul> 83 * 84 * <li> Support an annotation processor option to control checking 85 * exported API elements ({@code public} and {@code protected} ones) 86 * or all elements 87 * 88 * <li> Print out warnings that are more informative 89 * 90 * <li> Return a true/false status if any warnings were printed or 91 * compute and return name warning count 92 * 93 * <li> Implement checks of package names 94 * 95 * <li> Use the Tree API, com.sun.source, to examine names within method bodies 96 * 97 * <li> Define an annotation type whose presence can indicate a 98 * different naming convention is being followed 99 * 100 * <li> Implement customized checks on elements in chosen packages 101 * 102 * </ul> 103 * 104 * @author Joseph D. Darcy 105 */ 106 @SupportedAnnotationTypes("*") // Process (check) everything 107 public class CheckNamesProcessor extends AbstractProcessor { 108 private NameChecker nameChecker; 109 110 /** 111 * Check that the names of the root elements (and their enclosed 112 * elements) follow the appropriate naming conventions. This 113 * processor examines all files regardless of whether or not 114 * annotations are present; no new source or class files are 115 * generated. 116 * 117 * <p>Processors that actually process specific annotations should 118 * <em>not</em> report supporting {@code *}; this could cause 119 * performance degradations and other undesirable outcomes. 120 */ 121 @Override 122 public boolean process(Set<? extends TypeElement> annotations, 123 RoundEnvironment roundEnv) { 124 if (!roundEnv.processingOver()) { 125 for (Element element : roundEnv.getRootElements() ) 126 nameChecker.checkNames(element); 127 } 128 return false; // Allow other processors to examine files too. 129 } 130 131 @Override 132 public void init(ProcessingEnvironment processingEnv) { 133 super.init(processingEnv); 134 nameChecker = new NameChecker(processingEnv); 135 } 136 137 @Override 138 public SourceVersion getSupportedSourceVersion() { 139 /* 140 * Return latest source version instead of a fixed version 141 * like RELEASE_9. To return a fixed version, this class 142 * could be annotated with a SupportedSourceVersion 143 * annotation. 144 * 145 * Warnings will be issued if any unknown language constructs 146 * are encountered. 147 */ 148 return SourceVersion.latest(); 149 } 150 151 /** 152 * Provide checks that an element and its enclosed elements follow 153 * the usual naming conventions. 154 * 155 * <p> Conventions from section 6.8 of 156 * <cite>The Java™ Language Specification</cite> 157 * 158 * <ul> 159 * <li> Classes and interfaces: camel case, first letter is uppercase 160 * <li> Methods: camel case, first letter is lowercase 161 * <li> Type variables: one uppercase letter 162 * <li> Fields 163 * <ul> 164 * <li> non-final: camel case, initial lowercase 165 * <li> constant: uppercase separated by underscores 166 * </ul> 167 * <li> Packages: checks left as exercise for the reader, see section 7.7 of 168 * <cite>The Java™ Language Specification</cite>. 169 * </ul> 170 */ 171 private static class NameChecker { 172 private final Messager messager; 173 private final Types typeUtils; 174 175 NameCheckScanner nameCheckScanner = new NameCheckScanner(); 176 177 NameChecker(ProcessingEnvironment processsingEnv) { 178 this.messager = processsingEnv.getMessager(); 179 this.typeUtils = processsingEnv.getTypeUtils(); 180 } 181 182 /** 183 * If the name of the argument or its enclosed elements 184 * violates the naming conventions, report a warning. 185 */ 186 public void checkNames(Element element) { 187 // Implement name checks with a visitor, but expose that 188 // functionality through this method instead. 189 nameCheckScanner.scan(element); 190 } 191 192 /** 193 * Visitor to implement name checks. 194 */ 195 private class NameCheckScanner extends ElementScanner9<Void, Void> { 196 // The visitor could be enhanced to return true/false if 197 // there were warnings reported or a count of the number 198 // of warnings. This could be facilitated by using 199 // Boolean or Integer instead of Void for the actual type 200 // arguments. In more detail, one way to tally the number 201 // of warnings would be for each method to return the sum 202 // of the warnings it and the methods it called issued, a 203 // bottom-up computation. In that case, the first type 204 // argument would be Integer and the second type argument 205 // would still be Void. Alternatively, the current count 206 // could be passed along in Integer parameter p and each 207 // method could return the Integer sum of p and the 208 // warnings the method issued. Some computations are more 209 // naturally expressed in one form instead of the other. 210 // If greater control is needed over traversal order, a 211 // SimpleElementVisitor can be extended instead of an 212 // ElementScanner. 213 214 /** 215 * Check the name of a type and its enclosed elements and 216 * type parameters. 217 */ 218 @Override 219 public Void visitType(TypeElement e, Void p) { 220 scan(e.getTypeParameters(), p); // Check the names of any type parameters 221 checkCamelCase(e, true); // Check the name of the class or interface 222 super.visitType(e, p); // Check the names of any enclosed elements 223 return null; 224 } 225 226 /** 227 * Check the name of an executable (method, constructor, 228 * etc.) and its type parameters. 229 */ 230 @Override 231 public Void visitExecutable(ExecutableElement e, Void p) { 232 scan(e.getTypeParameters(), p); // Check the names of any type parameters 233 234 // Check the name of the executable 235 if (e.getKind() == METHOD) { 236 // Make sure that a method does not have the same 237 // name as its class or interface. 238 Name name = e.getSimpleName(); 239 if (name.contentEquals(e.getEnclosingElement().getSimpleName())) 240 messager.printMessage(WARNING, 241 "A method should not have the same name as its enclosing type, ``" + 242 name + "''." , e); 243 checkCamelCase(e, false); 244 } 245 // else constructors and initializers don't have user-defined names 246 247 // At this point, could use the Tree API, 248 // com.sun.source, to examine the names of entities 249 // inside a method. 250 super.visitExecutable(e, p); 251 return null; 252 } 253 254 /** 255 * Check the name of a field, parameter, etc. 256 */ 257 @Override 258 public Void visitVariable(VariableElement e, Void p) { 259 if (!checkForSerial(e)) { // serialVersionUID checks 260 // Is the variable a constant? 261 if (e.getKind() == ENUM_CONSTANT || 262 e.getConstantValue() != null || 263 heuristicallyConstant(e) ) 264 checkAllCaps(e); // includes enum constants 265 else 266 checkCamelCase(e, false); 267 } 268 // A call to super can be elided with the current language definition. 269 // super.visitVariable(e, p); 270 return null; 271 } 272 273 /** 274 * Check the name of a type parameter. 275 */ 276 @Override 277 public Void visitTypeParameter(TypeParameterElement e, Void p) { 278 checkAllCaps(e); 279 // A call to super can be elided with the current language definition. 280 // super.visitTypeParameter(e, p); 281 return null; 282 } 283 284 /** 285 * Check the name of a package. 286 */ 287 @Override 288 public Void visitPackage(PackageElement e, Void p) { 289 /* 290 * Implementing the checks of package names is left 291 * as an exercise for the reader, see JLS section 292 * 7.7 for conventions. 293 */ 294 295 // Whether or not this method should call 296 // super.visitPackage, to visit the packages enclosed 297 // elements, is a design decision based on what a 298 // PackageElemement is used to mean in this context. 299 // A PackageElement can represent a whole package, so 300 // it can provide a concise way to indicate many 301 // user-defined types should be visited. However, a 302 // PackageElement can also represent a 303 // package-info.java file, as would be in the case if 304 // the PackageElement came from 305 // RoundEnvironment.getRootElements. In that case, 306 // the package-info file and other files in that 307 // package could be passed in. Therefore, without 308 // further checks, types in a package could be visited 309 // more than once if a package's elements were visited 310 // too. 311 return null; 312 } 313 314 @Override 315 public Void visitUnknown(Element e, Void p) { 316 // This method will be called if a kind of element 317 // added after JDK 7 is visited. Since as of this 318 // writing the conventions for such constructs aren't 319 // known, issue a warning. 320 messager.printMessage(WARNING, 321 "Unknown kind of element, " + e.getKind() + 322 ", no name checking performed.", e); 323 return null; 324 } 325 326 // All the name checking methods assume the examined names 327 // are syntactically well-formed identifiers. 328 329 /** 330 * Return {@code true} if this variable is a field named 331 * "serialVersionUID"; false otherwise. A true 332 * serialVersionUID of a class has type {@code long} and 333 * is static and final. 334 * 335 * <p>To check that a Serializable class defines a proper 336 * serialVersionUID, run javac with -Xlint:serial. 337 * 338 * @return true if this variable is a serialVersionUID field and false otherwise 339 */ 340 private boolean checkForSerial(VariableElement e) { 341 // If a field is named "serialVersionUID" ... 342 if (e.getKind() == FIELD && 343 e.getSimpleName().contentEquals("serialVersionUID")) { 344 // ... issue a warning if it does not act as a serialVersionUID 345 if (!(e.getModifiers().containsAll(EnumSet.of(STATIC, FINAL)) && 346 typeUtils.isSameType(e.asType(), typeUtils.getPrimitiveType(LONG)) && 347 e.getEnclosingElement().getKind() == CLASS )) // could check that class implements Serializable 348 messager.printMessage(WARNING, 349 "Field named ``serialVersionUID'' is not acting as such.", e); 350 return true; 351 } 352 return false; 353 } 354 355 /** 356 * Using heuristics, return {@code true} is the variable 357 * should follow the naming conventions for constants and 358 * {@code false} otherwise. For example, the public 359 * static final fields ZERO, ONE, and TEN in 360 * java.math.BigDecimal are logically constants (and named 361 * as constants) even though BigDecimal values are not 362 * regarded as constants by the language specification. 363 * However, some final fields may not act as constants 364 * since the field may be a reference to a mutable object. 365 * 366 * <p> These heuristics could be tweaked to provide better 367 * fidelity. 368 * 369 * @return true if the current heuristics regard the 370 * variable as a constant and false otherwise. 371 */ 372 private boolean heuristicallyConstant(VariableElement e) { 373 // Fields declared in interfaces are logically 374 // constants, JLSv3 section 9.3. 375 if (e.getEnclosingElement().getKind() == INTERFACE) 376 return true; 377 else if (e.getKind() == FIELD && 378 e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL))) 379 return true; 380 else { 381 // A parameter declared final should not be named like 382 // a constant, neither should exception parameters. 383 return false; 384 } 385 } 386 387 /** 388 * Print a warning if an element's simple name is not in 389 * camel case. If there are two adjacent uppercase 390 * characters, the name is considered to violate the 391 * camel case naming convention. 392 * 393 * @param e the element whose name will be checked 394 * @param initialCaps whether or not the first character should be uppercase 395 */ 396 private void checkCamelCase(Element e, boolean initialCaps) { 397 String name = e.getSimpleName().toString(); 398 boolean previousUpper = false; 399 boolean conventional = true; 400 int firstCodePoint = name.codePointAt(0); 401 402 if (Character.isUpperCase(firstCodePoint)) { 403 previousUpper = true; 404 if (!initialCaps) { 405 messager.printMessage(WARNING, 406 "Name, ``" + name + "'', should start in lowercase.", e); 407 return; 408 } 409 } else if (Character.isLowerCase(firstCodePoint)) { 410 if (initialCaps) { 411 messager.printMessage(WARNING, 412 "Name, ``" + name + "'', should start in uppercase.", e); 413 return; 414 } 415 } else // underscore, etc. 416 conventional = false; 417 418 if (conventional) { 419 int cp = firstCodePoint; 420 for (int i = Character.charCount(cp); 421 i < name.length(); 422 i += Character.charCount(cp)) { 423 cp = name.codePointAt(i); 424 if (Character.isUpperCase(cp)){ 425 if (previousUpper) { 426 conventional = false; 427 break; 428 } 429 previousUpper = true; 430 } else 431 previousUpper = false; 432 } 433 } 434 435 if (!conventional) 436 messager.printMessage(WARNING, 437 "Name, ``" + name + "'', should be in camel case.", e); 438 } 439 440 /** 441 * Print a warning if the element's name is not a sequence 442 * of uppercase letters separated by underscores ("_"). 443 * 444 * @param e the element whose name will be checked 445 */ 446 private void checkAllCaps(Element e) { 447 String name = e.getSimpleName().toString(); 448 if (e.getKind() == TYPE_PARAMETER) { // Should be one character 449 if (name.codePointCount(0, name.length()) > 1 || 450 // Assume names are non-empty 451 !Character.isUpperCase(name.codePointAt(0))) 452 messager.printMessage(WARNING, 453 "A type variable's name,``" + name + 454 "'', should be a single uppercace character.", 455 e); 456 } else { 457 boolean conventional = true; 458 int firstCodePoint = name.codePointAt(0); 459 460 // Starting with an underscore is not conventional 461 if (!Character.isUpperCase(firstCodePoint)) 462 conventional = false; 463 else { 464 // Was the previous character an underscore? 465 boolean previousUnderscore = false; 466 int cp = firstCodePoint; 467 for (int i = Character.charCount(cp); 468 i < name.length(); 469 i += Character.charCount(cp)) { 470 cp = name.codePointAt(i); 471 if (cp == (int) '_') { 472 if (previousUnderscore) { 473 conventional = false; 474 break; 475 } 476 previousUnderscore = true; 477 } else { 478 previousUnderscore = false; 479 if (!Character.isUpperCase(cp) && !Character.isDigit(cp) ) { 480 conventional = false; 481 break; 482 } 483 } 484 } 485 } 486 487 if (!conventional) 488 messager.printMessage(WARNING, 489 "A constant's name, ``" + name + "'', should be ALL_CAPS.", 490 e); 491 } 492 } 493 494 } 495 } 496 } 497 498 /** 499 * Lots of bad names. Don't write code like this! 500 */ 501 class BADLY_NAMED_CODE { 502 enum colors { 503 red, 504 blue, 505 green; 506 } 507 508 // Don't start the name of a constant with an underscore 509 static final int _FORTY_TWO = 42; 510 511 // Non-constants shouldn't use ALL_CAPS 512 public static int NOT_A_CONSTANT = _FORTY_TWO; 513 514 // *Not* a serialVersionUID 515 private static final int serialVersionUID = _FORTY_TWO; 516 517 // Not a constructor 518 protected void BADLY_NAMED_CODE() { 519 return; 520 } 521 522 public void NOTcamelCASEmethodNAME() { 523 return; 524 } 525 }