1. 程式人生 > >第3篇-如何編寫一個面試時能拿的出手的開源專案?

第3篇-如何編寫一個面試時能拿的出手的開源專案?

前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、專案簡介
以外掛的形式直觀的檢視基於OpenJDK的編譯器Javac的抽象語法樹。
## 2、專案描述
可以在Eclipse中以外掛的方式安裝、檢視Javac的抽象語法樹結構。類似於Eclipse AST View外掛。Eclipse AST View外掛顯示的是ECJ編譯器的抽象語法樹,而JavacASTViewer顯示的是基於OpenJDK的編譯器Javac的抽象語法樹結構。
## 3、用法
可直接下載專案原始碼,自己匯出Eclipse外掛包,或者下載本專案中zip目錄下作者打包好的外掛包,以Install的方式安裝。
目前只測試了Eclipse IDE for RCP and RAP Developers 4.14.0版本。

 

這樣一個外掛就開發完了,不過後續還需要增加更多的測試用例,以及完善更多的功能。