在Eclipse中创建新的重构功能
对重构的强大支持是软件开发人员喜爱Eclipse的一个最为重要的原因。而Eclipse还有一个至少和重构不相上下的优点,那就是其近乎无懈可击的可扩展性。这两者的结合意味着我们可以根据自己的需要来创建展新的重构功能。
重构在现代软件开发过程中扮演着重要的角色,它能够减轻软件开发人员的工作负担,提高软件开发的生产效率。为了阐明重构的重要性,我们在这里引用了developerWorks上David Carew提供的关于重构的教程中的一段话:
现在,一个开发者的工作大部分在于对现有的代码进行修改,而不是起草写新的代码。简单的修改可能包括对现有代码进行添加。然而,多样化的修改或扩展 的改变会使软件内部结构开始恶化。重构改变软件的内部结构使得软件更容易理解并且在不需要改变其显著的行为的情况下使得修改的代价也更小。
在Java软件开发过程中,通过使用Eclipse提供的重构工具,我们至少获得了以下好处:
1. 最终产品更为健壮:我们对程序代码的修改将不太可能出错,出现遗漏修改的可能变少,即使出现问题也能够通过Undo功能回退到重构前的状态。
2. 提高了生产效率。通常一次重构能够完成对程序代码的多处改动。最为明显的例子可能是Eclipse提供的Rename重构,它能够在修改名称的同时相应的更改所有的引用。
Eclipse为我们提供了多种实用的重构功能,在软件开发过程中使用这些重构能够给我们带来极大的好处。然而,针对每个开发人员的特殊需要,总有 一些迫切需要的功能是不能通过已有的重构来获得的。这个时候,我们可以对Eclipse平台进行一些扩展,创建适应我们自己需要的重构。如果这个重构恰好 能够符合大多数人的需要,我们也可以像其他Eclipse的contributor一样,将我们的重构贡献给Eclipse社区。
接下来,我们将通过一个例子来展示如何在Eclipse中创建新的重构功能。我们这里创建的重构将用于迁移JUnit的测试用例。我们知道,在当前 版本的JUnit中,一个用于测试的函数必须以字符串"test"作为方法名称的开始。而在即将来到的JUnit 4中,一个"@Test"的Annotation被用于标明方法是一个测试方法。我们将要创建的重构将完成这个迁移工作,即在所有的以"test"开始的 方法之前加上"@Test"标记。@Test Annotation还可以包含一个timeout属性用来规定方法的最大执行时间,我们在向导中提供了一个页面供用户选择是否需要timeout属性。
![]() ![]() |
![]()
|
为了给读者一个直观的感受,我们下面首先介绍本文中例子的实际运行效果。在阅读完本文之后,读者朋友也能够顺利的完成类似的功能。
启动例子程序提供的Refactor之后,我们获得了一个由三个页面组成的向导。在第一个页面中,用户可以选择是否需要timeout参数,并且用户能够设置timeout参数的值。
图 1 输入参数
![图 1 输入参数](https://i-blog.csdnimg.cn/blog_migrate/46063c6cecf565091430350d6c331dfd.png)
当用户输入参数完毕之后,通过单击Next按钮我们将进入下一个页面。向导将进行初始条件检查和最终条件检查,并将检查的结果反馈给用户。在图 2中我们可以看到,初始条件和最终条件都正常,因此我们可以进入下一步。
图 2 显示条件检查
![图 2 显示条件检查](https://i-blog.csdnimg.cn/blog_migrate/145c439db5a94ea072ffb9ed4b87a474.png)
接下来是预览窗口(图 3),向导用直观的界面显示了在应用向导之后,我们将会对源代码造成怎样的改动。用户可以在这个页面中判断最终的修改是否符合自己的需要。另外,用户也能够选择性的取消对某些文件的修改。
![image006.gif](https://i-blog.csdnimg.cn/blog_migrate/7b299ae33b43f14229a2a40429d6fe71.png)
当用户检查预览页面确认没有问题之后,用户可以按下Finish按钮从而完成重构。这个时候,源代码会发生修改,最后的结果如下所示:
清单 1
package main; |
![]() ![]() |
![]()
|
在Eclipse中,一个重构操作主要由以下三个部分组成:
1. RefactoringWizard类:RefactoringWizard提供了向导式的用户界面来引导用户完成重构工作。不需要我们做任何工作, Eclipse已经通过RefactoringWizard为我们提供了预览页面、条件检查页面以及Undo/Redo等功能。我们需要继承这个类从而为 重构过程提供特定的用户界面。
2. Refactoring类:Refactoring类完成具体的定位和修改代码功能。为了建立新的Refactoring,我们需要继承这个类并实现重构的逻辑部分。
3. AST和ASTParser:在Refactoring类中,我们需要对代码进行定位和修改,这可以通过AST机制来完成。AST是abstract syntax tree的简称,它能够将Java代码解析成为一个树形结构。在利用了AST树之后,对源代码的修改变成了对AST树的遍历、更改节点属性,以及插入和删 除节点等。
一个典型的重构操作流程如下所示:
1. 用户选择要进行重构的对象,通过菜单项或按钮启动重构操作。
2. 创建具体的Refactoring类,弹出RefactoringWizard。
3. RefactoringWizard与用户交互,引导用户输入必要的参数;RefactoringWizard调用Refactoring类的函数进行条件检查。
4. Refactoring类创建AST,并利用其对源代码进行定位和修改。这里进行的修改并不直接应用到源代码上,而是被保存成Change对象,供Refactoring框架使用。
5. RefactoringWizard调用Refactoring类的函数,获得重构内容的详细描述信息(即第4步生成的Change对象),显示在预览界面上,待用户确认。
6. 用户确认后Refactoring框架将修改代码,重构操作结束。
接下来,我们将详细介绍新建重构类型的各个步骤。
![]() ![]() |
![]()
|
在大家对整个系统构架有了一个大概的了解之后,我们的介绍就从创建工程开始。大家都知道Eclipse提供了很好的扩展性,通过创建插件就能把我们 要添加的重构功能无缝的插入到Eclipse平台中。创建插件工程的方法在很多地方都有介绍,这里不再详细讲解。如果需要基础的插件开发知识,我们可以参 考《 开发 Eclipse 插件》,树立基本的插件开发意识。
通过菜单 File -> New-> Project,选择Plug-in Project。点击Next,出现对话框,输入项目名称manage.annotation,接受其他选项的默认值。点击Next,出现插件属性设置的 对话框,继续接受默认值。点击Next,出现选择插件模板对话框,该工程要在Refactor菜单中添加一个新的菜单项,所以这里我们采用"Hello, World"的插件模板。点击Next,修改"Action类名称"的值为AnnotationManageAction,点击Finish按钮。至此, 一个最基本Eclipse工作台的插件工程就被创建出来了。
插件工程创建后,缺省进入Plug-in开发透视图,Plug-in Manifest编辑器自动打开,显示这个插件工程的基本信息,如对其他插件的依赖,扩展点,构建(build)的配置信息等等。由于该工程需要用到其他 插件的功能,必须为其添加到其他插件的依赖。在Plug-in Manifest编辑器点击Dependencies页面,在该页面中的Required Plug-ins列表中通过Add按钮添加如下的插件:
清单 2
org.eclipse.jface.text |
或者也可以通过直接修改MANIFEST.MF文件完成。操作完成后察看MANIFEST.MF文件,注意Require-Bundle列表中是否出现了新添加的这几项。MANIFEST.MF文件如下:
清单 3
Manifest-Version: 1.0 |
在Plug-in Manifest编辑器中打开插件清单文件plugin.xml,可以看到,这个插件扩展了org.eclipse.ui.actionSets扩展点, 这是一个基本的Eclipse工作台的扩展点,通过扩展它,插件可以很简单得对Eclipse的菜单、工具条进行扩展。这个plugin.xml是 "Hello,World"插件模板的清单文件,我们把它改成适合这个工程的文件。清单如下:
清单 4
<?xml version="1.0" encoding="UTF-8"?>label="%Refactoring.menu.label" path="source" id="org.eclipse.jdt.ui.refactoring.menu"> 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"/> |
该清单文件表明,在Refactor菜单中添加了一个新菜单项"Annotation Manage",并在工具条上相应添加了一个按钮。点击菜单项或者按钮的事件由类 "manage.annotation.actions.AnnotationManageAction"处理。
最后需要修改的就是manage.annotation.actions.AnnotationManageAction类。它继承了 org.eclipse.ui.IWorkbenchWindowActionDelegate接口,该接口用于处理各种通过扩展点添加的操作。当菜单项 或者按钮被点击时,这个类就被Eclipse工作台装载进来,处理转发过来的请求以及接下来的操作。
AnnotationManageAction被创建后,一旦用户的选择部分有所改变,接口的selectionChanged函数就会被触发,告 知用户所选择的部分,可以在这个函数中根据用户的选择相应的修改操作的可用性或者其他显示属性。例如在本文的工程中,我们希望只有当用户选择了一个 Java模型元素时才能使用这个操作,那么就需要在selectionChanged中添加如下的代码:
清单 5
public void selectionChanged(IAction action, ISelection selection) { |
selectionChanged函数的参数selection记录了用户选择的部分,我们首先判断它的选择部分的数目是否为一,然后判断这个唯一 的选择部分是否为Java模型元素,这两个条件任何一个不满足都会导致action.setEnabled(false)的执行,这时会弹出如下的对话框 说明操作不可用,同时菜单项和按钮都会显示成灰色,直到用户选择了合适的部分时,菜单项和按钮才会实显,就可以进行具体的操作了。
图 4 表明Action目前不能执行的对话框
![图 4 表明Action目前不能执行的对话框](https://i-blog.csdnimg.cn/blog_migrate/55ba5a884fadb5c6d490f7ec185f626f.png)
操作的执行是在AnnotationManageAction的run函数中实现的,例如在本文的工程中,就是弹出RefactoringWizard对话框,指导用户完成重构的工作,这些我们将在下面的章节中讲述。
![]() ![]() |
![]()
|
通过前面系统构架的介绍,大家知道了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) { |
接着完成checkInitialConditions函数,实现初始检查的具体操作。作为示例,在本文工程中我们不进行任何具体的检查操作,只简单得给出初始检查成功的信息,返回RefactoringStatus.
INFO以使重构操作继续执行。checkInitialConditions函数代码如下:
清单 7
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) |
接着完成checkFinalConditions函数,实现获得用户输入参数后的后续检查操作。在本文工程中,我们首先收集所有需要添加注释的以 test开头的方法,判断是否不存在这样的方法,如果不存在给出出错信息,返回RefactoringStatus.FATAL以结束重构操作;如果存在 这样的方法,则给出后续检查成功的信息,返回RefactoringStatus.
INFO。checkFinalConditions函数代码如下:
清单 8
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) |
最后,创建Change对象的createChange函数是整个重构操作中最核心的代码,它的实现将在下面章节中介绍。
![]() ![]() |
![]()
|
当我们找到了修改的位置时,我们有两个选择:
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); |
AST树中的每个节点都是ASTNode类型,通过Visit模式,我们可以访问一个ASTNode包含的所有节点。下面的代码演示了访问一个AST节点并获得其中所有的MethodDeclaration节点的方法。
清单 10
private void getMethods(ASTNode cuu, final List methods) { |
在收集到了所有的MethodDeclaration节点之后,我们就可以通过向AST树中插入和删除节点或者修改已有的节点的方法来修改AST树了。下面的代码演示了使用AST工具为方法添加@Test Annotation的功能。
清单 11
private boolean collectChanges(CompilationUnit root, |
在Refactoring框架中,我们要求对AST树的修改并不立刻反映到源代码中。相反,我们需要一个能记录整个修改过程的Change对象。 Refactoring框架将利用这个Change对象来显示Priveiw窗口、进行Undo和Redo等操作。大致上,我们记录对一个AST树的修改 从而生成Change对象的过程如以下代码所示。
清单 12
root.recordModifications(); |
最后,由于Refactoring类的createChange方法仅返回一个Change对象,如果我们需要对多个源代码文件进行修改,我们可以 利用CompositeChange类将多个Change对象封装成一个Change对象。这个过程可能类似如下代码所执行的流程
清单 13
public Change createChange(IProgressMonitor pm) throws CoreException, |
![]() ![]() |
![]()
|
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架构图
![图 5 Refactoring Wizard架构图](https://i-blog.csdnimg.cn/blog_migrate/a2285bdef37bbf88a676fedac0cc499e.png)
从图 5中我们可以看到,如果我们把每一个WizardPage页看作一项业务,那么Refactoring正是处理业务逻辑的控制中心,它封装了所有对业务逻 辑的处理,当然它可以在将处理任务委任出去。但请注意,它并不负责实现业务流程,也就是说各业务(各个Page界面)之间的逻辑顺序关系不由它维护。
RefactoringWizard框架充分考虑到了应用的可扩展性,它在SWT的MVC(模型-视图-控制)元架构模式的基础上,添加了一些新的 架构元素。MVC模式促使业务逻辑与界面分离,界面与控制行为分离,而RefactoringWizard框架增强了界面本身分离的特性,它将一个完整的 界面分拆成多个页面,用户可以动态组合这些页面或添加新的页面来扩展界面行为。这种特性-界面的动态组合,低耦合,高内聚,封装良好的接口-让我们领略到 了OO设计的精髓。
下面我们通过以下几个步骤来扩展RefactoringWizard框架:
- 扩展RefactoringWizardPage
- 扩展RefactoringWizard
- 启动RefactoringWizard
第一步,扩展RefactoringWizardPage:首先我们新建一个类AnnotationRefactoringWizardPage, 它需要继承UserInputWizardPage类(其父类是RefactoringWizardPage,而 RefactoringWizardPage最终实现了IDialogPage接口)。接下来就是实现IDialogPage接口的 createControl(…)方法,在这个方法里实现你的界面行为,比如我们例子中的TimeOut文本框,代码清单如下:
清单 14
|
在这里我们要特别注意的一点是在定义完我们的界面元素后,需要将自定义的Composite纳入框架的控制,就是这行代码:"setControl(composite);"
在我们处理完输入数据检查后进入下一页面之前,我们需要设置页面完成的状态,以及传递输入数据到领域类Refactoring。我们用以下代码设置好页面完成的状态后,下个页面ErrorWizardPage就会处理显示逻辑:
清单 15
private void notifyStatus(boolean valid, String message) { |
传递输入数据通过以下代码来处理:
清单 16
private void setRefactoring(boolean selection, String text) { |
其中getRefactoring()方法是继承自RefactoringWizardPage的方法,由于我们的 RefactoringWizard类装载了RefactoringWizardPage和Refactoring类,这个方法是从 RefactoringWizard类中获得的,这里面用到了Observer设计模式。 至此,我们完成RefactoringWizardPage的扩展。
第二步,扩展RefactoringWizard:首先我们新建一个类AnnotationRefactoringWizard,它需要继承 RefactoringWizard类,这个类中我们只需要加载定义好的AnnotationRefactoringWizardPage类和 AnnotationRefactoring类,当然复杂的处理已经有RefactoringWizard框架处理好了。下面我们在构造函数中加载 Refactoring类:
清单 17
public AnnotationRefactoringWizard(Refactoring refactoring) { |
然后我们加载我们的AnnotationRefactoringWizardPage类,只需重载父类RefactoringWizard的addUserInputPages()方法就可以:
清单 18
protected void addUserInputPages() { |
第三步,启动RefactoringWizard。扩展好RefactoringWizard之后,就需要在用户点击菜单项或是按钮时弹出这个对话 框。RefactoringWizard最好使用RefactoringWizardOpenOperation类来打开(当然也可以用 RefactoringWizardDialog)。RefactoringWizardOpenOperation首先进行重构的初始检查,通过后才打 开RefactoringWinzard对话框,否则就会打开错误对话框。前面完成创建插件工程时我们提到,弹出RefactoringWizard对话 框的代码应该放到响应菜单操作的类的run函数中。具体到本文工程中,就是把下面的代码放到AnnotationManageAction的run函数 中。这些代码首先依次构造Refactoring和RefacoringWizard子类AnnotationRefactoring和 AnnotationRefactoringWizard,并将AnnotationRefactoring的引用传递给 AnnotationRefactoringWizard,然后用RefactoringWizardOpenOperation打开 AnnotationRefactoringWizard,弹出向导对话框。
清单 19
public void run(IAction action) { |
![]() ![]() |
![]()
|
在Eclipse中有效的利用重构能够大大的减轻软件开发人员的工作负担,提高软件的健壮性。然而,目前重构仍然处在一个工具缺乏的时代。以 Eclipse为例,只有JDT提供的重构工具最为完善,而针对其他语言例如C++、Python等的开发环境,都缺乏对应的重构功能。
通过本文提供的方法,我们能够有效的利用Eclipse中的重构框架创建新的重构,从而进一步提高已有开发环境的效率。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130706/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/374079/viewspace-130706/