插入式註解處理器

deyang發表於2024-12-01

實戰:插入式註解處理器

目標:檢查是否符合駝式命名法

詳細描述檢視【深入理解Java虛擬機器:JVM高階特性與最佳實踐(第3版)】10.4 實戰:插入式註解處理器(510頁)

1.程式碼部分

1.註解處理器

NameCheckProcessor

package org.anno;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;

// 可以用"*"表示支援所有Annotations
@SupportedAnnotationTypes("*")
// 只支援JDK 6的Java程式碼
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NameCheckProcessor extends AbstractProcessor {
    private NameChecker nameChecker;

    /**
     * 初始化名稱檢查外掛
     */
    @Override
    public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        nameChecker = new NameChecker(processingEnv);
    }

    /**
     * 對輸入的語法樹的各個節點進行名稱檢查
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            for (Element element : roundEnv.getRootElements())
                nameChecker.checkNames(element);
        }
        return false;

    }

}
2.命名檢查器

NameChecker

package org.anno;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementScanner8;

import java.util.EnumSet;

import static javax.lang.model.element.ElementKind.*;
import static javax.lang.model.element.Modifier.*;
import static javax.tools.Diagnostic.Kind.WARNING;

/**
 * 程式名稱規範的編譯器外掛:<br>
 * 如果程式命名不合規範,將會輸出一個編譯器的WARNING資訊
 */
public class NameChecker {
    private final Messager messager;
    NameCheckScanner nameCheckScanner = new NameCheckScanner();

    NameChecker(ProcessingEnvironment processsingEnv) {
        this.messager = processsingEnv.getMessager();
    }

    /**
     *
     * 對Java程式命名進行檢查,根據《Java語言規範》第三版第6.8節的要求,Java程式命名應當符合下列格式:
     *
     * <ul>
     * <li>類或介面:符合駝式命名法,首字母大寫。
     * <li>方法:符合駝式命名法,首字母小寫。
     * <li>欄位:
     * <ul>
     * <li>類、例項變數: 符合駝式命名法,首字母小寫。
     * <li>常量: 要求全部大寫。
     * </ul>
     * </ul>
     */
    public void checkNames(Element element) {
        nameCheckScanner.scan(element);
    }

    /**
     * 名稱檢查器實現類,繼承了JDK 6中新提供的ElementScanner6<br>
     * 將會以Visitor模式訪問抽象語法樹中的元素
     */
    private class NameCheckScanner extends ElementScanner8<Void, Void> {
        /**
         * 此方法用於檢查Java類
         */
        @Override
        public Void visitType(TypeElement e, Void p) {
            scan(e.getTypeParameters(), p);
            checkCamelCase(e, true);
            super.visitType(e, p);
            return null;
        }

        /**
         * 檢查方法命名是否合法
         */
        @Override
        public Void visitExecutable(ExecutableElement e, Void p) {
            if (e.getKind() == METHOD) {
                Name name = e.getSimpleName();
                if (name.contentEquals(e.getEnclosingElement().getSimpleName()))
                    messager.printMessage(WARNING, "一個普通方法 " + name + "不應當與類名重複,避免與建構函式產生混淆");
                checkCamelCase(e, false);
            }
            super.visitExecutable(e, p);
            return null;
        }

        /**
         * 檢查變數命名是否合法
         */
        @Override
        public Void visitVariable(VariableElement e, Void p) {
            // 如果這個Variable是列舉或常量,則按大寫命名檢查,否則按照駝式命名法規則檢查
            if (e.getKind() == ENUM_CONSTANT || e.getConstantValue() != null || heuristicallyConstant(e))
                checkAllCaps(e);
            else
                checkCamelCase(e, false);
            return null;
        }

        /**
         * 判斷一個變數是否是常量
         */
        private boolean heuristicallyConstant(VariableElement e) {
            if (e.getEnclosingElement().getKind() == INTERFACE)
                return true;
            else if (e.getKind() == FIELD && e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL)))
                return true;
            else {
                return false;
            }
        }

        /**
         * 檢查傳入的Element是否符合駝式命名法,如果不符合,則輸出警告資訊
         */
        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 + "應當以小寫字母開頭", e);
                    return;
                }
            } else if (Character.isLowerCase(firstCodePoint)) {
                if (initialCaps) {
                    messager.printMessage(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(WARNING, "名稱“" + name + "應當符合駝式命名法(Camel Case Names)", e);
        }

        /**
         * 大寫命名檢查,要求第一個字母必須是大寫的英文字母,其餘部分可以是下劃線或大寫字母
         */
        private void checkAllCaps(Element 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(WARNING, "常量" + name + "應當全部以大寫字母或下劃線命名,並且以字母開頭", e);
        }
    }
}
3.不規範命名的程式碼
package org.anno;

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;
    }
}

2.執行與測試

待編譯的類目錄:D:\ToolOfProductionData\IDEAFile\DemoTest\src\main\java\org\anno

1.執行測試

注意:因為有包名的緣故,如果直接在包下執行javac或者java 這樣是找不到class檔案的。

解決:去對應專案的java目錄下執行命令 例如:cd D:\ToolOfProductionData\IDEAFile\DemoTest\src\main\java

d:
cd D:\ToolOfProductionData\IDEAFile\DemoTest\src\main\java
javac org/anno/NameChecker.java
有編碼問題是則使用:javac -encoding utf-8 org/anno/NameChecker.java
javac org/anno/NameCheckProcessor.java

javac -processor org.anno.NameCheckProcessor org\anno\BADLY_NAMED_CODE.java
有編碼問題是則使用:
javac -processor org.anno.NameCheckProcessor -encoding utf-8 org\anno\BADLY_NAMED_CODE.java

執行效果圖

注意:javac編譯問題

因為有包名的緣故,如果直接在包下執行javac或者java 這樣是找不到 class檔案的。

解決:去對應專案的java目錄下執行命令 例如:D:\ToolOfProductionData\IDEAFile\DemoTest\src\main\java

詳細請看:javac編譯問題_找不到註釋處理程式-CSDN部落格

編譯條件

待編譯的類目錄:D:\ToolOfProductionData\IDEAFile\DemoTest\src\main\java\org\anno

1.中文亂碼

javac NameChecker.java

改成:

javac -encoding utf-8 NameChecker.java

2.引用其他類的時候,報找不到符號

(1)javac -encoding utf-8 *.java

(2) 換到src\main\java目錄下:cd C:\work\workspace\my\my-utils\src\main\java

javac -encoding utf-8 com/my/jvm/processor/NameCheckProcessor.java

3.javac -processor報錯

(1)java.lang.NoClassDefFoundError: NameCheckProcessor (wrong name: com/my/jvm/processor/NameCheckProcessor)

javac -processor NameCheckProcessor -encoding utf-8 .\BADLY_NAMED_CODE.java

(2)錯誤: 找不到註釋處理程式 'com.my.jvm.processor.NameCheckProcessor'

javac -processor com.my.jvm.processor.NameCheckProcessor -encoding utf-8 .\BADLY_NAMED_CODE.java

因為有包名的緣故,如果直接在包下執行javac或者java 這樣是找不到 class檔案的。

解決:

cd  C:\work\workspace\my\my-utils\src\main\java

javac  -processor com.my.jvm.processor.NameCheckProcessor  -encoding utf-8  com\my\jvm\processor\BADLY_NAMED_CODE.java

相關文章