当前位置: 首页 > article >正文

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

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 的代码片段:

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


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的调用示例代码

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》


http://www.kler.cn/news/325959.html

相关文章:

  • 【Linux】基于驱动框架的程序编写测试
  • 全国糖酒会全域采购商选品会前瞻-见证零售新势力的崛起与变革
  • 第七讲-按钮控件QRadioButton
  • LINUX之Ansible自动化运维工具配置和ssh-keygen配置远程免密钥登录
  • InputStream为什么不能被重复读取?为啥只能被读取一次?
  • 探索 Android DataBinding:实现数据与视图的完美融合
  • 腾讯邮箱上传附件卡、慢、无法上传,下载慢问题处理
  • Harmony 获取定位位置的方式
  • 休眠唤醒不了?你的4G模组不是装睡,而是少了一条指令…
  • Spring Mvc 基础源码分析
  • OceanBase 关于一号表笔记与ERROR 1060(42S21)问题
  • 表驱法优化代码
  • 入职2年的程序员,被劝退了!年纪大了,感觉好绝望!
  • Studying-图论包含的算法总结
  • [Python学习日记-31] Python 中的函数
  • Java开发:文件上传和下载
  • PCL 移动立方体重建(HOPPE)
  • STM32引脚PB3、PB4、PA15作为输入输出的特殊配置
  • mysql代理服务器
  • 自然语言处理实战项目
  • MinIO使用客户端进行桶和对象的管理
  • OpenCV视频I/O(1)视频采集类VideoCapture介绍
  • Mybatis-Mapper接口方式
  • SpringBoot依赖之Microsoft SQL Server Driver
  • 谈谈Redisson分布式锁的底层实现原理
  • 怎么提取视频里的音频?非常简单的提取音频方法
  • 上下位关系自动检测方法(论文复现)
  • Stargazers Ghost Network在GitHub平台上的隐性威胁
  • 大数据复习知识点4
  • 深度估计任务中的有监督和无监督训练