1 Java程序文件中函数起始行和终止行在程序文件位置中的判定__抽象语法树方法
应用需求:
实现对BigCloneBench中函数体的克隆检测,必须标注出起始行号和终止行号。
问题:
给定一个Java文件,从中提取出每个函数的起始行和终止行。
难点:
这个问题的难点在于,对于Java的解析器而言,其在形成抽象语法树的过程中,已经对源码文件进行了划分,然后,形成了对函数的抽象语法树。但是这部分操作是不开源的,因此我们无法操作。我们只能在已经形成的抽象语法树上进行操作,读取函数的起始行和终止行。
技术手段:
Eclipse中的Eclipse JDT提供了一组访问和操作Java源代码的API,Eclipse AST是其中一个重要组成部分,它提供了AST、ASTParser、ASTNode、ASTVisitor等类,通过这些类可以获取、创建、访问和修改抽象语法树。
实验与观察:
示例函数:
主体程序代码:
CompilationUnit cu = extractCompilationUnit(sourceFilePath, javaVersion); //Method visitor
MethodVisitor methodVisitor = new MethodVisitor();
cu.accept(methodVisitor);
List<MethodDeclaration> 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 {@link Antlr4ErrorLog}.
* @param log The Maven log
*/
public Antlr4ErrorLog(Tool tool,BuildContext buildContext,Log log){
this.tool=tool;
this.buildContext=buildContext;
this.log=log;
}
52
可以看到:示例函数的起始行52是javadoc对应起始行的位置,并不是public起始行的位置。这是因为一个method的抽象语法树单元是包括javadoc单元和block单元的,其规则为:
* <pre>
* MethodDeclaration:
* [ Javadoc ] { ExtendedModifier } [ <b><</b> TypeParameter { <b>,</b> TypeParameter } <b>></b> ] ( Type | <b>void</b> )
* Identifier <b>(</b>
* [ ReceiverParameter <b>,</b> ] [ FormalParameter { <b>,</b> FormalParameter } ]
* <b>)</b> { Dimension }
* [ <b>throws</b> Type { <b>,</b> Type } ]
* ( Block | <b>;</b> )
* ConstructorDeclaration:
* [ Javadoc ] { ExtendedModifier } [ <b><</b> TypeParameter { <b>,</b> TypeParameter } <b>></b> ]
* Identifier <b>(</b>
* [ ReceiverParameter <b>,</b> ] [ FormalParameter { <b>,</b> FormalParameter } ]
* <b>)</b> { Dimension }
* [ <b>throws</b> Type { <b>,</b> Type } ]
* ( Block | <b>;</b> )
* </pre>
可以看到Block是最后的一个元素。
我们做三种进一步的小实验:
实验一:移动大括号{,观察函数主体位置
假如我们将{从public所在行打到下一行去,即58行,我们再观察一下:
输出结果:
我们会看到,block的所在行从57行变成了58行。
实验二:添加人工注释,观察节点内容变化
输出结果:
之前终止行是61行,现在是第62行。然后public还有block等的起始行都是58。虽然第57行注释没有被解析为抽象语法树的内容,但是,整个函数的行数还是包括人工注释的行数!
所以,如何精确计算函数的起始位置和终止位置?不能简简单单的去除javadoc的行数!
结论:人工注释不被解析,只有javadoc格式才会被解析成为抽象语法树的一部分。但是,尽管没有被解析进去,但是人工注释的那一行被包含在内。你会发现函数的总行数增加了。
实验三:在大括号}后添加注释
最终方案:
我们不能简单通过去除javadoc行数的方式得到函数真正函数体和声明的起始行和终止行。
我们只有首先判断是否有javadoc节点,如果没有,接下来的第一个节点的行号就是起始行。然后,函数的终止行很好计算。
于是,就有了方法。
不再展开。
1 Java程序文件中函数起始行和终止行在程序文件位置中的判定__抽象语法树方法的更多相关文章
- C运行时库(C Run-time Library)详解(提供的另一个最重要的功能是为应用程序添加启动函数。Visual C++对控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)
一.什么是C运行时库 1)C运行时库就是 C run-time library,是 C 而非 C++ 语言世界的概念:取这个名字就是因为你的 C 程序运行时需要这些库中的函数. 2)C 语言是所谓的“ ...
- java将类和函数封装成jar,然后在别的项目中使用这个jar包
本来想用idea安装的,不过用maven生成后发现jar有20,30M肯定不对,后来还是用eclipse生成了,方便很多 环境: eclipse luna,jdk1.8_112 1.生成jar包,首先 ...
- IE浏览器下载文件保存时提示:“你没有权限在此位置中保存文件”解决办法
E浏览器下载文件保存时提示 解决办法: 1.Win + R,打开运行命令,输入gpedit.msc,如图所示 2.打开计算机本地组策略编辑器:选择计算机配置-windows设置-安全设置-本地策略-安 ...
- 【JAVA-JDT-AST】Java抽象语法树的构建、遍历及转成dot格式(附Github源码)
Background: 最近为了重现tree-based clone detection的论文:L. Jiang, G. Misherghi, Z. Su, and S. Glondu. Deckar ...
- 【阅读笔记】《C程序员 从校园到职场》第六章 常用文件操作函数 (Part 1)
参考链接:https://blog.csdn.net/zhouzhaoxiong1227/article/details/24926023 让你提前认识软件开发(18):C语言中常用的文件操作函数总结 ...
- PHP中函数的使用
函数是一种可以在任何被需要的时候执行的代码块函数的定义 1.函数是一个被命名的独立的代码段 2.它执行特殊的任务 3.并可以给调用它的程序返回值 函数的优点: 1.提高程序的重 ...
- 总结文件操作函数-文件夹(三)-C语言
获取.改变当前文件夹: 原型为: #include <unistd.h> //头文件 char *getcwd(char *buf, size_t size); //获取当前文件夹.相 ...
- 总结文件操作函数(一)-C语言
在进程一開始执行,就自己主动打开了三个相应设备的文件.它们是标准输入.输出.错误流.分别用全局文件指针stdin.stdout.stderr表示,相应的文件描写叙述符为0.1.2:stdin具有可读属 ...
- C语言样式的文件操作函数
使用C语言样式的文件操作函数,需要包含stdio.h头文件. 1.打开文件的函数: //oflag的取值为“w”或“r”,分别表示以写或读的方式打开 FILE* fd = fopen(filename ...
随机推荐
- javascript数据基本类型和引用类型区别详解
JavaScript基本数据类型: js基本数据类型包括:undefined,null,number,boolean,string.基本数据类型是按值访问的,就是说我们可以操作保存在变量中的实际的值. ...
- Spring Security构建Rest服务-0701-个性化用户认证流程
上一篇说了用户认证的基本流程,但是上一篇当访问一个受保护的服务后,如果未认证会调到默认的登录页面,这样是不行的,而且认证成功后,就直接访问了那个服务,如果想要做认证成功后做一些操作,还需要自定义. 个 ...
- sql返回行id
1.sql语句中 insert into tableName() output inserted.id values() 2 .存储过程中 ALTER PROCEDURE dbo.getBuyMedi ...
- windows下安装并使用redis
一.安装前首先了解一下phpinfo里面的一些信息,能否正确安装非常有帮助. (下图是我的本机环境) compiler :编译器 Architecture :CPU架构 Configuration F ...
- IntelliJ IDEA使用心得之问题篇;
在使用IDEA的时候,难免会遇到一些问题,总结下来,一是备忘,而是分享. 1.IDEA代码编辑区无法编辑? 当系统时间被调整到当前时间以前时会导致IDEA无法编辑,有3中解决方案:1)重启IDEA:( ...
- j2ee高级开发技术课程第一周
一.课程目标 这学期开始了J2EE高级开发技术这门课,在此之前我学习了javaSE,为这门课的学习打下了一定的基础.到这学期的结束我希望我能熟悉javaee,能开发企业级应用,对开发轻量级企业应用的主 ...
- Git 什么时候用什么指令
转自:http://blog.gogojimmy.net/2012/02/29/git-scenario/ Git 教學(1):Git的基本使用 Git 教學(2):Git Branch 的操作與基本 ...
- 用Shell编写项目发布脚本
1.首先在github上创建一个测试用的仓库 2.本地编写一个可以运行的测试项目,上传至github 3.链接服务器,编写脚本如下:注意:编写前需要在服务器上安装git和maven 执行build_c ...
- git分支branch合并到主分支master
如何使用git将分支branch合并到主干master上 对于一人独立使用git进行系统开发时,branch分支相当于版本(Version),如果每次都将新的分支branch提交到GitHub上,则会 ...
- 工程中添加工程依赖 Xcode iOS
有时我们需要在一个主工程中添加其他的子工程,用来对子工程进行编写修改或者是利用子工程中的库文件等等操作,这时候我们需要用到工程的嵌套. 步骤:(看图说话) 1.新建主工程,名为TestTTTT ...