zoukankan      html  css  js  c++  java
  • 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高级特性与最佳实践》

     将这个处理器注册到 Eclipse 上,我建立如下 META-INF 文件:

    META-INF/services/javax.annotation.processing.Processor:
    
    
    annotation.processing.NameCheckProcessor

        这里只包含了处理器实现类的类名。我不确定你是否可以在这里列出多个处理器。

        就这样。现在导出一个 jar 文件,并且在你需要用到这个处理器的工程上导入这个文件。

    第二步:建立一个使用你的处理器的工程

    In the properties for your new project go to Java Compiler -> Annotation Processing
    Check the “Enable Project Specific Settings” and make sure “Enable annotation processing” is checked. I also changed the generated source directory to a name which didn’t start with a dot so it wouldn’t be hidden in the package explorer (files or directories which start with a dot are by default filtered away in eclipse).

        在工程的属性中找到  Java Compiler -> Annotation Processing 查看 “Enable Project Specific Settings” 确认 “Enable annotation processing” 被选中。为了不让他在包浏览器中隐藏,我还修改了  generated source directory ,去掉了开始的点(Eclipse 会将文件名以点开始的文件或文件夹过滤掉)。

     然后,转到  Java Compiler -> Annotation Processing -> Factory Path 你可以在这里导入处理器的 jar 文件。不可以使用工程引用。

        点击 “Advanced” 按钮,会显示一个对话框,列出了  META-INF/services/javax.annotation.processing.Processor 文件中的内容。选择它并按OK。

    第三步:Build!

        完成了。这是在我的工程里显示的样子:

      

  • 相关阅读:
    半年时间
    deep learning书的阅读
    wgan pytorch,pyvision, py-faster-rcnn等的安装使用
    caffe新版本的各种软件
    你会允许自己家孩子一直不停跟人要东西吗?
    sup inf max min
    leangoo大讲堂—北京站
    使用Leangoo玩转故事地图
    Leangoo:用敏捷开发管理思维做团队协作的SaaS软件
    张江男的逆袭,我如何使用leangoo提升团队效率
  • 原文地址:https://www.cnblogs.com/mjorcen/p/4296475.html
Copyright © 2011-2022 走看看