• 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}
    * @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()));
    	}
    	/**
    	 * 检查两个方法对象的签名是否匹配
    * @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}对象
    * 没找到匹配的对象则返回{@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}对象
    * @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}
    * 如果没有在当前方法上找到注释且是重写方法,则尝试向上父类查找父类方法 * @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)

  • 相关阅读:
    Mac OS 搭建C++开发环境【已解决】
    迅为龙芯开发板固态硬盘启动(烧写系统到固态)-分区
    AI应用开发之路-准备:发起一个开源小项目 DashScope SDK for .NET
    机器学习(四十二):遗传算法对机器学习多分类器的寻优
    【python技巧】文本处理-re库字符匹配
    跨平台使用:第三方美颜SDK在多种操作系统上的应用
    CSS选择器分类( 通配符、标签选择器、id选择器、类选择器)
    短剧小程序开发,重塑视频观看体验的科技革命
    [每日两题系列]刷算法题咯~~
    MySQL高可用九种方案
  • 原文地址:https://blog.csdn.net/10km/article/details/139703473