前 2 篇的链接如下:
第 1 篇 - 如何编写一个面试时能拿的出手的开源项目?
第 2 篇 - 如何编写一个面试时能拿的出手的开源项目?
第 1 篇介博文中详细介绍过编写一个规范开源项目所要遵循的规范, 并且初步实现了博主自己的开源项目 Javac AST View 插件, 不过只搭建了项目开发的基本框架, 树状结构的数据模型也是硬编码的; 第 2 篇从 Eclipse 编辑器中读取 Java 源代码并转换为 Javac 的抽象语法树, 然后又将 Javac 的抽象语法树转换为了 Eclipse 树形插件所识别的数据模型, 在视图中动态展现出来. 本篇将为这个树形插件视图完善功能. 主要就是添加读入, 刷新, 展开与折叠的功能按钮, 同时双击树中的某个结点, 选中 Eclipse 中对应的源代码信息.
1, 添加读入, 刷新, 展开与折叠功能按钮
代码比较简单, 只需要定义相应的 Action, 然后添加到工具栏管理器 IToolBarManager 中即可.
定义的相应 Action 如下:
- private void makeActions() {
- fFocusAction = new Action() {
- @Override
- public void run() {
- performSetFocus();
- }
- };
- fFocusAction.setText("&Show AST of active editor");
- fFocusAction.setToolTipText("Show AST of active editor");
- fFocusAction.setActionDefinitionId(IWorkbenchCommandConstants.FILE_REFRESH);
- ASTViewImages.setImageDescriptors(fFocusAction, ASTViewImages.SETFOCUS);
- fRefreshAction = new Action() {
- @Override
- public void run() {
- try {
- refreshAST();
- } catch (CoreException e) {
- e.printStackTrace();
- }
- }
- };
- fRefreshAction.setText("&Refresh AST");
- fRefreshAction.setToolTipText("Refresh AST");
- fRefreshAction.setEnabled(false);
- ASTViewImages.setImageDescriptors(fRefreshAction, ASTViewImages.REFRESH);
- fCollapseAction = new Action() {
- @Override
- public void run() {
- performCollapse();
- }
- };
- fCollapseAction.setText("C&ollapse");
- fCollapseAction.setToolTipText("Collapse Selected Node");
- fCollapseAction.setEnabled(true);
- ASTViewImages.setImageDescriptors(fCollapseAction, ASTViewImages.COLLAPSE);
- fExpandAction = new Action() {
- @Override
- public void run() {
- performExpand();
- }
- };
- fExpandAction.setText("E&xpand");
- fExpandAction.setToolTipText("Expand Selected Node");
- fExpandAction.setEnabled(true);
- ASTViewImages.setImageDescriptors(fExpandAction, ASTViewImages.EXPAND);
- fDoubleClickAction = new Action() {
- @Override
- public void run() {
- performDoubleClick();
- }
- };
- }
单击 4 个按钮后执行的对应动作由 3 个函数定义, 如下:
- private void refreshAST() throws CoreException {
- internalSetInput(uri);
- }
- protected void performCollapse() {
- IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
- if (selection.isEmpty()) {
- fViewer.collapseAll();
- } else {
- fViewer.getTree().setRedraw(false);
- for (Object s : selection.toArray()) {
- fViewer.collapseToLevel(s, AbstractTreeViewer.ALL_LEVELS);
- }
- fViewer.getTree().setRedraw(true);
- }
- }
- protected void performExpand() {
- IStructuredSelection selection= (IStructuredSelection) fViewer.getSelection();
- if (selection.isEmpty()) {
- fViewer.expandToLevel(3);
- } else {
- fViewer.getTree().setRedraw(false);
- for (Object s : selection.toArray()) {
- fViewer.expandToLevel(s, AbstractTreeViewer.ALL_LEVELS);
- }
- fViewer.getTree().setRedraw(true);
- }
- }
- protected void performSetFocus() {
- IEditorPart part= EditorUtility.getActiveEditor();
- if (part instanceof ITextEditor) {
- try {
- setInput((ITextEditor) part);
- } catch (CoreException e) {
- showAndLogError("Could not set Javac AST view input", e); //$NON-NLS-1$
- }
- }
- }
performSetFocus() 函数执行读入动作, refreshAST() 函数执行刷新动作, performCollapse() 函数执行语法树合上动作, 而 performExpand() 函数执行语法树展开动作. 可以展开特定语法树节点, 只要选中这个语法树节点, 然后点击展开按钮即可.
向工具栏管理器中添加定义好的 Action, 如下:
- private void contributeToActionBars() {
- IActionBars bars = getViewSite().getActionBars();
- bars.getToolBarManager().add(fFocusAction);
- bars.getToolBarManager().add(fRefreshAction);
- bars.getToolBarManager().add(fCollapseAction);
- bars.getToolBarManager().add(fExpandAction);
- bars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), fFocusAction);
- }
在 createPartControl() 函数中调用相关方法, 如下:
- makeActions();
- contributeToActionBars();
效果如下:
2, 选中源代码功能
要选中 Eclipse 插件中某个范围的源代码, 需要调用相关函数, 同时传递 2 个重要的参数, 一个就是字符的开始位置 pos, 另外就是长度 length. 这两个参数我们可以直接从 Javac 的相关 API 中获取, 修改 createAST() 函数, 如下:
- public static EndPosTable ept = null;
- private JCCompilationUnit createAST(URI is) {
- Context context = new Context();
- JavacFileManager.preRegister(context);
- JavaFileManager fileManager = context.get(JavaFileManager.class);
- JavaCompiler comp = JavaCompiler.instance(context);
- JavacFileManager dfm = (JavacFileManager) fileManager;
- JavaFileObject jfo = dfm.getFileForInput(is.getPath());
- comp.genEndPos = true;
- JCCompilationUnit tree = comp.parse(jfo);
- ept = tree.endPositions;
- comp.parseFiles(otherFiles);
- return tree;
- }
需要打开 JavaCompiler 的 genEndPos 开关, 这样 Javac 在分析 Java 源代码字符流的过程中, 就会保存对应的语法树节点到字符结束位置的对应关系, 这个关系就保存在 EndPostTable 中, 所以我们用全局变量 ept 保存即可.
在 JavacASTNode 中新定义 2 个属性, 用来保存对应语法树节点在字符流中的开始与结束位置, 如下:
private int startpos,endpos;
然后修改 JavacASTNode 的构造函数, 如下:
- public JavacASTNode(int startpos,int endpos) {
- children = new ArrayList<JavacASTNode>();
- this.startpos = startpos;
- this.endpos = endpos;
- }
在访问者方法中为这 2 个属性赋值, 例如在 visitCompilationUnit() 和 visitClass() 方法中赋值, 实现如下:
- @Override
- public JavacASTNode visitCompilationUnit(CompilationUnitTree node, Void p) {
- JCCompilationUnit t = (JCCompilationUnit) node;
- JavacASTNode currnode = new JavacASTNode(t.getStartPosition(),t.getEndPosition(JavacASTViewer.ept));
- currnode.setProperty("root");
- currnode.setType(t.getClass().getSimpleName());
- traverse(currnode,"packageAnnotations",t.packageAnnotations);
- traverse(currnode,"pid",t.pid);
- traverse(currnode,"defs",t.defs);
- return currnode;
- }
- @Override
- public JavacASTNode visitClass(ClassTree node, Void p) {
- JCClassDecl t = (JCClassDecl) node;
- JavacASTNode currnode = new JavacASTNode(t.getStartPosition(),t.getEndPosition(JavacASTViewer.ept));
- traverse(currnode,"extending",t.extending);
- traverse(currnode,"implementing",t.implementing);
- traverse(currnode,"defs",t.defs);
- return currnode;
- }
通过调用节点类的 getStartPosition() 获取开始位置, 调用 getEndPosition() 获取结束位置, 不过调用这个方法需要传递之前保存的 EndPosTable 信息.
定义监听器监听双击事件, 如下:
- package astview.listener;
- import org.eclipse.jface.text.DocumentEvent;
- import org.eclipse.jface.text.IDocumentListener;
- import org.eclipse.jface.viewers.DoubleClickEvent;
- import org.eclipse.jface.viewers.IDoubleClickListener;
- import astview.JavacASTViewer;
- public class ListenerMix implements IDocumentListener,IDoubleClickListener {
- private JavacASTViewer fView;
- public ListenerMix(JavacASTViewer view) {
- fView= view;
- }
- public void dispose() {
- fView= null;
- }
- // ...
- @Override
- public void doubleClick(DoubleClickEvent event) {
- fView.handleDoubleClick();
- }
- }
使用这个监听器, 在 createPartControl() 中为 fViewer 添加监听器, 如下:
fViewer.addDoubleClickListener(fSuperListener);
这样在双击语法树某个节点时, 监听器会调用 fView.hanbndleDoubleClick() 方法对动作做相应的处理, 函数的实现如下:
- protected void performDoubleClick() {
- if (fEditor == null) {
- return;
- }
- ISelection selection = fViewer.getSelection();
- Object obj = ((IStructuredSelection) selection).getFirstElement();
- if(obj!=null && obj instanceof JavacASTNode) {
- JavacASTNode node = (JavacASTNode)obj;
- EditorUtility.selectInEditor(fEditor, node.getStartpos(),node.getEndpos()-node.getStartpos());
- }
- }
调用 EditorUtility 工具类中的 selectInEditor() 方法, 这个方法的定义如下:
- public static void selectInEditor(ITextEditor editor, int offset, int length) {
- IEditorPart active = getActiveEditor();
- if (active != editor) {
- editor.getSite().getPage().activate(editor);
- }
- editor.selectAndReveal(offset, length);
- }
调用 IEditorPart 的 selectAndReveal() 方法, 同时传递开始位置和选中的长度就完成了这个功能.
3, 发布插件
发布 Eclipse 插件非常简单, 网上相关的资料也非常多, 这里就不做过多介绍. 作者将打包好的插件放到了 JavacASTViewer 项目的 zip 目录下, 大家可以下载下来以 Install 的方式安装.
4, 编写 README.md
- # JavacTreeViewer
- ## 1, 项目简介
来源: https://www.cnblogs.com/extjs4/p/12381467.html