java中visit函数_1 Java程序文件中函数起始行和终止行在程序文件位置中的判定__抽象语法树方法...

应用需求:

实现对BigCloneBench中函数体的克隆检测,必须标注出起始行号和终止行号。

问题:

给定一个Java文件,从中提取出每个函数的起始行和终止行。

难点:

这个问题的难点在于,对于Java的解析器而言,其在形成抽象语法树的过程中,已经对源码文件进行了划分,然后,形成了对函数的抽象语法树。但是这部分操作是不开源的,因此我们无法操作。我们只能在已经形成的抽象语法树上进行操作,读取函数的起始行和终止行。

技术手段:

Eclipse中的Eclipse JDT提供了一组访问和操作Java源代码的API,Eclipse AST是其中一个重要组成部分,它提供了AST、ASTParser、ASTNode、ASTVisitor等类,通过这些类可以获取、创建、访问和修改抽象语法树。

实验与观察:

示例函数:

6c44d0ab360304094cee5b02fd8739d9.png

主体程序代码:

CompilationUnit cu =extractCompilationUnit(sourceFilePath, javaVersion);//Method visitor

MethodVisitor methodVisitor = newMethodVisitor();cu.accept(methodVisitor);

List methods =methodVisitor.getMethods();for(MethodDeclaration method : methods){int methodStartLineNumber=cu.getLineNumber(method.getStartPosition());

System.out.println("methodCode:");

System.out.println(method.toString());

System.out.println(methodStartLineNumber);//Visit the method node and extract all ASTNodes

nodes =ASTNodeVisitor.visitMethod(method);int j=0;for(ASTNode node : nodes) {

System.out.println("子节点"+(++j));

System.out.println("所在起始行:"+cu.getLineNumber(node.getStartPosition()));//计算起始行

System.out.println("所在终止行:"+cu.getLineNumber(node.getStartPosition()+node.getLength()-1));//计算终止行

System.out.println("子节点类型:"+ASTNode.nodeClassForType(node.getNodeType()));

System.out.println("子节点内容:");

System.out.println(node.toString());

}

}

其中,cu是使用ASTParser类对Java文件进行解析以后得到的CompilationUnit类的编译单元。MethodVisitor继承ASTVisitor类,是对抽象语法树的每个MethodDeclaration类节点进行存储,构建methods列表,每个元素对应一个函数的抽象语法树的顶层节点。ASTNodeVisitor的visitMethod方法则对method对应抽象语法树的每个节点进行遍历,将节点存储到nodes列表中。

部分输出结果是:

methodCode:/*** Creates an instance of {@linkAntlr4ErrorLog}.

*@paramlog The Maven log*/

publicAntlr4ErrorLog(Tool tool,BuildContext buildContext,Log log){this.tool=tool;this.buildContext=buildContext;this.log=log;

}52

可以看到:示例函数的起始行52是javadoc对应起始行的位置,并不是public起始行的位置。这是因为一个method的抽象语法树单元是包括javadoc单元和block单元的,其规则为:

*

 
 

*MethodDeclaration:* [ Javadoc ] { ExtendedModifier } [ < TypeParameter { , TypeParameter } > ] ( Type | void)* Identifier (

* [ ReceiverParameter , ] [ FormalParameter { ,FormalParameter } ]* ){ Dimension }* [ throws Type { ,Type } ]* ( Block | ;)*ConstructorDeclaration:* [ Javadoc ] { ExtendedModifier } [ < TypeParameter { , TypeParameter } >]* Identifier (

* [ ReceiverParameter , ] [ FormalParameter { ,FormalParameter } ]* ){ Dimension }* [ throws Type { ,Type } ]* ( Block | ;)*

可以看到Block是最后的一个元素。

我们做三种进一步的小实验:

实验一:移动大括号{,观察函数主体位置

假如我们将{从public所在行打到下一行去,即58行,我们再观察一下:

b6a45797a4747ce1a7fe7973eac12e86.png

输出结果:

835176e806278f098a2596cd47f0d0ed.png

我们会看到,block的所在行从57行变成了58行。

实验二:添加人工注释,观察节点内容变化

0186062fdf825b30db7f27d091175400.png

输出结果:

b7dd46d3f7a9333049b1003115b5275e.png

之前终止行是61行,现在是第62行。然后public还有block等的起始行都是58。虽然第57行注释没有被解析为抽象语法树的内容,但是,整个函数的行数还是包括人工注释的行数!

所以,如何精确计算函数的起始位置和终止位置?不能简简单单的去除javadoc的行数!

结论:人工注释不被解析,只有javadoc格式才会被解析成为抽象语法树的一部分。但是,尽管没有被解析进去,但是人工注释的那一行被包含在内。你会发现函数的总行数增加了。

实验三:在大括号}后添加注释

6c918e2d159a86f8450b49e229631723.png

937193bd369234fb76e16b0c8e07d818.png

最终方案:

我们不能简单通过去除javadoc行数的方式得到函数真正函数体和声明的起始行和终止行。

我们只有首先判断是否有javadoc节点,如果没有,接下来的第一个节点的行号就是起始行。然后,函数的终止行很好计算。

于是,就有了方法。

不再展开。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值