java 命名代码检查-注解处理器

命名代码检查

  根据 <Java 语言规范( 第 3 版 ) > 中第6.8节的要求, Java 程序命名应当符合下列格式的书写规范:

  1. 类 ( 或接口 ) : 符合驼式命名法, 首字母大写.
  2. 方法 : 符合驼式命名法,首字母小写
  3. 字段 :
    1. 类或实例变量 : 符合驼式命名法 , 首字母小写
    2. 常量 : 要求全部有大写字母或下划线构成, 并且第一个字符不能是下划线.

  要通过注解处理器的API 实现一个编译器插件 , 首先需要了解这组 API 的基本知识.我们实现注解处理器的代码需要继承抽象类 javax.annotation.processing.AbstractProcessor ,这个抽象类中只有一个必须覆盖的abstract方法 : "process()"  它是 javac 编译器在执行注解处理器代码时需要调用的过程 , 我们可以从这个方法的第一个参数 ‘annotations‘ 中获取到此注解处理器所要求处理的注解集合,从第二个参数 ‘roundEnv‘ 中访问到当前这个 round 中得语法树节点, 每个语法树节点在这里表示为一个 Element  , 在 JDK1.6 新增的 javax.lang.model 包中定义了16类 Element , 包括了 Java 代码中最常用的元素,

  如 : ‘包( PACKAGE ) , 枚举 ( ENUM )  , 类 ( CLASS ) , 注解 (ANNOTATION_TYPE) , 接口 (INTERFACE ) , 枚举值 ( ENUM_VARIABLE ) ,字段 ( FIELD ) , 参数 ( PARAMETER ) , 本地变量 ( LOCAL_VARIABLE ) , 异常 ( EXCEPTOIN_PARAMETER )  ,方法 ( METHOD ) , 构造函数 (CONSTRUCTOR ) 静态块语句 ( STATIC_INIT ,即 static {} 块 ) ,实例语句块 (INSTANCE_INIT, 即{}块) , 参数化类型 ( TYPE_PARAMETER ) , 和未定义的其他语法树节点 ( OTHER ) ;

  除了 process () 方法的传入参数之外, 还有一个很常用的实例变量‘ processingEnv‘ ,它是 AbstractProcessor 中的一个 protected 变量, 在注解处理器代码可以直接访问到它,它代表了注解处理器框架提供了一个上下文环境,眼创建新的代码,向编译器输出信息,获取其他工具类等都需要用到这个实例变量,

 注解处理器除了 process 方法及其参数外,还有两个可以配合使用的 Annotations :@SupportedAnnotationTypes 和@SupportedSourceVersion 前者代表这个注解处理器对哪些注解感兴趣,可以使用 ‘*‘ 作为通配符表示对所有的感兴趣.后者指出这个注解处理器可以处理哪些版本的 Java 代码.

  每个注解处理器在运行的时候都是单例的.如果不需要改变生成语法树的内容, process() 方法就可以返回一个值为 false 的布尔值,通知编译器这个 round 中得代码未发生变化, 无需构造新的 JavaCompiler 实例,在这里只对程序命名进行检查 , 不需要改变语法树的内容, 因此process() 方法的返回值都是 false

  实例如下:

AbstractProcessor
package annotation.processing;

import java.util.EnumSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementScanner6;
import javax.tools.Diagnostic.Kind;

// 使用*表示支持所有的Annotations
/**
 * 以下代码出自 《深入理解Java虚拟机:JVM高级特性与最佳实践》
 *
 */
@SupportedAnnotationTypes("*")
// 表示只对 JDK 1.6 的 Java 源码感兴趣
@SupportedSourceVersion(value = SourceVersion.RELEASE_6)
public class NameCheckProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.nameCheck = new NameCheck(processingEnv);
    }

    private NameCheck nameCheck;

    @Override
    public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            for (Element element : roundEnv.getRootElements()) {
                nameCheck.check(element);
            }
        }
        return false;
    }

    /**
     * 程序名称规范的编译器插件 如果程序命名不合规范,将会输出一个编译器的Warning信息
     *
     * @author kevin
     *
     */
    static class NameCheck {
        Messager messager = null;
        public NameCheckScanner nameCheckScanner;

        private NameCheck(ProcessingEnvironment processingEnv) {
            messager = processingEnv.getMessager();
            nameCheckScanner = new NameCheckScanner(processingEnv);
        }

        /**
         * 对Java程序明明进行检查,根据《Java语言规范(第3版)》6.8节的要求,Java程序命名应当符合下列格式:
         * <ul>
         * <li>类或接口:符合驼式命名法,首字母大写。
         * <li>方法:符合驼式命名法,首字母小写。
         * <li>字段:
         * <ul>
         * <li>类,实例变量:符合驼式命名法,首字母小写。
         * <li>常量:要求全部大写
         * </ul>
         * </ul>
         *
         * @param element
         */
        public void check(Element element) {
            nameCheckScanner.scan(element);
        }

        /**
         * 名称检查器实现类,继承了1.6中新提供的ElementScanner6<br>
         * 将会以Visitor模式访问抽象语法数中得元素
         *
         *
         */
        static class NameCheckScanner extends ElementScanner6<Void, Void> {
            Messager messager = null;

            public NameCheckScanner(ProcessingEnvironment processingEnv) {
                this.messager = processingEnv.getMessager();
            }

            /**
             * 此方法用于检查Java类
             */
            @Override
            public Void visitType(TypeElement e, Void p) {
                scan(e.getTypeParameters(), p);
                checkCamelCase(e, true);
                super.visitType(e, p);
                return null;
            }

            /**
             * 检查传入的Element是否符合驼式命名法,如果不符合,则输出警告信息
             *
             * @param e
             * @param b
             */
            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(Kind.WARNING, "名称:" + name
                                + " 应当以小写字母开头", e);
                        return;
                    }
                } else if (Character.isLowerCase(firstCodePoint)) {
                    if (initialCaps) {
                        messager.printMessage(Kind.WARNING, "名称:" + name
                                + " 应当以大写字母开头", e);
                        return;
                    }
                } else {
                    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(Kind.WARNING, "名称:" + name
                            + "应当符合驼式命名法(Camel Case Names)", e);
                }
            }

            /**
             * 检查方法命名是否合法
             */
            @Override
            public Void visitExecutable(ExecutableElement e, Void p) {
                if (e.getKind() == ElementKind.METHOD) {
                    Name name = e.getSimpleName();
                    if (name.contentEquals(e.getEnclosingElement()
                            .getSimpleName())) {
                        messager.printMessage(Kind.WARNING, "一个普通方法:" + name
                                + " 不应当与类名重复,避免与构造函数产生混淆", e);
                        checkCamelCase(e, false);
                    }
                }
                super.visitExecutable(e, p);
                return null;
            }

            /**
             * 检查变量是否合法
             */
            @Override
            public Void visitVariable(VariableElement e, Void p) {
                /* 如果这个Variable是枚举或常量,则按大写命名检查,否则按照驼式命名法规则检查 */
                if (e.getKind() == ElementKind.ENUM_CONSTANT
                        || e.getConstantValue() != null
                        || heuristicallyConstant(e)) {
                    checkAllCaps(e);
                } else {
                    checkCamelCase(e, false);
                }
                super.visitVariable(e, p);
                return null;
            }

            /**
             * 大写命名检查,要求第一个字符必须是大写的英文字母,其余部分可以下划线或大写字母
             *
             * @param e
             */
            private void checkAllCaps(VariableElement e) {
                String name = e.getSimpleName().toString();
                boolean conventional = true;
                int firstCodePoint = name.codePointAt(0);
                if (!Character.isUpperCase(firstCodePoint)) {
                    conventional = false;
                } else {
                    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(Kind.WARNING, "常量:" + name
                            + " 应该全部以大写字母" + "或下划线命名,并且以字符开头", e);
                }
            }

            /**
             * 判断一个变量是否是常量
             *
             * @param e
             * @return
             */
            private boolean heuristicallyConstant(VariableElement e) {
                if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) {
                    return true;
                } else if (e.getKind() == ElementKind.FIELD
                        && e.getModifiers()
                                .containsAll(
                                        EnumSet.of(
                                                javax.lang.model.element.Modifier.FINAL,
                                                javax.lang.model.element.Modifier.STATIC,
                                                javax.lang.model.element.Modifier.PUBLIC))) {
                    return true;
                }
                return false;
            }
        }
    }
}

测试代码:

BADLY_NAMED_CODE
package annotation.processing;
public class BADLY_NAMED_CODE {

    enum Colors {
        Red, Blue, Green;
    }
    static final int FORTY_TWO =42;

    public static int NOT_A_CONSTANT = FORTY_TWO;

    protected void Badly_Named_Code() {
        return;
    }

    public void NOTcamelCASEmethodNAME() {
        return;
    }

}

执行过程如下:

  

bogon:Desktop mjorcen$ javac annotation/processing/BADLY_NAMED_CODE.java
bogon:Desktop mjorcen$ javac annotation/processing/NameCheckProcessor.java
bogon:Desktop mjorcen$ javac -processor annotation.processing.NameCheckProcessor annotation/processing/BADLY_NAMED_CODE.java
警告: 来自注释处理程序 ‘annotation.processing.NameCheckProcessor‘ 的受支持 source 版本 ‘RELEASE_6‘ 低于 -source ‘1.7‘
annotation/processing/BADLY_NAMED_CODE.java:2: 警告: 名称:BADLY_NAMED_CODE应当符合驼式命名法(Camel Case Names)
public class BADLY_NAMED_CODE {
       ^
annotation/processing/BADLY_NAMED_CODE.java:5: 警告: 常量:Red 应该全部以大写字母或下划线命名,并且以字符开头
        Red, Blue, Green;
        ^
annotation/processing/BADLY_NAMED_CODE.java:5: 警告: 常量:Blue 应该全部以大写字母或下划线命名,并且以字符开头
        Red, Blue, Green;
             ^
annotation/processing/BADLY_NAMED_CODE.java:5: 警告: 常量:Green 应该全部以大写字母或下划线命名,并且以字符开头
        Red, Blue, Green;
                   ^
annotation/processing/BADLY_NAMED_CODE.java:9: 警告: 名称:NOT_A_CONSTANT 应当以小写字母开头
    public static int NOT_A_CONSTANT = FORTY_TWO;
                      ^
6 个警告

....

以上内容出自:

《深入理解Java虚拟机:JVM高级特性与最佳实践》

  

时间: 2024-10-27 12:31:37

java 命名代码检查-注解处理器的相关文章

PMD 5.7.0 发布,Java 程序代码检查工具

PMD 5.7.0 发布了.PMD 是一款采用 BSD 协议发布的 Java 程序代码检查工具.该工具可以做到检查 Java 代码中是否含有未使用的变量.是否含有空的抓取块.是否含有不必要的对象等.该软件功能强大,扫描效率高,是 Java 程序员 debug 的好帮手.  值得注意的变化有:  Modified Rules   java-design 规则集中的 "FieldDeclarationsShouldBeAtStartOfClass" 规则具有一个新的 ignoreInter

最近开始研究PMD(一款采用BSD协议发布的Java程序代码检查工具)

PMD是一款采用BSD协议发布的Java程序代码检查工具.该工具可以做到检查Java代码中是否含有未使用的变量.是否含有空的抓取块.是否含有不必要的对象等.该软件功能强大,扫描效率高,是Java程序员debug的好帮手. PMD支持的编辑器包括: JDeveloper.Eclipse.JEdit.JBuilder.BlueJ.CodeGuide.NetBeans/Sun Java Studio Enterprise/Creator.IntelliJ IDEA.TextPad.Maven.Ant,

FindBugs —— Java 静态代码检查

在使用 Jenkins 构建 Java Web 项目时候,有一项叫做静态代码检查,是用内置的 findBugs 插件,对程序源代码进行检查,以分析程序行为的技术,应用于程序的正确性检查. 安全缺陷检测.程序优化等,特点就是不执行程序.它有助于在项目早期发现以下问题:变量声明了但未使用.变量类型不匹配.变量在使用前未定义.不可达代码.死循环.数组越界.内存泄漏等.分为以下几种类型: 一.Bad Practice (糟糕的写法) 二.Correctness (不太的当) 三.Experimental

PMD-Java 代码检查工具对 error 和 warning 的配置

PMD是一款优秀的Java程序代码检查工具.该它可以检查Java代码中是否含有未使用的变量.是否含有空的抓取块.是否含有不必要的对象等. 但在使用过程中,你会项目中发现存在大量的 PMD 插件报出的 error 和 warning,尽管 PMD 鼓励程序员去改进它们并写出干净的代码,但也产生了很多让人烦躁的感觉.更重要的是,你可能忽略了 Java 代码中真正的错误和问题.下面我们就通过在 Eclipse 中配置只有在选中的资源中才去对代码检查. 第一步,在 Eclipse 中选择Problems

Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)

如果没有用来读取注解的工具,那注解将基本没有任何作用,它也不会比注释更有用.读取注解的工具叫作注解处理器.Java提供了两种方式来处理注解:第一种是利用运行时反射机制:另一种是使用Java提供的API来处理编译期的注解. 反射机制方式的注解处理器 仅当定义的注解的@Retention为RUNTIME时,才能够通过运行时的反射机制来处理注解.下面结合例子来说明这种方式的处理方法. Java中的反射API(如java.lang.Class.java.lang.reflect.Field等)都实现了接

Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)

注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理.在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译. Java5中提供了apt工具来进行编译期的注解处理.apt是命令行工具,与之配套的是一套描述"程序在编译时刻的静态结构"的API:Mirror API(com.sun.mirror.*).通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供自定义的处理逻辑.具体的处理工具交给apt来处理.编写注解处理器的核心是两个

深入理解Java:注解(Annotation)--注解处理器

深入理解Java:注解(Annotation)--注解处理器 如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了.使用注解的过程中,很重要的一部分就是创建于使用注解处理器.Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器. 注解处理器类库(java.lang.reflect.AnnotatedElement): Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口.除此之外,Java在java.l

Java注解处理器

这几天看公司一个中间件对异步的支持的新特性中,它使用注解处理器来自动生成异步接口.就看了下如何使用注解及编译期注解处理器,一下是些学习笔记吧! 此处说的Java注解及注解处理器,不是运行期注解及使用Class对象和反射来处理的处理器!而是编译期处理的源文件注解(RetentionPolicy.SOURCE);其注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation).你可以对自定义注解,并注册相应的注解处理器. 用途 编译

[3] 注解(Annotation)-- 深入理解Java:注解(Annotation)--注解处理器

转载 http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html 深入理解Java:注解(Annotation)--注解处理器 如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了.使用注解的过程中,很重要的一部分就是创建于使用注解处理器.Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器. 注解处理器类库(java.lang.reflect.AnnotatedElement): Java使用