javadoc:ClassDoc中查找指定方法(Method)对应的MethodDoc对象

javadoc的ClassDoc对象完整且结构化保存了一个类代码的所有注释信息,

如果你对JavaDoc工具还不太了解,请参考我之前写的博客《java:通过javadoc API读取java源码中的注释信息(comment)》

本文要说明的是如何从已经得到的com.sun.javadoc.ClassDoc实例中获取指定方法的注释对象(MethodDoc)。

JavaDoc将Java代码注释信息解析是一个相当结构化的一组对象。
Java中对一个类的所有描述对象Class,Method,Field,Constructor,Type,…JavaDoc中都有对应的对象:

Java 类JavaDoc 对应的接口类
java.lang.Classcom.sun.javadoc.ClassDoc
java.lang.reflect.Methodcom.sun.javadoc.MethodDoc
java.lang.reflect.Constructorcom.sun.javadoc.ConstructorDoc
java.lang.reflect.Fieldcom.sun.javadoc.FieldDoc
java.lang.reflect.Typecom.sun.javadoc.Type
java.lang.reflect.ParameterizedTypecom.sun.javadoc.ParameterizedType
java.lang.reflect.TypeVariablecom.sun.javadoc.TypeVariable
java.lang.reflect.WildcardTypecom.sun.javadoc.WildcardType

但是JavaDoc是一套独立的代码,它本身并没有提供与这些Java 类之间的互操作功能。

所以虽然ClassDoc中有methods()方法可以获取所有的MethodDoc对象,ClassDoc中并没有提供查找java.lang.reflect.Method对应的MethodDoc对象这样的功能。

方案一

首先说明这是一个讨巧的方案,但也是不安全的方案。

我查看了ClassDoc接口的实现类com.sun.tools.javadoc.ClassDocImpl,发现它是有提供findMethod,findConstructor,findField方法来获取MethodDoc,ConstructorDoc,FieldDoc对象的。

于是直接调用ClassDocImpl提供的方法就能正确查找到方法对应的注释对象

ClassDocImpl是不开源的,看起来麻烦点,但是能看到方法定义看应该与Class查找方法的用法差不多,

ClassDocImpl.findMethod(String var1, String[] var2)

虽然看不到参数名,但对比Class.getMethod(String name, Class<?>... parameterTypes)应该知道,第一个参数是方法名。

后面的数组是参数类型数组,只是在findMethod中数组类型成了String[]

能猜到,ClassDocImpl内部是对于参数类型比较是通过字符串比较来实现的。那么只要能将Class转为ClassDocImpl可以正确匹配的字符串,应该就能正确找到方法的注释对象

如下是实现代码,其中getTypeName用于将Class转为类的全名字符,这才是实现的关键:

JavaDocUtils.java

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MemberDoc;
import com.sun.tools.javadoc.ClassDocImpl;

@SuppressWarnings("restriction")
public class JavaDocUtils {

	/**
	 * 返回类型的全名字符串
	 * @param type
	 */
	private static final String getTypeName(Class<?> type) {
		StringBuilder dimensions = new StringBuilder();
		while (type.isArray()) {
			dimensions.append("[]");
			type = type.getComponentType();
		} 
		String name = type.getName().replaceAll("\\$", "\\.");
		return new StringBuilder(name).append(dimensions).toString();
	}
	/**
	 * 将类型数组转为对应的全名类型字符串数组 
	 * @param parameterTypes
	 */
	private static String[] prepareTypes(Class<?>[] parameterTypes) {
		String[] typeNames = new String[parameterTypes.length];
		for(int i=0;i<typeNames.length;++i) {
			typeNames[i] = getTypeName(parameterTypes[i]);
		}
		return typeNames;
	}
	/**
	 * 在{@link ClassDoc}中查找与 {@link Member} 匹配的{@link MemberDoc}<br>
	 * @param classDoc
	 * @param member  member
	 */	
	public static MemberDoc getMemberDoc(ClassDoc classDoc,Member member) {
		if (null == classDoc || null == member){
			return null;
		}
		ClassDocImpl classDocImpl=(ClassDocImpl)classDoc;
		if(member instanceof Method) {
			return classDocImpl.findMethod(member.getName(),prepareTypes(((Method)member).getParameterTypes()));
		}else if(member instanceof Constructor<?>){
			return classDocImpl.findConstructor(member.getName(),prepareTypes(((Constructor<?>)member).getParameterTypes()));
		}else if(member instanceof Field){
			return classDocImpl.findField(member.getName());
		} else {
			throw new IllegalArgumentException("UNSUPPORTD member type " + member.getClass().getName());
		}
	}
}

经测试,可以正常工作。

方案二

前面说过方案一实现虽然简单,是个不安全的方案。因为com.sun.tools.javadoc.ClassDocImpl只是Oracle JDK提供的实现,如果贸然使用,在其他JDK(如OpenJDK)上运行时可能会找不到(我还没有试过)。

所以安全稳妥的方案是自己实现遍历从ClassDoc.methods()返回的数组,逐个匹配参数类型找到对应的MemberDoc的逻辑。

好在我们已经正确实现了getTypeName方法可以将一个Class转为JavaDoc可以匹配的字符串。自己实现遍历匹配参数也不算复杂:

以下是实现代码,

MemberMatchUtils.java

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.MemberDoc;
import com.sun.javadoc.Parameter;

@SuppressWarnings("restriction")
public class MemberMatchUtils {

	/**
	 * 返回类型的全名字符串
	 * @param type
	 */
	private static String getTypeName(Class<?> type) {
		StringBuilder dimensions = new StringBuilder();
		while (type.isArray()) {
			dimensions.append("[]");
			type = type.getComponentType();
		} 
		String name = type.getName().replaceAll("\\$", "\\.");
		return new StringBuilder(name).append(dimensions).toString();
	}
	/**
	 * 如果两个类型字符串匹配,返回{@code true},否则返回{@code false}
	 * @param docType
	 * @param type
	 */
	private static boolean equalType(com.sun.javadoc.Type docType,Class<?> type) {
		String typeName = getTypeName(type);
		String paramName = docType.qualifiedTypeName()+docType.dimension();
		if(typeName.equals(paramName)) {
			return true;
		}
		/** 
		 * primitive为true,说明docType 代表的类型没有对应的源码注释,
		 * 这时qualifiedTypeName()返回只是该类型的类名(不包含包名) ,
		 * 所以尝试使用Class的simpleName来比较
		 */
		return (docType.isPrimitive() && docType.qualifiedTypeName().equals(((Class<?>)type).getSimpleName()));
	}
	/**
	 * 检查两个方法对象的签名是否匹配<br>
	 * @param member
	 * @param doc
	 * @return 不匹配返回 {@code false} ,匹配返回 {@code true}
	 */
	private static boolean match(Member member, MemberDoc doc) {
		if (!member.getName().equals(doc.name())){
			return false;
		}
		if(member instanceof Field) {
			return true;
		}
		Class<?>[] paramTypes;
		if(member instanceof Method){
			paramTypes = ((Method)member).getParameterTypes();
		}else if(member instanceof Constructor<?>){
			paramTypes = ((Constructor<?>)member).getParameterTypes();
		}else{
			throw new IllegalArgumentException(String.format("INVALID member type %s,Method or Constructor required",member.getClass().getSimpleName()));
		}
		if(!(doc instanceof ExecutableMemberDoc)) {
			throw new IllegalArgumentException(String.format("INVALID doc type %s,ExecutableMemberDoc required",doc.getClass().getSimpleName()));
		}
		Parameter[] parameters = ((ExecutableMemberDoc)doc).parameters();
		if (paramTypes.length != parameters.length) {
			return false;
		}
		for (int i = 0; i < paramTypes.length; ++i) {
			if(!equalType(parameters[i].type(),paramTypes[i])) {
				return false;
			}
		}
		return true;
	}
	/**
	 * 在{@link ClassDoc}中查找与method匹配的{@link ExecutableMemberDoc}对象<br>
	 * 没找到匹配的对象则返回{@code null}
	 * @param classDoc
	 * @param member 
	 */
	public static MemberDoc findMember(ClassDoc classDoc,Member member) {
		if (null == classDoc || null == member){
			return null;
		}
		if(!equalType(classDoc,member.getDeclaringClass())) {
			return null;
		}
		MemberDoc[] memberDocs;
		if(member instanceof Field) {
			memberDocs = classDoc.fields();
		}else if(member instanceof Method){
			memberDocs = classDoc.methods();
		}else if(member instanceof Constructor<?>){
			memberDocs = classDoc.constructors();
		}else{
			throw new IllegalArgumentException(String.format("INVALID member type %s,Field,Method or Constructor required",member.getClass().getSimpleName()));
		}
		for (int i = 0 ; i < memberDocs.length ; ++ i ) {
			if (match(member, memberDocs[i]))
				return memberDocs[i];
		}
		return null;
	}
	/**
	 * 
	 * [递归]在{@link ClassDoc}中递归查找与method匹配的{@link MemberDoc}对象<br>
	 * @see #findMember(ClassDoc, Member)
	 */
	private static MemberDoc getMemberDoc(ClassDoc classDoc,Member member) {
		if (null == classDoc || null == member){
			return null;
		}
		MemberDoc matched = findMember(classDoc, member);
		if(matched == null){
			return getMemberDoc(classDoc.superclass(), member);
		}
		return matched;
	}
}

findMember(ClassDoc classDoc,Member member)方法实现在ClassDoc中查找与Member匹配的MemberDoc对象,没找到匹配的对象则返回nulll.

getMemberDoc(ClassDoc classDoc,Member member)方法调用findMember在当前ClassDoc上如果没找到匹配的对象则尝试在父类的ClassDoc对象上递归查找。

ClassDoc.overriddenMethod

按照我们的代码习惯,子类重写的方法(@Override)上一般没有注释,如果获取方法的注释对象为空,就要尝试向上查找父类方法才能找到真正的方法注释,这里就要用到ClassDoc.overriddenMethod()方法,如果当前方法对象是重写方法(@Override),它可以返回方法父类方法,否则返回null。

于是我们对于获取方法注释的实现可以实现如下:

	/**
	 * 在{@link ClassDoc}中查找与 {@link Method} 匹配的{@link MethodDoc}<br>
	 * 如果没有在当前方法上找到注释且是重写方法,则尝试向上父类查找父类方法
	 * @param classDoc
	 * @param method  
	 * @return 没有找则返回{@code null}
	 * @see #getMemberDoc(ClassDoc, Member)
	 */	
	public static MethodDoc getMethodDoc(ClassDoc classDoc, Method method) {
		MethodDoc doc = (MethodDoc) getMemberDoc(classDoc,method);
		while(null != doc && Strings.isNullOrEmpty(doc.getRawCommentText())){
			// 如果没有注释,向上父类查找被重写的方法
			doc = doc.overriddenMethod();
		}
		return doc;
	}

javadocreader

以上代码完整应用参见码云仓库:

common-javadocreader/src/main/java/gu/doc/ExtClassDoc.java · 10km/common-java - 码云 - 开源中国 (gitee.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值