当前页面: 开发资料首页 → JSP 专题 → 在Eclipse中创建新的重构功能
摘要: 在Eclipse中创建新的重构功能
package main;
public class TestSomething {
@Test(timeout=500)
public void testSomething(){}
}
org.eclipse.jface.text
org.eclipse.ltk.core.refactoring
org.eclipse.ltk.ui.refactoring
org.eclipse.jdt
org.eclipse.jdt.core
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Annotation Plug-in
Bundle-SymbolicName: manage.annotation; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: manage.annotation.AnnotationPlugin
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.jface.text,
org.eclipse.ltk.core.refactoring,
org.eclipse.ltk.ui.refactoring,
org.eclipse.jdt,
org.eclipse.jdt.core
Eclipse-AutoStart: true
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.ui.actionSets">
<actionSet
label="Annotation Action Set"
visible="true"
id="manage.annotation.actionSet">
<menu
label="%Refactoring.menu.label"
path="source"
id="org.eclipse.jdt.ui.refactoring.menu">
<separator name="reorgGroup"/>
</menu>
<action
class="manage.annotation.actions.AnnotationManageAction"
icon="icons/sample.gif"
id="manage.annotation.actions.AnnotationManageAction"
label="%Annotation.manage"
menubarPath="org.eclipse.jdt.ui.refactoring.menu/reorgGroup"
toolbarPath="reorgGroup"
tooltip="Manage Annotation in Java Project"/>
</actionSet>
</extension>
</plugin>
public void selectionChanged(IAction action, ISelection selection) {
if (selection.isEmpty())
select = null;
else if (selection instanceof IStructuredSelection) {
IStructuredSelection strut = ((IStructuredSelection) selection);
if (strut.size() != 1)
select = null;
if (strut.getFirstElement() instanceof IJavaElement)
select = (IJavaElement) strut.getFirstElement();
} else
select = null;
action.setEnabled(select != null);
}
操作的执行是在AnnotationManageAction的run函数中实现的,例如在本文的工程中,就是弹出RefactoringWizard对话框,指导用户完成重构的工作,这些我们将在下面的章节中讲述。
扩展Refactoring类
通过前面系统构架的介绍,大家知道了Refactoring和RefactoringWizard是完成EClipse重构功能的基础类。在创建好插件工程后,我们就通过扩展Refactoring来实现具体的功能。
Refactoring是所有支持代码转化的类的抽象父类,它在整个流程中与RefactoringWizard交互以完成重构的功能,起着非常重要的作用。这些类需要提供以下两类方法:
用于条件检查的方法,判断重构操作大体而言能否执行,以及具体的转化能否完成;
创建Change对象的方法,Change对象描述了所有将要执行的对当前代码的修改操作。
Refactoring类的典型流程如下所示:
1. 具体的Refactoring类被创建。
2. 获得用户选择的要进行重构的对象,初始化该Refactoring类。这个由具体的实现类给出相应的方法。
3. 在重构操作开始执行时,首先调用Refactoring的checkInitialConditions(IProgressMonitor) 基于用户选择的对象做一个的初始检查,这个通常由界面自动执行。返回RefactoringStatus.FATAL表明初始检查没有通过,重构操作不能继续。
4. 获得进行重构的其他参数,比如,对重命名操作来说就是指新名字。这个通常是由界面根据用户的输入提供的。由具体的实现类给出相应的方法。
5. 获得用户输入参数后,调用Refactoring的checkFinalConditions(IProgressMonitor)进行剩下的检查,这个通常由界面自动执行,返回RefactoringStatus.FATAL表明最后的检查没有通过,重构操作不能继续。
6. 调用Refactoring的createChange(IProgressMonitor)获得Change对象,这个通常由界面自动执行,界面可以根据Change对象显示预览界面。
基于以上的介绍,为了实现本文工程中的重构操作,我们需要扩展Refactoring类,为它增加一个构造函数,并且具体实现checkInitialConditions、checkFinalConditions和createChange三个函数。
首先通过菜单File -> New->Class弹出创建类的对话框,输入包名manage.annotation.refactor,类名AnnotationRefactoring,输入父类org.eclipse.ltk.core.refactoring.Refactoring,选中"继承抽象方法"复选框,点击完成按钮,一个扩展了Refactoring的最基本的类AnnotationRefactoring就被创建出来了。
首先为其增加构造函数,以用户选择的Java模型元素作为参数。Refactoring分析这个参数以得到所有相关的可写Java文件,作为重构操作的对象,如果该模型元素包含在Java文件中,则找到包含它的文件节点;如果该模型元素包含Java文件,则找到它的所有子Java文件。构造函数代码如下:
清单 6
public AnnotationRefactoring(IJavaElement element) {
while (element.getElementType() > IJavaElement.COMPILATION_UNIT) {
element = element.getParent();
if (element == null)
return;
}
if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
if (!element.isReadOnly())
compilationUnits.add(element);
}
if (element.getElementType() < IJavaElement.COMPILATION_UNIT)
findWritableCompilationUnits(element);
}
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
return RefactoringStatus.createInfoStatus("Initial Condition is OK!");
}
public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
collectChanges();
if (fChangeManager.size() == 0)
return RefactoringStatus.createFatalErrorStatus("No testing methods found!");
else return RefactoringStatus.createInfoStatus("Final condition is OK!");
}
使用AST构造Change对象
当我们找到了修改的位置时,我们有两个选择:
1. 通过IScanner接口扫描代码,然后通过IBuffer接口直接修改代码
2. 通过遍历和编辑AST树进行结构化的修改
DeveloperWorks提供的文章《扩展Eclipse的Java开发工具》中,给出了使用IBuffer接口的例子。现在我们要讲述使用AST来遍历和修改Java源代码的方法。
AST是abstract syntax tree的缩写。它是Eclipse中的Java开发环境(JDT)为我们提供的极为强大的源代码解析和编辑工具。
在使用AST树提供的功能之前,我们首先要创建AST树。由于AST树的构建是一项费时的操作,JDT缺省情况下不将源代码解析为AST树。下面的代码演示了获得一个ICompilationUnit对应的AST树的过程。在JDT提供的API中,ICompilationUnit接口用于表示一个可以被编译的源代码文件。在我们提供的例子程序中,我们通过下面的代码将整个文件解析成为了一颗AST树。
清单 9
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(cu);
ASTNode root = parser.createAST(null);
private void getMethods(ASTNode cuu, final List methods) {
cuu.accept(new ASTVisitor() {
public boolean visit(MethodDeclaration node) {
methods.add(node);
return false;
}
});
}
private boolean collectChanges(CompilationUnit root,MethodDeclaration method) {
if (method.getName().getFullyQualifiedName().startsWith("test")) {
AST ast = method.getAST();
if (needTimeout) {
NormalAnnotation na = ast.newNormalAnnotation();
na.setTypeName(ast.newSimpleName("Test"));
MemberValuePair pair = ast.newMemberValuePair();
pair.setName(ast.newSimpleName("timeout"));
pair.setValue(ast.newNumberLiteral("500"));
na.values().add(pair);
method.modifiers().add(0, na);
} else {
MarkerAnnotation na = ast.newMarkerAnnotation();
na.setTypeName(ast.newSimpleName("Test"));
method.modifiers().add(0, na);
}
return true;
}
return false;
}
root.recordModifications();
//在这里修改AST树…
TextEdit edits = root.rewrite(document, cu.getJavaProject()
.getOptions(true));
TextFileChange change = new TextFileChange("", (IFile) cu
.getResource());
change.setEdit(edits);
public Change createChange(IProgressMonitor pm) throws CoreException,OperationCanceledException {
Change[] changes = new Change[fChangeManager.size()];
System.arraycopy(fChangeManager.toArray(), 0, changes, 0,fChangeManager.size());
CompositeChange change = new CompositeChange("Add @Override Annotation", changes);
return change;
}
扩展RefactoringWizard 框架
Eclipse中的RefactoringWizard框架扩展了Eclipse的Wizard框架,关于Wizard框架的介绍可以在Eclipse的帮助系统中找到,这里我们仅从OO设计和架构的角度探讨一下RefactoringWizard框架。
我们从Wizard相关的几个类开始:
1. WizardPage类
WizardPage是一个包含了多个界面元素(比如文本框Text,按钮Button)的一个界面组合部分。各个Page之间是独立的,是可以动态加载的。WizardPage类的职责有:
·组合SWT界面元素,构造出一个界面页。
·定义本身界面元素的操作行为。
在RefactoringWizard框架中预设了两个通用的属性页:PreviewWizardPage和ErrorWizardPage。PreviewWizardPage类是用来预览重构后的修改,对比代码或其他资源的变化。ErrorWizardPage类是用来处理条件检查及错误状态通知的。我们只需扩展RefactoringWizard框架就可以自动获取这两项强大的功能。
2. Wizard类
一个Wizard就是一个装载一系列WizardPage页的容器,Wizard类的职责有:
·装载一系列WizardPage,构造出一个复杂的界面。
·装载领域类来处理具体业务逻辑。(在RefactoringWizard框架中这个类就是Refactoring类)
维护WizardPage页之间以及页与领域类之间的数据传递和状态共享。(在这里要补充一点,其实在具体RefactoringWizard框架的实现中有专门的类来分担这部分职责。)
我们的界面行为可以千变万化(通过组合不同的WizardPage),而负责处理业务逻辑的领域类也可以独立的变化,你可以随意扩展Wizard的界面功能(-对扩展开放),而不用修改现有RefactoringWizard框架(-对修改封闭),这正是OO设计的最基本原则-OCP(Open-Close Principle)。
3. WizardDialog类
这个对话框类的主要职责是构造一个完整的GUI界面以及操作界面。它预设了一些按钮(Back,Next,Finish,Cancel)等界面元素,它负责装载Wizard类,操作时通过按钮Back、Next来在多个WizardPage之间切换。
下面我们给出RefactoringWizard框架的架构图:
图 5 Refactoring Wizard架构图
/**
* create composite to add UI elements
*/
public void createControl(Composite parent) {
// define UI
Composite composite = new Composite(parent, SWT.NONE);
GridLayout lay = new GridLayout();
lay.numColumns = 2;
composite.setLayout(lay);
btnCheck = new Button(composite, SWT.CHECK);
btnCheck.setText("Add timeout parameter");
GridData gdBtnCheck = new GridData();
gdBtnCheck.horizontalSpan = 2;
gdBtnCheck.horizontalAlignment = GridData.FILL;
btnCheck.setLayoutData(gdBtnCheck);
labName = new Label(composite, SWT.WRAP);
labName.setText("TimeOut:");
GridData gdLabName = new GridData();
gdLabName.horizontalAlignment = GridData.BEGINNING;
gdLabName.grabExcessHorizontalSpace = true;
labName.setLayoutData(gdLabName);
txtTimeOut = new Text(composite, SWT.SINGLE | SWT.BORDER);
GridData gdTxtTimeOut = new GridData();
gdTxtTimeOut.horizontalAlignment = GridData.END;
gdLabName.grabExcessHorizontalSpace = true;
txtTimeOut.setLayoutData(gdTxtTimeOut);
txtTimeOut.setText("500");
// init status
labName.setEnabled(false);
txtTimeOut.setEnabled(false);
// add listener
defineListener();
// 将composite纳入框架的控制
setControl(composite);
Dialog.applyDialogFont(composite);
}
private void notifyStatus(boolean valid, String message) {
//设置错误信息
setErrorMessage(message);
//设置页面完成状态
setPageComplete(valid);
}
private void setRefactoring(boolean selection, String text) {
AnnotationRefactoring refactoring = (AnnotationRefactoring) getRefactoring();
refactoring.setNeedTimeout(true);
if(selection) {
refactoring.setTimeout(Integer.valueOf(txtTimeOut.getText()).intValue());
}
}
public AnnotationRefactoringWizard(Refactoring refactoring) {
super(refactoring, WIZARD_BASED_USER_INTERFACE);
}
protected void addUserInputPages() {
page = new AnnotationRefactoringWizardPage("refactor annotation");
addPage(page);
}
public void run(IAction action) {
Shell shell = window.getShell();
AnnotationRefactoring refactor = new AnnotationRefactoring(select);
AnnotationRefactoringWizard wizard = new AnnotationRefactoringWizard(refactor);
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
try {
op.run(shell, "Inserting @Override Annotation");
} catch (InterruptedException e) {
e.printStackTrace();
}
}