javdoc:(JDK9)VISITOR模式遍历语法树(DocCommentTree)获取代码注释中的tag(@return,@param)对象

上一篇博客《javadoc:jdk 9通过javadoc API读取java源码中的注释信息(comment)》介绍了JDK9下javadoc API的基本使用方法。

本文进一步示例说明如何通过使用遍历语法树的方式更精确获取注释对象中子对象的方法。

DocCommentTree

JDK9 的Javadoc API可以语法树的形式提供解析后的注释对象DocCommentTree

以下是通过Doclet获取DocCommentTree并输出的代码片段,

代码取自
https://download.java.net/java/early_access/valhalla/docs/api/jdk.javadoc/jdk/javadoc/doclet/package-summary.html#example-heading

java 复制代码
public class Example implements Doclet {
    public void printElement(DocTrees trees, Element e) {
    	/** 从 DocTrees 中获取对应类型的注释语法树 */
        DocCommentTree docCommentTree = trees.getDocCommentTree(e);
        if (docCommentTree != null) {
            stdout.println("Element (" + e.getKind() + ": "
                    + e + ") has the following comments:");
            /** 输出注释内容 */
            stdout.println("Entire body: " + docCommentTree.getFullBody());
            /** 输出注释中块标签,例如 @return,@param,@throws */
            stdout.println("Block tags: " + docCommentTree.getBlockTags());
        }
    }

    @Override
    public boolean run(DocletEnvironment docEnv) {
        // 获取DocTrees实用程序类以访问文档注释
        DocTrees docTrees = docEnv.getDocTrees();
		/** 循环调用printElement输出所有类的注释 */
        for (TypeElement t : ElementFilter.typesIn(docEnv.getIncludedElements())) {
            stdout.println(t.getKind() + ":" + t);
            for (Element e : t.getEnclosedElements()) {
                printElement(docTrees, e);
            }
        }
        return true;
    }
}

从上面的代码可以理解 DocCommentTree对象代表了一个类、方法、成员的对应的结构化注释数据,DocCommentTree.getFullBody()返回注释内容,DocCommentTree.getBlockTags()返回注释中块标签对象列表,例如 @return,@param,@throws

在DocCommentTree对象的基础上,我们就可以自由地获取一个注释对象的所有细节。

DocTreeVisitor

所有注释对象(包括DocCommentTree)都实现了com.sun.source.doctree.DocTree接口。

JDK 9提供了com.sun.source.doctree.DocTreeVisitor接口,用于以VISITOR模式遍历 DocTree对象。以实现对注释对象的自由处理。

为了理解DocTreeVisitor接口的作用和用法,我觉得最直接的方式就是看它的一个实现com.sun.tools.javac.tree.DocPretty,这个类实现了对一个DocTree的打印输出到java.io.Writer,

所有DocTree实现的toString()方法都是用这个DocPretty输出为String。

如下是所有DocTree接口的实现的基类com.sun.tools.javac.tree.DCTree 的代码片段:

java 复制代码
public abstract class DCTree implements DocTree {
    /**
     * Convert a tree to a pretty-printed string.
     */
    @Override
    public String toString() {
        StringWriter s = new StringWriter();
        try {
            new DocPretty(s).print(this);
        }
        catch (IOException e) {
            // should never happen, because StringWriter is defined
            // never to throw any IOExceptions
            throw new AssertionError(e);
        }
        return s.toString();
    }
}

SimpleDocTreeVisitor

话说DocTreeVisitor接口有十几个方法,如果要全部自己实现,要写太多代码了。

其实一般不需要这么做。因为JDK 9同时还提供了com.sun.source.util.SimpleDocTreeVisitor方法实现了DocTreeVisitor所有方法。对于调用者来说,继承SimpleDocTreeVisitor类,根据需要重写特定的方法,才是一般场景的DocTreeVisitor的实现方式。

JDK 9提供的另一个DocTreeVisitor实现com.sun.source.util.DocTreeScanner也建议看一下,DocTreeScanner偏向于遍历所有子节点,调用者根据自己需要,决定使用哪个做基类。

在本文中,我们需要实现遍历DocCommentTree所有的BlockTagTree。所以选择SimpleDocTreeVisitor作为基类。因为SimpleDocTreeVisitor所有默认方法实现都调用SimpleDocTreeVisitor.defaultAction方法,只要重写defaultAction方法,就可以实现所有块标签类型判断。程序逻辑要简单很多。

BlockTagExtracter

以下是继承SimpleDocTreeVisitor实现DocTreeVisitor接口的代码。作用很简单,当输入遍历 对象是DocCommentTree 时将,所有对象中的所有块标签(block tag)对象保存到一个Map,以供后续使用。

BlockTagExtracter.java

java 复制代码
import java.util.LinkedHashSet;
import java.util.Set;

import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.sun.source.doctree.*;
import com.sun.source.util.SimpleDocTreeVisitor;

/**
 * 读取所有注释标签({@link BlockTagTree}),如:'@author','@param',保存到{@link #blockTags}
 */
class BlockTagExtracter extends SimpleDocTreeVisitor<Void,Void> {
	/** 保存块标签(block tag)的MultiMap(允许Key重复) */
	private final SetMultimap<String, BlockTagTree> blockTags = Multimaps.newSetMultimap(Maps.newLinkedHashMap(),
			LinkedHashSet::new);

    BlockTagExtracter() {
    }
	public Set<String> allTags() {
		return blockTags.keySet();
	}
    @Override
	protected Void defaultAction(DocTree node, Void p) {
    	if(node instanceof BlockTagTree) {
    		/** 如果是块标签,则保存到blockTags */
    		blockTags.put("@"+ node.getKind().tagName, (BlockTagTree) node);
    	}
		return super.defaultAction(node, p);
	}

	@Override 
    public Void visitDocComment(DocCommentTree node, Void p) {
    	/** 遍历所有 BlockTagTree实例  */
		return super.visit(node.getBlockTags(),null);
    }
}

示例

以下为BlockTagExtracter的调用示例代码

java 复制代码
import org.junit.Test;
import static org.junit.Assert.*;

import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.spi.ToolProvider;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind;


import com.sun.source.doctree.BlockTagTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.util.DocTrees;

import jdk.javadoc.doclet.Doclet;
import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.doclet.Reporter;

/**
 * JDK 9的javadoc tool 调用测试
 * @author guyadong
 *
 */
public class JavadocToolTest {

	@Test
	public void testJavadocTool() {
		try {
			/** 获取 javadoc tool */
			ToolProvider javadocTool = ToolProvider.findFirst("javadoc").orElseThrow();
			int returnCode = javadocTool.run(System.out,System.err,new String[] {
					/** 指定自定义的  Doclet 接口实现类(全名)  */
					"-doclet", DocletExample.class.getName(), 
					/** 指定-doclet选项定义类名的所在的类搜索路径  */
					"-docletpath", DocletExample.class.getProtectionDomain().getCodeSource().getLocation().getPath(),
					/** --subpackages 要获取注释的包名 */
					"-subpackages",	"net.gdface.utils",
					/** --sourcepath 要源码路径 */
					"-sourcepath","D:/j/common-java/common-base/src/main/java",
					/** --classpath 指定javadoc执行时搜索引用类的路径 */
					"-classpath","D:/j/common-java/common-base/target/classes",
					"-encoding","utf-8",
					"-Xdoclint", "none"
			});
			if(0 != returnCode){
				System.out.printf("javadoc ERROR CODE = %d\n", returnCode);
				throw new IllegalStateException();
			}
		} catch (Throwable e) {
			e.printStackTrace();
			fail();
		}
	}
	public static class DocletExample implements Doclet {
	    private Reporter reporter;
		private Elements elementUtils;

	    @Override
	    public void init(Locale locale, Reporter reporter) {
	        reporter.print(Kind.NOTE, "Doclet using locale: " + locale);
	        this.reporter = reporter;
	    }

	    public void printElement(DocTrees trees, Element e) {
	        DocCommentTree docCommentTree = trees.getDocCommentTree(e);
	        if (docCommentTree != null) {
	        	reporter.print(Kind.NOTE,"Element " + e.getKind() + ": "
	                    + e);
reporter.print(Kind.NOTE,unicodeToString(elementUtils.getDocComment(e)));
	        	BlockTagExtracter extracter = new BlockTagExtracter();
	        	/** 遍历 docCommentTree的所有节点 */
	        	docCommentTree.accept(extracter,null);
	        	reporter.print(Kind.NOTE,extracter.allTags().toString());
	        	for(String tag:extracter.allTags()) {
	        		for(BlockTagTree blockTag: extracter.tags(tag)) {
	        			reporter.print(Kind.NOTE,unicodeToString(blockTag.toString()));
	        		}
	        	}
	        }
	    }
	    /** 将字符串中的unicode转义字符转为unicode字符 */
		public static String unicodeToString(String unicodeStr) {
			Matcher matcher = Pattern.compile("\\\\u([0-9a-f]{4})").matcher(unicodeStr);
			StringBuilder sb = new StringBuilder();
			int lastAppendPosition = 0;
			while (matcher.find()) {
				sb.append(unicodeStr, lastAppendPosition, matcher.start());
				sb.append((char) Integer.parseInt(matcher.group(1), 16));
				lastAppendPosition = matcher.end();
			}
			sb.append(unicodeStr, lastAppendPosition, unicodeStr.length());

			return sb.toString();
		}
	    @Override
	    public boolean run(DocletEnvironment docEnv) {
	        // get the DocTrees utility class to access document comments
	        DocTrees docTrees = docEnv.getDocTrees();
	        elementUtils = docEnv.getElementUtils();

	        for (TypeElement t : ElementFilter.typesIn(docEnv.getIncludedElements())) {
	        	reporter.print(Kind.NOTE,t.getKind() + ":" + t.getQualifiedName());
	        	if(t.getQualifiedName().toString().endsWith("ResourcePool")) {
	        		reporter.print(Kind.NOTE,"ResourcePool");
	        	}
	        	reporter.print(Kind.NOTE,"getDocComment:"+elementUtils.getDocComment(t));
	            
	            for (Element e : t.getEnclosedElements()) {
	                printElement(docTrees, e);
	            }
	        }
	        return true;
	    }

	    @Override
	    public String getName() {
	        return "DocletExample";
	    }

	    @Override
	    public Set<? extends Option> getSupportedOptions() {
	        Option[] options = {
	            new Option() {
	                private final List<String> someOption = List.of(
	                        "-Xdoclint"
	                );

	                @Override
	                public int getArgumentCount() {
	                    return 1;
	                }

	                @Override
	                public String getDescription() {
	                    return "Enables recommended checks for problems in Javadoc comments";
	                }

	                @Override
	                public Option.Kind getKind() {
	                    return Option.Kind.STANDARD;
	                }

	                @Override
	                public List<String> getNames() {
	                    return someOption;
	                }

	                @Override
	                public String getParameters() {
	                    return "";
	                }

	                @Override
	                public boolean process(String opt, List<String> arguments) {
	                    return true;
	                }
	            }
	        };

	        return Set.of(options);
	    }

		@Override
	    public SourceVersion getSupportedSourceVersion() {
	        // support the latest release
	        return SourceVersion.latest();
	    }
	}
}

完整代码及示例

完整代码参见码云仓库:https://gitee.com/l0km/javadocreader9

参考资料

《Package jdk.javadoc.doclet》

相关推荐
as_jopo3 分钟前
-Dspring.profiles.active=dev与--spring.profiles.active=dev的区别
java·后端·spring
hummhumm3 分钟前
第 32 章 - Go语言 部署与运维
java·运维·开发语言·后端·python·sql·golang
QQ_1154320314 分钟前
基于Java+SpringBoot+Mysql在线简单拍卖竞价拍卖竞拍系统功能设计与实现四
java·spring boot·mysql·毕业设计·毕业源码·竞拍系统·竞拍平台
fa_lsyk10 分钟前
Spring:AOP面向切面案例讲解AOP核心概念
java·后端·spring
陈奕迅本讯10 分钟前
人力资源项目学习
java·学习
2401_8784673213 分钟前
大连环保公益管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·学习·tomcat·maven
Genius Kim16 分钟前
JWT加解密应用方案设计与实现
java·开发语言
尘浮生32 分钟前
Java项目实战II基于SpringBoot的客户关系管理系统(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
2401_8576100334 分钟前
企业OA系统:Spring Boot技术实现与管理
java·spring boot·后端
飞滕人生TYF37 分钟前
java 集合 菱形内定义封装类 而非基本数据类型 原因解释 详解
java