PMD XPath 规则如何判断两个 ClassType 节点是否表示同一个 Java 类?
结论
PMD 7.24.0 的内置 XPath 接口没有通用方法来获取任意 ClassType 节点解析后的 FQCN,也没有内置函数可直接比较两个节点所代表的类型。
需要比较任意两个类时,建议编写 Java 规则:通过 getTypeMirror() 获取类型,再比较类符号。这样无需预先知道 FQCN,比较结果也不受简单类名、导入方式或全限定名称写法的影响。
目标类已知时,可以继续使用 XPath,通过 pmd-java:typeIsExactly 判断是否为指定类。PMD 7.24.0 XPath 文档
为什么 Designer 能显示,XPath 却读不到?
Designer 显示的类型信息与 XPath 能访问的属性并不完全相同。PMD 只将部分 Java getter 暴露为 XPath 属性。即使界面显示了 Type: java.lang.String,也不能据此认定存在 @Type 或 @TypeMirror 属性。XPath 的 AST 映射说明
ClassType 的名称属性也不能替代类型解析:
@SimpleName只有简单名称,无法区分java.util.Date和java.sql.Date。@PackageQualifier反映源码中的包限定部分。类以简单名称引用时,这个属性无法补出包名。
因此,拼接这两个属性无法得到适用于所有引用方式的 FQCN。具体行为可参阅 ASTClassType 源码。
已知目标类时,使用 typeIsExactly
题目中的 String 示例可以直接写成:
//ClassType[pmd-java:typeIsExactly('java.lang.String')]
只要类型解析正确,String 和 java.lang.String 两种写法都会匹配。
typeIs 接受目标类型及其子类型,typeIsExactly 则检查是否为指定类型。例如,ArrayList 可以满足 typeIs('java.util.List'),但它和 List 并不是同一个类或接口。类型检查函数说明
任意两个类的比较,放到 Java 规则中处理
- 根据规则的业务条件,找到需要比较的两个
ASTClassType节点。 - 调用
getTypeMirror(),确认解析结果是JClassType。 - 获取各自的
JClassSymbol,使用equals比较。
Java 规则通过 TypeNode.getTypeMirror() 访问类型信息,再通过 JClassType.getSymbol() 获取声明该类型的类符号。Java 类型解析 API、JClassType 接口
下面的辅助类可用于 Java 自定义规则:
import net.sourceforge.pmd.lang.java.ast.ASTClassType;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
public final class ClassTypeComparison {
private ClassTypeComparison() {
}
private static JClassSymbol resolvedClass(ASTClassType node) {
JTypeMirror type = node.getTypeMirror();
if (!(type instanceof JClassType)) {
return null;
}
JClassSymbol symbol = ((JClassType) type).getSymbol();
return symbol.isUnresolved() ? null : symbol;
}
public static boolean sameClass(ASTClassType left,
ASTClassType right) {
JClassSymbol leftSymbol = resolvedClass(left);
JClassSymbol rightSymbol = resolvedClass(right);
return leftSymbol != null
&& rightSymbol != null
&& leftSymbol.equals(rightSymbol);
}
public static String canonicalName(ASTClassType node) {
JClassSymbol symbol = resolvedClass(node);
return symbol == null ? null : symbol.getCanonicalName();
}
}
如果传入题目中两个字段的 ClassType 节点,且类型解析成功,sameClass 应返回 true,对两个节点调用 canonicalName 都应得到 java.lang.String。实际规则仍需自行选择节点并报告违规,这些辅助方法只负责比较类和获取类名。
判断是否为同一个类时,直接比较符号即可;需要输出类名时,再调用 getCanonicalName()。该方法允许返回 null,例如局部类就没有 canonical name。JClassSymbol 接口
注意“同一个类”和“同一个类型”的区别
sameClass 忽略泛型实参。例如:
List<String> names;
List<Integer> counts;
这两个类型对应同一个 java.util.List 类符号,因此 sameClass 返回 true。如果规则还要求泛型实参一致,应比较类型镜像:
left.getTypeMirror().equals(right.getTypeMirror())
PMD 的 JTypeMirror.equals 用于比较类型是否等价。不应使用 ==,也不应比较 getTypeMirror().toString(),因为后者是调试表示,格式可能变化。JTypeMirror 接口说明
分析所需的依赖还应放入 auxClasspath。示例对未解析符号采用保守处理:返回 false 只表示尚未确认两者相同,不能据此认定它们一定不同。PMD 可能为缺失依赖保留部分名称信息,实际规则需要明确如何处理这类情况。辅助类路径配置、未解析符号说明
备注:内容仅供参考。
