【Eclipse AST】AST的修改

        AST的修改主要包括三个方面的内容:修改节点移动节点创建节点


       本文将通过一个综合实例来说明如何修改AST,并通过修改AST来修改源代码。

【Eclipse AST】AST的修改_第1张图片

图1 代码修改实例

       可以看到实例中的代码包含以下三种修改:

       (1) 条件表达式中的符号由等号改为不等号;

       (2) 原有的then部分移动到else部分;

       (3) 创建新的then部分。

       在对AST进行修改之前,需要先了解Java语法和各AST节点的涵义。if语句在AST中对应的节点名为IfStatement;条件表达式可以通过getExpression()方法获得;thenelse部分作为依附于IfStatement的两个角色分别为THEN_STATEMENTELSE_STATEMENT,可以通过getThenStatement()getElseStatement()方法获得,对应的方法还有setThenStatement()setElseStatement()可以利用工具ASTView,了解源代码对应的AST结构。

       图2是修改前的AST,由于完整展示需要标明过多的节点而导致图变得非常复杂,因此下图仅展示跟本次代码修改相关的重要节点。(学习过前几篇AST文章的读者可以发现,这里用大写字母开头的短语表示节点名,而全大写的短语表示节点在父节点中的角色名

【Eclipse AST】AST的修改_第2张图片

 图2 修改之前的AST


(1) 修改节点:条件表达式的符号由等号改为不等号

       实例中的条件表达式为中缀表达式,AST中对应的节点名为InfixExpression,所做的修改是将表达式的等号改为不等号。在修改时可以通过getExpression()方法获得表达式节点并进行强制类型转换(此处转为InfixExpression),转换后即可通过setOperator()方法修改操作符(Operator) 

/**
 * fragment != null
 */
InfixExpression ie = (InfixExpression)node.getExpression();
ie.setOperator(Operator.NOT_EQUALS);

       此时AST被修改为如图3所示。

【Eclipse AST】AST的修改_第3张图片

 图3 修改节点后的AST


(2) 移动节点:原有的then部分移动到else部分

       实例中if语句的then部分为语句块,AST中对应的节点名为Block,简单说就是带大括号的部分。现在我们要做的就是把if语句的这个Block节点从THEN_STATEMENT角色移动到ELSE_STATEMENT角色。在AST中,“移动”更准确的说是“复制”,“复制”之后把原有的部分使用delete()方法删除即可。

      那么是否可以直接通过如下代码来实现呢?

node.setElseStatement(node.getThenStatement());

       结果是不行的,抛出一个IllegalArgumentException异常,查看异常信息为“new child currently has a different parent”,类似的异常信息还有“new child is from a different AST”等。

       应当采用的方法是使用来自ASTNode类的copySubtree(AST target, ASTNode node)方法,意为将某一节点复制到某一AST,之后需经强制类型转换才可使用。

       通过copySubTree()方法产生与原父节点没有关联的独立节点,再将其连接到需要的节点上。对于本实例,正确方法如下:

/**
 * if (fragment == null) {
 *	  System.out.println("Wrong!");
 * }
 * else {
 *    System.out.println("Wrong!");
 * }
 */
node.setElseStatement(
(Block) ASTNode.copySubtree(node.getAST(),  node.getThenStatement()));

       此时AST被修改为图4所示。

【Eclipse AST】AST的修改_第4张图片

 图4 移动节点后的AST

 

(3) 创建节点:创建新的then部分

       完成这一部分的工作需要借助上一篇文章的内容【【Eclipse AST】AST的创建】,需要注意的是:因为new***()方法是通过对ast的调用而来,因此需要先获得当前节点所在的ast对象;创建节点完成后,需要把新节点放回原有的AST中。 

/**
 * if (fragment == null) {
 *	  System.out.println("Done!");
 * }
 * else {
 *    System.out.println("Wrong!");
 * }
 */
AST ast = node.getAST();

MethodInvocation methodInv = ast.newMethodInvocation();

SimpleName nameSystem = ast.newSimpleName("System");
SimpleName nameOut = ast.newSimpleName("out");
SimpleName namePrintln = ast.newSimpleName("println");

//连接‘System’和‘out’
//System.out
QualifiedName nameSystemOut = ast.newQualifiedName(nameSystem, nameOut);

//连接‘System.out’和‘println’到MethodInvocation节点
//System.out.println()
methodInv.setExpression(nameSystemOut);
methodInv.setName(namePrintln);

//"Done!"
StringLiteral sDone = ast.newStringLiteral();
sDone.setEscapedValue("\"Done!\"");

//System.out.println("Done!")
methodInv.arguments().add(sDone);

//将方法调用节点MethodInvocation连接为表达式语句ExpressionStatement的子节点
//System.out.println("Done!");
ExpressionStatement es = ast.newExpressionStatement(methodInv);

//将表达式语句连接为新建语句块节点Block的子节点
//{
//System.out.println("Done!");
//}
Block block = ast.newBlock();
block.statements().add(es);

//将语句块节点Block连接为IfStatement节点的子节点
node.setThenStatement(block);

        最终,AST被修改为图5所示:

【Eclipse AST】AST的修改_第5张图片

图5 创建节点后的AST

        本实例完整代码如下:

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InfixExpression.Operator;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StringLiteral;

public class IfTransformer extends ASTVisitor {
	
	@Override
	public boolean visit(IfStatement node) {
		/**
		 * fragment != null
		 */
		InfixExpression ie = (InfixExpression)node.getExpression();
		ie.setOperator(Operator.NOT_EQUALS);
		
		/**
		 * if (fragment == null) {
		 *	  System.out.println("Wrong!");
		 * }
		 * else {
		 *    System.out.println("Wrong!");
		 * }
		 */
		node.setElseStatement(
				(Block) ASTNode.copySubtree(node.getAST(),  node.getThenStatement()));

		/**
		 * if (fragment == null) {
		 *	  System.out.println("Done!");
		 * }
		 * else {
		 *    System.out.println("Wrong!");
		 * }
		 */
		AST ast = node.getAST();
		
		MethodInvocation methodInv = ast.newMethodInvocation();

		SimpleName nameSystem = ast.newSimpleName("System");
		SimpleName nameOut = ast.newSimpleName("out");
		SimpleName namePrintln = ast.newSimpleName("println");

		//连接‘System’和‘out’
		//System.out
		QualifiedName nameSystemOut = ast.newQualifiedName(nameSystem, nameOut);

		//连接‘System.out’和‘println’到MethodInvocation节点
		//System.out.println()
		methodInv.setExpression(nameSystemOut);
		methodInv.setName(namePrintln);

		//"Done!"
		StringLiteral sDone = ast.newStringLiteral();
		sDone.setEscapedValue("\"Done!\"");

		//System.out.println("Done!")
		methodInv.arguments().add(sDone);

		//将方法调用节点MethodInvocation连接为表达式语句ExpressionStatement的子节点
		//System.out.println("Done!");
		ExpressionStatement es = ast.newExpressionStatement(methodInv);
		
		//将表达式语句连接为新建语句块节点Block的子节点
		//{
		//System.out.println("Done!");
		//}
		Block block = ast.newBlock();
		block.statements().add(es);
		
		//将语句块节点Block连接为IfStatement节点的子节点
		node.setThenStatement(block);

		return false;
	}
}

       

       至此,本实例全部介绍完毕,本文所介绍的流程是使用AST来实现代码重构的核心,在后续文章中还将结合一些重构实例进行深入学习。

 

本文作者:刘伟,刘宏韬  http://blog.csdn.net/lovelion

你可能感兴趣的:(源代码,软件工程,代码质量,代码分析,ast)