QDox

123 阅读7分钟

Status

QDox 是一种高速、小内存占用的解析器,用于完全提取类/接口/方法定义(包括注解、参数、参数名)。它被设计为可供活跃的代码生成器或文档工具使用。

大概意思是一款完整的 java 类、接口、方法定义的提取器,包括了注释、参数及参数名称。其实核心功能就是你输入一个 java 类的源码,他可以把这个 java 类解析成一个对象,我们通过这个对象可以获取很方便的获取解析的类的不同组成,比如我可以获得这个类有哪些方法,这个方法的参数是什么,返回值又是什么,他们的类型又分别是什么?还有这个方法上有哪些注释、哪些 tag。也能获取类中有哪些的 field。。。总之把这个类庖丁解牛般解析好,使得调用者很方便的获取到自己感兴趣的信息。

虽然不再那么重要,但它也能处理 JavaDoc @标签。

In A Nutshell(简而言之)

使用JFlex和BYacc/J构建了一个定制的解析器。之所以选择这些库,是因为它们的性能已得到验证,并且在运行时不需要外部库。

解析器只略读源文件,查找感兴趣的内容,如类/接口定义、导入语句、JavaDoc和成员声明。解析器忽略诸如实际方法实现之类的内容以避免开销(而在方法块中,花括号计数就足够了)。

解析器的最终结果是一个非常简单的文档模型,其中包含足够有用的信息。

Frequently Asked Questions(常见问题)

General

接口的对象类型是什么?

JavaClass方法用于表示类和接口。isInterface()方法允许您区分这两者。

当使用一个类时,getSuperClass()返回被扩展的类。如果在输入源代码中没有定义,则返回java.lang.Object。当使用接口时,此方法总是返回null。

当使用一个类时,getImplements()返回一个由这个类实现的接口数组。如果没有实现,则返回一个空数组。当使用接口时,这将返回当前接口EXTENDS的接口数组。

我可以完全控制类加载器吗?

在某些情况下,QDox被用来为另一个具有自己依赖项的项目生成类。这可能导致类冲突。默认情况下,JavadocBuilder将包含当前项目的类加载器,但是通过定义您自己的classLibrary,您可以拥有所需的控制。

/* new ClassLibrary()将给你一个空的classLoader
 * 很有可能你至少需要 system classloader(系统类加载器)。
 */
ClassLibraryBuilder libraryBuilder = new SortedClassLibraryBuilder(); //or OrderedClassLibraryBuilder()
libraryBuilder.addClassLoader( ClassLoader.getSystemClassLoader() );
JavaProjectBuilder builder = new JavaProjectBuilder( libraryBuilder );

下载

QDox可在Maven中心获得。要在您的pom中包含最新的QDox,请包含以下依赖项:

<dependency>
  <groupId>${project.groupId}</groupId>
  <artifactId>${project.artifactId}</artifactId>
  <version>${project.version}</version>
</dependency>

QDox 的使用

入口点

JavaProjectBuilder是QDox的入口点。它负责解析源代码、解析导入和存储数据。

要创建它,需要做的就是调用默认构造函数。

JavaProjectBuilder builder = new JavaProjectBuilder();

读取源文件

然后可以将Java源代码添加到JavaProjectBuilder中。源代码可以一次读取一个文件(使用java.io.Reader),也可以递归地添加整个源代码树。

// Reading a single source file.
builder.addSource(new FileReader("MyFile.java"));
​
// Reading from another kind of input stream.
builder.addSource(new StringReader("package test; public class Hello {}"));
​
// Adding all .java files in a source tree (recursively).
builder.addSourceTree(new File("mysrcdir"));

解析类名

为了解析使用通配符导入的类(例如import java.util.*;), ClassLibrary必须知道项目中使用的其他类。

ClassLibrary有4种方法来解析类:

  • 通过查看已添加的其他源。
  • 通过搜索提供的sourceFolders
  • 通过查看当前类路径(包括标准JRE类)。
  • 通过查看在运行时指定的其他classloader。

添加到JavaProjectBuilder中的所有源代码和源代码树都将被解析。这通常远远超出了要求。为了提高效率,使用ClassLibrary来添加源文件夹。将这些文件视为惰性解析源文件。

当前的类路径是由JavaProjectBuilder自动设置的。在大多数情况下,这应该是足够的,但是在某些情况下,您可能希望在外部库中解析完整的类。

// 获取 ClassLibrary
JavaProjectBuilder builder = new JavaProjectBuilder();
​
// 添加一个 sourcefolder;
builder.addSourceFolder( new File( "src/main/java" ) );
builder.addSourceFolder( new File( "target/generated-sources/foobar" ) );
​
// 添加一个自定义 ClassLoader
builder.addClassLoader( myCustomClassLoader );
​
// Ant example : add the <classpath> element's contents
builder.addClassLoader( new AntClassLoader( getProject(), classpath ) );

在解析任何源文件之前添加额外的classloader是很重要的。

导航模型

现在已经解析了文件,接下来进行模型导航。

模型

在源代码被解析之后,可以使用简单易用且直观的对象模型来导航文件的内容。

Java源代码

表示一个完整的.java文件。它包含一个类的集合。

输入例子

package com.blah.foo;
​
import java.awt.*;
import java.util.List;
​
public class Class1 {
  ...
}
​
class Class2 {
}
​
interface Interface1 {
}
Example Code
JavaProjectBuilder builder = new JavaProjectBuilder();
JavaSource src = builder.addSource(myReader);
​
JavaPackage pkg      = src.getPackage();
List<String> imports     = src.getImports(); // {"java.awt.*",
                                     //  "java.util.List"}
​
JavaClass class1     = src.getClasses().get(0);
JavaClass class2     = src.getClasses().get(1);
JavaClass interface1 = src.getClasses().get(2);

JavaPackage

表示类的包。

输入例子

package com.blah.foo;
​
public class BarClass  {
...
}

示例代码

JavaProjectBuilder builder = new JavaProjectBuilder();
JavaSource src = builder.addSource(myReader);
​
JavaPackage pkg      = src.getPackage();
​
Collection<JavaClass> classes  = pkg.getClasses(); // BarClass
String name          = pkg.getName(); // "com.blah.foo"
String toString      = pkg.toString(); // "package com.blah.foo" conform javaAPI
JavaPackage parent   = pkg.getParentPackage(); //

JavaClass

表示类或接口。它包含doclet标记、字段和方法。可以获得有关类定义的信息,例如扩展了哪些类,实现了哪些接口和修饰符。

Example Input
package com.blah.foo;
​
import java.io.*;
import com.custom.*;
import com.base.SubClass;
​
/**
 * @author Joe
 */
public abstract class MyClass extends SubClass
            implements Serializable, CustomInterface  {
​
  private String name;
  public void doStuff() { ... }
  private int getNumber() { ... }
​
}

示例代码

JavaProjectBuilder builder = new JavaProjectBuilder();
builder.addSource(myReader);
​
JavaClass cls = builder.getClassByName("com.blah.foo.MyClass");
​
String pkg      = cls.getPackage();            // "com.blah.foo"
String name     = cls.getName();               // "MyClass"
String fullName = cls.getCanonicalName(); // "com.blah.foo.MyClass";
String canonicalName = cls.getFullyQualifiedName(); // "com.blah.foo.MyClass";
boolean isInterface = cls.isInterface();       // false
​
boolean isPublic   = cls.isPublic();   // true
boolean isAbstract = cls.isAbstract(); // true
boolean isFinal    = cls.isFinal();    // false
​
JavaType superClass = cls.getSuperClass(); // "com.base.SubClass";
List<JavaType> imps     = cls.getImplements(); // {"java.io.Serializable",
                                       //  "com.custom.CustomInterface"}
​
String author = cls.getTagsByName("author").getValue(); // "joe"
​
JavaField nameField = cls.getFields()[0];
JavaMethod doStuff = cls.getMethods()[0];
JavaMethod getNumber = cls.getMethods()[1];
​
JavaSource javaSource = cls.getParentSource();

JavaField

表示类中的字段。它包含doclet标记、名称和类型。

输入例子

import java.util.Date;
​
public class MyClass  {
​
  /**
   * @magic
   */
  private String email;
  public static Date[][] dates;
}

示例代码

JavaField e = cls.getFields()[0];
​
JavaType eType     = e.getType(); // "java.lang.String";
String eName   = e.getName(); // "email";
DocletTag eTag = e.getTagsByName("magic"); // @magic
boolean eArray = e.getType().isArray(); // false;
​
JavaField d = cls.getFields()[1];
​
JavaType dType     = d.getType(); // "java.util.Date";
String dName   = d.getName(); // "dates";
DocletTag dTag = d.getTagsByName("magic"); // null
boolean dArray = d.getType().isArray(); // true;
int dDimensions= d.getType().getDimensions(); // 2;
boolean dStatic= d.isStatic(); // true;
​
JavaClass javaClass = d.getParentClass();

JavaMethod

表示类中的方法。它包含doclet标记、名称、返回类型、参数和异常。

输入例子

import java.util.Date;
import java.io.*;
​
public class MyClass  {
​
  /**
   * @returns Lots of dates
   */
  public static Date[] doStuff(int number,
                               String stuff)
            throws RuntimeException, IOException {
    ...
  }
}

示例代码

JavaMethod m = cls.getMethods()[0];
​
String mName = m.getName(); // "doStuff";
JavaType mReturns = m.getReturns(); // "java.util.Date";
boolean mArray = m.getReturns().isArray(); // true
boolean mStatic = m.isStatic(); // true
boolean mPublic = m.isPublic(); // true
​
String doc = m.getTagByName("returns").getValue();
  // "Lots of dates"
​
List<JavaType> exceptions = m.getExceptions();
  // {"java.lang.RuntimeException", "java.io.IOException"}
​
JavaParameter numberParam = m.getParameters()[0];
JavaParameter stuffParam = m.getParameters()[1];
​
JavaClass javaClass = m.getParentClass();

JavaParameter

表示传递给方法的参数。它有名称和类型。

Example Input
public class MyClass  {
​
  public void stuff(int n, Object[] objects) {
    ...
  }
​
}

示例代码

JavaMethod m = cls.getMethods()[0];
​
JavaParameter n = m.getParameters()[0];
String nName = n.getName(); // "n"
JavaType nType   = n.getType(); // "int";
​
JavaParameter o = m.getParameters()[1];
String oName   = o.getName(); // "objects"
JavaType oType     = o.getType(); // "java.lang.Object";
boolean oArray = o.getJavaClass().isArray(); // true
​
JavaMethod javaMethod = o.getParentMethod();

JavaType

表示被另一个类使用的类的特定实例(如返回值、超类等)。该值表示类的名称。数组尺寸也是可用的。从1.8开始,也可以获得Type的泛型值

输入例子

import java.util.*;
​
public class MyClass  {
​
  public void stuff(int n, Object[] objects,
                    Date[][] dates, List<String> stringList) {
    ...
  }
​
}

示例代码

JavaMethod m = cls.getMethods()[0];
​
JavaType returns = m.getReturns();
returns.getValue(); // "void"
returns.isArray(); // false
returns.getDimensions(); // 0
​
JavaType n = m.getParameters()[0].getType();
n.getValue(); // "int"
n.isArray(); // false
n.getDimensions(); // 0
​
JavaType objects = m.getParameters()[1].getType();
objects.getValue(); // "java.lang.Object"
objects.isArray(); // true
objects.getDimensions(); // 1
​
JavaType dates = m.getParameters()[2].getType();
dates.getValue(); // "java.util.Date"
dates.isArray(); // true
dates.getDimensions(); // 2
​
JavaType stringList = m.getParameters()[3].getType();
stringList.getValue(); // "java.util.List"
stringList.getGenericValue(); // "java.util.List<java.lang.String>"
stringList.isArray(); // false
stringList.getDimensions(); // 0

DocletTag

表示JavaDoc标记。每个标记都有一个名称和一个值。可选地,可以将值分解为按索引或名称访问的令牌。

JavaClass、JavaField和JavaMethod类都支持注释和文档标签

返回的DocletTag带有名称、值和将值分解为特定参数的方法。

输入例子

/**
 * This method does nothing at all.
 *
 * @returns A boolean of whether we care or not.
 * @param email Someone's email address.
 * @param dob Date of birth.
 *
 * @permission administrator full-access
 * @webservice publish=true name=myservice type=rpc
 */
boolean doWeCare(String email, Date dob);

示例代码

JavaMethod mth = cls.getMethods()[0];
​
// Access the JavaDoc comment
String comment = mth.getComment();
// "This method does nothing at all."// Access a single doclet tag
DocletTag returns = mth.getTagByName("returns");
returns.getName(); // "returns";
returns.getValue(); // "A boolean of whether we care or not."// Access multiple doclet tags with the same name
DocletTag[] params = mth.getTagsByName("param");
params[0].getValue(); // "Someone's email address."
params[1].getValue(); // "Date of birth."// Access specific parameters of a doclet tag by index
DocletTag permission = mth.getTagByName("permission");
permission.getParameter[0]; // "administrator"
permission.getParameter[1]; // "full-access"// Access specific parameters of a doclet tag by name
DocletTag webservice = mth.getTagByName("webservice");
webservice.getNamedParameter("type"); // "rpc"
webservice.getNamedParameter("name"); // "myservice"