1. 程式人生 > >開源碼應用之Eclipse篇

開源碼應用之Eclipse篇

arr avi comment 用戶 net disco .net list 打包

開寫這篇的時候,恰逢Eclpse Mars(4.5)正式公布,最終由日蝕變登火星了,也離我開始基於Eclipse開發產品已經過去10年,這10年間,經歷了Eclipse由私有核心框架到擁抱OSGi, 由單一Java IDE成長為巨無霸式的技術平臺。由純桌面到Web,嵌入式全面開花,個人也經歷了從普通開發人員成長為committer,又離開社區的過程,唯一不變的是:Eclipse依舊是我開發Java唯一的選擇。


對於這樣一個由全世界最smart的一群人貢獻和維護的開源項目(群)。我相信不論什麽熱愛這個行業的project師都能從中獲得收益,這次就談談我基於Eclipse寫的一個小工具。


不知道大家有沒有類似的體會,每到產品公布期截止的時候,team就會開始忙亂的整理Java源碼中的license聲明問題,嚴格統一的開發風格對全部的team來講,基本都是一種奢望。從頭開始不可能,那怎麽辦。不修復吧,不能公布,修復吧。這種爛活沒人願意幹,大概說來。修復Java源碼裏面的license聲明分為下面兩個主流方式:

1. 既然是Java源碼,那就Java上啊,不就讀出文件來。插入或替換嗎?,定位嗎,嗯,文件頭的easy,成員變量型的,得想想...

2. 殺雞焉用牛刀?,組合下Unix裏面的小命令,分分鐘搞定。


兩種方式下的結果我都見過。實話說,的確不怎麽樣。


這件事情簡單嗎?說實話不難,但 Oracle依舊把Java源碼裏的license聲明整成以下這個模樣,就為了把曾經Sun的license聲明改成自己的。


技術分享


這對非常多有代碼格式強迫癥的project師來講。比殺了他們還難受啊。


事實上我並沒有接到這種爛活。我僅僅是思考了下。假設要處理好。該怎麽辦?嗯,這事要搞好,要是能操縱Java源碼的每個部分不即可了?


哇靠,有人立即會跳起來說,這得懂編譯器哪,對。就是編譯器,只是也沒有那麽復雜,也就用了一丁丁點AST知識,不知道AST?哦。哪也沒問題,有Eclipse替你做。


於是我開始動手實現這麽一個能高速修復Java源碼中license聲明的小工具,基本思路是基於Eclipse JDT裏的AST實現。在Java語法這個粒度來改動,並做成一個Eclipse Plug-in,這下大家安裝後。簡單到點個button,就能完畢工作。


詳細實現過程例如以下:


1. 生成一個Eclipse Plug-in項目,選個模版,最簡單的那種,能點toolbar上面的button,彈出個"hello, world"對話框就能夠。

不知道怎麽開發一個Eclipse Plug-in啊。沒關系,看完這篇blog。你就會了。(別忘了好評!)


2. 在Action的回調方法裏面,代碼例如以下。

	public void run(IAction action) {
		license = getLicenseContent(LICENSE_FILE_NAME);
		license_inline = getLicenseContent(LICENSE_INLINE_FILE_NAME);
		if (license_inline.endsWith("\n")) {
			license_inline = license_inline.substring(0, license_inline.length() - 1);
		}
		sum = 0;
		
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		IWorkspaceRoot root = workspace.getRoot();
		IProject[] projects = root.getProjects();
		for (IProject project : projects) {
			try {
				if (project.isOpen()) {
					processProject(project);
				}		
			} catch (Exception e) {
				MessageDialog.openInformation(window.getShell(), "Fix License", "Exception happened, please check the console log.");
				e.printStackTrace();
				return;
			}
		}
		MessageDialog.openInformation(window.getShell(), "Fix License", "All java source files have been processed. Total = " + sum);
	}


首先獲得license的內容,分為主license和行內license,詳細內容這裏就不顯示了。然後獲取Eclipse裏面全部的項目,遍歷每一個項目並處理,這裏僅僅處理打開的項目,假設你有不想處理的項目。關閉即可。


3. 處理項目。

	private void processProject(IProject project) throws Exception {
		if (project.isNatureEnabled("org.eclipse.jdt.core.javanature")) {
			IJavaProject javaProject = JavaCore.create(project);
			IPackageFragment[] packages = javaProject.getPackageFragments();
			for (IPackageFragment mypackage : packages) {
				if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
					for (ICompilationUnit unit : mypackage.getCompilationUnits()) {
						sum = sum + 1;
						processJavaSource(unit);
					}
				}
			}
		}
	}


當然僅僅修復Java項目,沒有Java nature的,一律拋棄。

獲得Java項目後,獲取全部的package,這裏的package和通常意義上Java的package不同。詳細意義看API,就當課後作業。

再進一步,就能夠獲取Java源文件,並取得編譯單元,有了這個,以後的路就有方向了。


4. 處理Java源文件。

	private void processJavaSource(ICompilationUnit unit) throws Exception {
		ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
		IPath path = unit.getPath();
		try {
			bufferManager.connect(path, null);
			ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(path);
			IDocument doc = textFileBuffer.getDocument();
			if ((license !=null) && (license.length() > 0)) {
				processHeadLicense(doc);
			}
			if ((license_inline != null) && (license_inline.length() > 0)) {
				processInlineLicense(doc);
			}
			textFileBuffer.commit(null, false);
		} finally {
			bufferManager.disconnect(path, null);
		}
	}

這裏用到了一些Eclipse Jface text包裏面的東西,和Java裏面常見的文件讀寫API有些不一樣,但基本思想是一致的。

等取到了IDocument對象,就能夠開始正式的license處理。


5. 處理Java文件頭license聲明。

	private void processHeadLicense(IDocument doc) throws Exception {
		CompilationUnit cu = getAST(doc);
		Comment comment = null;
		if (cu.getCommentList().size() == 0) {
			doc.replace(0, 0, license);
		} else {
			comment = (Comment)cu.getCommentList().get(0);
			String firstComment = doc.get().substring(comment.getStartPosition(), comment.getStartPosition() + comment.getLength());
			if (validateHeadLicense(firstComment)) {
				doc.replace(comment.getStartPosition(), comment.getLength(), license);
			} else {
				doc.replace(0, 0, license);
			}		
		}
	}

	private CompilationUnit getAST(IDocument doc) {
		ASTParser parser = ASTParser.newParser(AST.JLS4);
	    parser.setKind(ASTParser.K_COMPILATION_UNIT);
	    parser.setSource(doc.get().toCharArray());
	    parser.setResolveBindings(true);
	    CompilationUnit cu = (CompilationUnit) parser.createAST(null);
	    
	    return cu;
	}


基於AST就能夠得到Java源碼裏面的全部comments,接下來就能夠依據各種情況插入或替換文件頭的license聲明。


6. 成員變量型license聲明。

這樣的license聲明類似以下這個樣例。

public class Demo {
	public static final String COPYRIGHT = "(C) Copyright IBM Corporation 2013, 2014, 2015.";

	public Demo() {
		
	}
	
	public void hello() {
		
	}

}

它的處理方式例如以下。

	private void processInlineLicense(IDocument doc) throws Exception {
		CompilationUnit cu = getAST(doc);
	    cu.recordModifications();
	    AST ast = cu.getAST();

	    if (cu.types().get(0) instanceof TypeDeclaration) {
	    	TypeDeclaration td = (TypeDeclaration)cu.types().get(0);
	    	FieldDeclaration[] fd = td.getFields();
	    	if (fd.length == 0) {
	    		td.bodyDeclarations().add(0, createLiceseInLineField(ast));
	    	} else {
	    		FieldDeclaration firstFd = fd[0];
	    		VariableDeclarationFragment vdf = (VariableDeclarationFragment)firstFd.fragments().get(0);
	    		if (vdf.getName().getIdentifier().equals("COPYRIGHT")) {
	    			td.bodyDeclarations().remove(0);
	    			td.bodyDeclarations().add(0, createLiceseInLineField(ast));
	    		} else {
	    			td.bodyDeclarations().add(0, createLiceseInLineField(ast));
	    		}
	    	}	    	
	    }
	    
		//record changes
		TextEdit edits = cu.rewrite(doc, null);
		edits.apply(doc);
	}
	
	private FieldDeclaration createLiceseInLineField(AST ast) {
		VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
		vdf.setName(ast.newSimpleName("COPYRIGHT"));
		StringLiteral sl = ast.newStringLiteral();
		sl.setLiteralValue(license_inline);
		vdf.setInitializer(sl);
		FieldDeclaration fd = ast.newFieldDeclaration(vdf);
		fd.modifiers().addAll(ast.newModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL));
		fd.setType(ast.newSimpleType(ast.newSimpleName("String")));
		
		return fd;
	}

成員變量類型的license聲明處理起來稍顯麻煩。主要原因是牽扯到Java成員變量的創建和解析,但事實上也不是非常難理解,並且從中能夠學到AST是怎樣精細處理Java類的各個組成部分的。


7. 測試。

啟動一個新的調試Eclipse Plug-in的Eclipse Runtime,導入隨意幾個Java項目,從菜單或工具欄上面選擇“Fix License” action。完畢之後檢查隨意的Java源文件,看看license是否已經修復。


來看看一個簡單的測試結果吧。

這個是修復前的

package com.demo;

public class Demo {
	public Demo() {
		
	}
	
	public void hello() {
		
	}

}


這個是修復後的

/* IBM Confidential
 * OCO Source Materials
 * 
 * (C)Copyright IBM Corporation 2013, 2014, 2015.
 *
 * The source code for this program is not published or otherwise
 * divested of its trade secrets, irrespective of what has been
 * deposited with the U.S. Copyright Office.
*/
package com.demo;

public class Demo {
	public static final String COPYRIGHT = "(C) Copyright IBM Corporation 2013, 2014, 2015.";

	public Demo() {
		
	}
	
	public void hello() {
		
	}

}

8. 打包分發。

這個工具Plug-in能夠按Eclipse的標準插件打包並安裝,或者生成一個Update Site以供用戶在線安裝。


好了。啰嗦了這麽多,到了該結束的時刻。最後一句,這個小工具全部的源碼已經在GitHub上開源,喜歡能夠去下載並測試。源碼裏面附有一份具體安裝的文檔。


工具地址:https://github.com/alexgreenbar/open_tools.git







開源碼應用之Eclipse篇