1 /* 2 * Copyright (c) 2006, 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_7. 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 JLSv3 section 6.8: 156 * 157 * <ul> 158 * <li> Classes and interfaces: camel case, first letter is uppercase 159 * <li> Methods: camel case, first letter is lowercase 160 * <li> Type variables: one uppercase letter 161 * <li> Fields 162 * <ul> 163 * <li> non-final: camel case, initial lowercase 164 * <li> constant: uppercase separated by underscores 165 * </ul> 166 * <li> Packages: checks left as exercise for the reader, see JLSv3 section 7.7 167 * </ul> 168 */ 169 private static class NameChecker { 170 private final Messager messager; 171 private final Types typeUtils; 172 173 NameCheckScanner nameCheckScanner = new NameCheckScanner(); 174 175 NameChecker(ProcessingEnvironment processsingEnv) { 176 this.messager = processsingEnv.getMessager(); 177 this.typeUtils = processsingEnv.getTypeUtils(); 178 } 179 180 /** 181 * If the name of the argument or its enclosed elements 182 * violates the naming conventions, report a warning. 183 */ 184 public void checkNames(Element element) { 185 // Implement name checks with a visitor, but expose that 186 // functionality through this method instead. 187 nameCheckScanner.scan(element); 188 } 189 190 /** 191 * Visitor to implement name checks. 192 */ 193 private class NameCheckScanner extends ElementScanner7<Void, Void> { 194 // The visitor could be enhanced to return true/false if 195 // there were warnings reported or a count of the number 196 // of warnings. This could be facilitated by using 197 // Boolean or Integer instead of Void for the actual type 198 // arguments. In more detail, one way to tally the number 199 // of warnings would be for each method to return the sum 200 // of the warnings it and the methods it called issued, a 201 // bottom-up computation. In that case, the first type 202 // argument would be Integer and the second type argument 203 // would still be Void. Alternatively, the current count 204 // could be passed along in Integer parameter p and each 205 // method could return the Integer sum of p and the 206 // warnings the method issued. Some computations are more 207 // naturally expressed in one form instead of the other. 208 // If greater control is needed over traversal order, a 209 // SimpleElementVisitor can be extended instead of an 210 // ElementScanner. 211 212 /** 213 * Check the name of a type and its enclosed elements and 214 * type parameters. 215 */ 216 @Override 217 public Void visitType(TypeElement e, Void p) { 218 scan(e.getTypeParameters(), p); // Check the names of any type parameters 219 checkCamelCase(e, true); // Check the name of the class or interface 220 super.visitType(e, p); // Check the names of any enclosed elements 221 return null; 222 } 223 224 /** 225 * Check the name of an executable (method, constructor, 226 * etc.) and its type parameters. 227 */ 228 @Override 229 public Void visitExecutable(ExecutableElement e, Void p) { 230 scan(e.getTypeParameters(), p); // Check the names of any type parameters 231 232 // Check the name of the executable 233 if (e.getKind() == METHOD) { 234 // Make sure that a method does not have the same 235 // name as its class or interface. 236 Name name = e.getSimpleName(); 237 if (name.contentEquals(e.getEnclosingElement().getSimpleName())) 238 messager.printMessage(WARNING, 239 "A method should not have the same name as its enclosing type, ``" + 240 name + "''." , e); 241 checkCamelCase(e, false); 242 } 243 // else constructors and initializers don't have user-defined names 244 245 // At this point, could use the Tree API, 246 // com.sun.source, to examine the names of entities 247 // inside a method. 248 super.visitExecutable(e, p); 249 return null; 250 } 251 252 /** 253 * Check the name of a field, parameter, etc. 254 */ 255 @Override 256 public Void visitVariable(VariableElement e, Void p) { 257 if (!checkForSerial(e)) { // serialVersionUID checks 258 // Is the variable a constant? 259 if (e.getKind() == ENUM_CONSTANT || 260 e.getConstantValue() != null || 261 heuristicallyConstant(e) ) 262 checkAllCaps(e); // includes enum constants 263 else 264 checkCamelCase(e, false); 265 } 266 // A call to super can be elided with the current language definition. 267 // super.visitVariable(e, p); 268 return null; 269 } 270 271 /** 272 * Check the name of a type parameter. 273 */ 274 @Override 275 public Void visitTypeParameter(TypeParameterElement e, Void p) { 276 checkAllCaps(e); 277 // A call to super can be elided with the current language definition. 278 // super.visitTypeParameter(e, p); 279 return null; 280 } 281 282 /** 283 * Check the name of a package. 284 */ 285 @Override 286 public Void visitPackage(PackageElement e, Void p) { 287 /* 288 * Implementing the checks of package names is left 289 * as an exercise for the reader, see JLSv3 section 290 * 7.7 for conventions. 291 */ 292 293 // Whether or not this method should call 294 // super.visitPackage, to visit the packages enclosed 295 // elements, is a design decision based on what a 296 // PackageElemement is used to mean in this context. 297 // A PackageElement can represent a whole package, so 298 // it can provide a concise way to indicate many 299 // user-defined types should be visited. However, a 300 // PackageElement can also represent a 301 // package-info.java file, as would be in the case if 302 // the PackageElement came from 303 // RoundEnvironment.getRootElements. In that case, 304 // the package-info file and other files in that 305 // package could be passed in. Therefore, without 306 // further checks, types in a package could be visited 307 // more than once if a package's elements were visited 308 // too. 309 return null; 310 } 311 312 @Override 313 public Void visitUnknown(Element e, Void p) { 314 // This method will be called if a kind of element 315 // added after JDK 7 is visited. Since as of this 316 // writing the conventions for such constructs aren't 317 // known, issue a warning. 318 messager.printMessage(WARNING, 319 "Unknown kind of element, " + e.getKind() + 320 ", no name checking performed.", e); 321 return null; 322 } 323 324 // All the name checking methods assume the examined names 325 // are syntactically well-formed identifiers. 326 327 /** 328 * Return {@code true} if this variable is a field named 329 * "serialVersionUID"; false otherwise. A true 330 * serialVersionUID of a class has type {@code long} and 331 * is static and final. 332 * 333 * <p>To check that a Serializable class defines a proper 334 * serialVersionUID, run javac with -Xlint:serial. 335 * 336 * @return true if this variable is a serialVersionUID field and false otherwise 337 */ 338 private boolean checkForSerial(VariableElement e) { 339 // If a field is named "serialVersionUID" ... 340 if (e.getKind() == FIELD && 341 e.getSimpleName().contentEquals("serialVersionUID")) { 342 // ... issue a warning if it does not act as a serialVersionUID 343 if (!(e.getModifiers().containsAll(EnumSet.of(STATIC, FINAL)) && 344 typeUtils.isSameType(e.asType(), typeUtils.getPrimitiveType(LONG)) && 345 e.getEnclosingElement().getKind() == CLASS )) // could check that class implements Serializable 346 messager.printMessage(WARNING, 347 "Field named ``serialVersionUID'' is not acting as such.", e); 348 return true; 349 } 350 return false; 351 } 352 353 /** 354 * Using heuristics, return {@code true} is the variable 355 * should follow the naming conventions for constants and 356 * {@code false} otherwise. For example, the public 357 * static final fields ZERO, ONE, and TEN in 358 * java.math.BigDecimal are logically constants (and named 359 * as constants) even though BigDecimal values are not 360 * regarded as constants by the language specification. 361 * However, some final fields may not act as constants 362 * since the field may be a reference to a mutable object. 363 * 364 * <p> These heuristics could be tweaked to provide better 365 * fidelity. 366 * 367 * @return true if the current heuristics regard the 368 * variable as a constant and false otherwise. 369 */ 370 private boolean heuristicallyConstant(VariableElement e) { 371 // Fields declared in interfaces are logically 372 // constants, JLSv3 section 9.3. 373 if (e.getEnclosingElement().getKind() == INTERFACE) 374 return true; 375 else if (e.getKind() == FIELD && 376 e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL))) 377 return true; 378 else { 379 // A parameter declared final should not be named like 380 // a constant, neither should exception parameters. 381 return false; 382 } 383 } 384 385 /** 386 * Print a warning if an element's simple name is not in 387 * camel case. If there are two adjacent uppercase 388 * characters, the name is considered to violate the 389 * camel case naming convention. 390 * 391 * @param e the element whose name will be checked 392 * @param initialCaps whether or not the first character should be uppercase 393 */ 394 private void checkCamelCase(Element e, boolean initialCaps) { 395 String name = e.getSimpleName().toString(); 396 boolean previousUpper = false; 397 boolean conventional = true; 398 int firstCodePoint = name.codePointAt(0); 399 400 if (Character.isUpperCase(firstCodePoint)) { 401 previousUpper = true; 402 if (!initialCaps) { 403 messager.printMessage(WARNING, 404 "Name, ``" + name + "'', should start in lowercase.", e); 405 return; 406 } 407 } else if (Character.isLowerCase(firstCodePoint)) { 408 if (initialCaps) { 409 messager.printMessage(WARNING, 410 "Name, ``" + name + "'', should start in uppercase.", e); 411 return; 412 } 413 } else // underscore, etc. 414 conventional = false; 415 416 if (conventional) { 417 int cp = firstCodePoint; 418 for (int i = Character.charCount(cp); 419 i < name.length(); 420 i += Character.charCount(cp)) { 421 cp = name.codePointAt(i); 422 if (Character.isUpperCase(cp)){ 423 if (previousUpper) { 424 conventional = false; 425 break; 426 } 427 previousUpper = true; 428 } else 429 previousUpper = false; 430 } 431 } 432 433 if (!conventional) 434 messager.printMessage(WARNING, 435 "Name, ``" + name + "'', should be in camel case.", e); 436 } 437 438 /** 439 * Print a warning if the element's name is not a sequence 440 * of uppercase letters separated by underscores ("_"). 441 * 442 * @param e the element whose name will be checked 443 */ 444 private void checkAllCaps(Element e) { 445 String name = e.getSimpleName().toString(); 446 if (e.getKind() == TYPE_PARAMETER) { // Should be one character 447 if (name.codePointCount(0, name.length()) > 1 || 448 // Assume names are non-empty 449 !Character.isUpperCase(name.codePointAt(0))) 450 messager.printMessage(WARNING, 451 "A type variable's name,``" + name + 452 "'', should be a single uppercace character.", 453 e); 454 } else { 455 boolean conventional = true; 456 int firstCodePoint = name.codePointAt(0); 457 458 // Starting with an underscore is not conventional 459 if (!Character.isUpperCase(firstCodePoint)) 460 conventional = false; 461 else { 462 // Was the previous character an underscore? 463 boolean previousUnderscore = false; 464 int cp = firstCodePoint; 465 for (int i = Character.charCount(cp); 466 i < name.length(); 467 i += Character.charCount(cp)) { 468 cp = name.codePointAt(i); 469 if (cp == (int) '_') { 470 if (previousUnderscore) { 471 conventional = false; 472 break; 473 } 474 previousUnderscore = true; 475 } else { 476 previousUnderscore = false; 477 if (!Character.isUpperCase(cp) && !Character.isDigit(cp) ) { 478 conventional = false; 479 break; 480 } 481 } 482 } 483 } 484 485 if (!conventional) 486 messager.printMessage(WARNING, 487 "A constant's name, ``" + name + "'', should be ALL_CAPS.", 488 e); 489 } 490 } 491 492 } 493 } 494 } 495 496 /** 497 * Lots of bad names. Don't write code like this! 498 */ 499 class BADLY_NAMED_CODE { 500 enum colors { 501 red, 502 blue, 503 green; 504 } 505 506 // Don't start the name of a constant with an underscore 507 static final int _FORTY_TWO = 42; 508 509 // Non-constants shouldn't use ALL_CAPS 510 public static int NOT_A_CONSTANT = _FORTY_TWO; 511 512 // *Not* a serialVersionUID 513 private static final int serialVersionUID = _FORTY_TWO; 514 515 // Not a constructor 516 protected void BADLY_NAMED_CODE() { 517 return; 518 } 519 520 public void NOTcamelCASEmethodNAME() { 521 return; 522 } 523 }