通过注解编实现一个编译器插件(java)

学习参考链接(注意:链接里面的检查方法名首字母非大写出错,修改可以参考我的代码)

编译器代码:

package main.java.com.process2;

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_8)
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 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程序命名应当符合下列格式:
		 * 
    *
  • 类或接口:符合驼式命名法,首字母大写。 *
  • 方法:符合驼式命名法,首字母小写。 *
  • 字段: *
      *
    • 类,实例变量:符合驼式命名法,首字母小写。 *
    • 常量:要求全部大写 *
    *
* * @param element */ public void check(Element element) { nameCheckScanner.scan(element); } /** * 名称检查器实现类,继承了1.6中新提供的ElementScanner6
* 将会以Visitor模式访问抽象语法数中得元素 * * */ static class NameCheckScanner extends ElementScanner6 { 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 initialCaps true:大写,false:小写 */ 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; } } } }
测试代码

package main.java.com.process2;

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


javac 命令测试 :
1.ctr+R 打开cmd
2.cd切换到项目的src目录下(一定要在src目录,因为全类名是src下的相对路劲),不懂win命令行可以参考win常用命令
3.切换到scr后输入javac 查看javac命令
我的操作:

通过注解编实现一个编译器插件(java)_第1张图片


3.编译注解器
输入: javac + 空格+编译器类的所在的相对路劲(包括包名,比如我的:main/java/com/prpcess2/NameCheckProcessor.java)
我的操作:
编译前的类所在目录包含的文件:

通过注解编实现一个编译器插件(java)_第2张图片


编译操作:

通过注解编实现一个编译器插件(java)_第3张图片

编译后结果:增加了.class文件

通过注解编实现一个编译器插件(java)_第4张图片


4.执行注解器检查测试类:
输入: javac -processor +空格 + 注解器名(包括包名,用“.”做分割符)+空格+被测试类的所在的相对路劲
我的操作:我的出现7个警告,如果你的jdk的版本和类里面的版本不匹配,还会抛出一个警告

通过注解编实现一个编译器插件(java)_第5张图片

eclipse配置该注解器:
1.在项目中建立 META-INF/services/javax.annotation.processing.Processor文件,建立后的项目目录结构如下:
通过注解编实现一个编译器插件(java)_第6张图片


2.在javax.annotation.processing.Processor文件中加入注解器名(包含src目录下的包名)

通过注解编实现一个编译器插件(java)_第7张图片


3.导出项目jar包(导出过程会有警告,可以忽略,记住导出jar包的位置)

4.对需要检查的java类的项目导入刚刚导出的jar包,操作如下:
a.项目右键点击Properties弹出项目属性窗口,然后点击Java Compiler——>Annotation Processing 勾选Enable project specific settings

通过注解编实现一个编译器插件(java)_第8张图片

b.点击Annotation Processing下的 Factory Path 并勾选Enable project specific settings,然后点击Add Externak JARs导入之前的jar包
通过注解编实现一个编译器插件(java)_第9张图片

c.点击Advanced会看到刚刚写的编译器名,然后点击OK,在点击Apply and Close(应用并关闭)

通过注解编实现一个编译器插件(java)_第10张图片


d.这时你打开项目中需要检查的类将会看到警告(我这里的枚举变量的警告没显示出来,本人也不知道为什么,不过通过上面的javac命令行检查是能检查出来)

通过注解编实现一个编译器插件(java)_第11张图片

源代码下载(源代码里面的检查方法名出错,修改看我博客中的代码)

 
  
 
  
 
  
 
  
 
 

你可能感兴趣的:(通过注解编实现一个编译器插件(java))