引言
Eclipse 為程序員提供了強大的文本搜索功能,程序員可以方便 的在工作空間中搜索到需要的 JAVA 代碼或者文本。但是有時候,程序員希望在 .class 文件源碼或者普通文本文件中搜索某個字符串,而這些文件包含於 Jar 文件中,此時 Eclipse 就無法滿足要求。比如,用戶試圖尋找 UI 上顯示的某 字符串的定義位置,這就需要在 Jar 文件內的普通文本文件 , 以及 .class 文 件源碼中搜索。這些 Jar 文件包含於項目類路徑中,這個功能在 RCP 開發中是 經常需要的,而 Eclipse 目前還未提供這個特性。本文通過使用 JDT(Java Development Toolkit)中與 Jar 相關的接口,解決了這個問題,並給出示例及 程序。
Eclipse 中搜索的原理
Eclipse 采用 Lucene 技術開發其搜索內核, 該內核通過對關鍵字進行索引,快速定位目標文件。例如,Eclipse 會對 JAVA 源文件中的類名、字段名、方法名等進行索引,當程序員使用 Open Type 功能 (快捷鍵:CTRL+SHIFT+T)進行類搜索時,便可以通過類名這個索引字段進行快 速搜索 ; 在使用 JAVA 搜索(Java Search)功能時,Eclipse 也會讓用戶指定 具體的索引字段(Search For),如可以選擇方法名、類名、字段名、包名、構 造器名等,Eclipse 會根據選擇的索引字段與用戶的輸入,快速搜索到源代碼。
Eclipse 在提供 Jar 源代碼搜索方面的限制
Eclipse 提供了文 件搜索(File Search)的功能,用來搜索指定范圍 ( 項目、工作空間等 ) 內 的文本文件。這個功能並沒有根據某些特殊關鍵字進行索引。因為對於任意字符 串的搜索,是無法找到特定關鍵字進行索引的。因此為了提高搜索效率, Eclipse 對於任意字符串的搜索范圍僅限於用戶編寫的文本文件,而沒有對項目 所依賴的 Jar 文件中的類的源代碼進行搜索。因為 Jar 源代碼的數量往往數量 龐大,搜索它們將是一個相當費時的操作。但是在很多情況下,程序員有必要進 行類源代碼的搜索,通過查看需要的源代碼解決一些問題,Eclipse 目前提供的 搜索功能就無法滿足這樣的需求。
JDT 中 Jar 文件相關的類結構圖分析
下面兩圖展示了 JDT 中與 Jar 文件處理相關的類,從圖中,可以清晰 的了解它們的層次、包含和對應關系。
圖 1. JDT 中與 Jar 文件相關的 UML 類結構圖
圖 2. JDT 中與 Jar 文件相關的類結構對應圖
上圖描述了各個節點之間的層次、包含和對應關系,了解這些信息, 對文章下一部分的閱讀是必要的。Java 項目,Jar 文件,Jar 文件中的包,Jar 文件中的普通文件夾,class 文件和非 class 文件分別對應於 IJavaProject, JarPackageFragmentRoot,JarPackageFragment,JarEntryDirectory, IClassFile 以及 JarEntryFile。IJavaProject 包含了多個 JarPackageFragmentRoot。而每個 JarPackageFragmentRoot 包含多個的 JarPackageFragment、JarEntryDirectory 和 JarEntryFile。類似的, JarPackageFragment 包含多個的 IClassFile 和 JarEntryFile;每個 JarEntryDirectory 包含多個的 JarEntryFile 和 JarEntryDirectory。
Jar 源代碼搜索解決方案
本文將用一個例子程序,逐步介紹如何 實現 Jar 源代碼的搜索,進一步了解 JDT 提供的 API。解決方案的主要邏輯為 :遍歷工作空間下的所有 JAVA 項目,並且逐一獲得項目所依賴的 Jar 文件列 表。然後遍歷該列表獲得每個 Jar 文件中的 class 文件源碼和非 class 文件 的文本內容,使用正則表達式進行匹配查找。最後輸出結果的類名、文件路徑、 匹配的起始位置和匹配的字符串長度等信息。
結合這個邏輯以及 JDT 中 Jar 文件相關的類結構,上述解決方案中的主要技術問題包括:
ResourcesPlugin.getWorkspace().getRoot().getProjects() 可以獲得 工作空間下的所有項目,類型為 IProject。那麼如何將 IProject 對象轉換為 JAVA 項目對應的 IJavaProject 對象?(文中步驟 1 解決該問題)
獲 得 IJavaProject 對象後,如何獲得它所依賴的 Jar 文件列表,也就是 JarPackageFragmentRoot 對象列表?(文中步驟 2 解決該問題)
獲得 JarPackageFragmentRoot 對象後,如何獲得它下面的包(JarPackageFragment ),又如何獲得包下的 class 文件(IClassFile)和非 class 文本文件 (JarFileEntry)?(文中步驟 3、5 解決該問題)
如何獲得 IClassFile 的源代碼,又如何獲得 JarFileEntry 的文本內容?(文中步驟 4 、6 解決該問題)
下面將對各個步驟逐一地進行分析,並且一一解決上 面提到的問題。
步驟 1. 轉換 IProject 為 IJavaProject
Eclipse 工作空間下,可能存在許多類型的項目,有 JAVA 項目也有非 JAVA 項目,為了獲得項目依賴的 Jar 文件,該項目必須是 Java 類型的項目。以下代碼通過調用 JDT 提供的接口,獲得 JAVA 項目列表。
清單 1. 獲得工作空間下的所有 JAVA 項目
/* 獲得工作 空間下的所有項目 */
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
.getProjects();
if (projects != null)
{
for (IProject p : projects)
{
/* 嘗試轉換普通項目為 JAVA 項目 */
IJavaProject create = JavaCore.create(p);
/* 判斷項目是否是 JAVA 項目 */
if (create != null && create.exists())
{
/* 操 作 JAVA 項目 */
…
}
}
}
步驟 2. 獲得依賴的 JarPackageFragmentRoot 列表
獲得 IJavaProject 對象後,需要得到它 所依賴的 Jar 文件列表。在 JDT 中,Jar 文件對應的類為 JarPackageFragmentRoot,下面一段程序用於獲得 Jar 文件列表。
清單 2. 獲得 JAVA 項目依賴的 Jar 文件列表的代碼
IJavaProject project= …
IJavaElement[] children = project.getChildren();
if (children != null)
{
/* 遍歷 project 下的 Java 元素 */
for (IJavaElement ele : children)
{
/* 判斷是否 是 JarPackageFragmentRoot 對象 */
if (ele instanceof JarPackageFragmentRoot)
{
/* 操作此 jar 文件 */
…
}
}
}
大多數時候程序員想要搜索的范圍並不包含 JRE 庫的源代碼,因此為了提高搜索效率,需要屏蔽 JRE 庫源代碼的搜索,下面一 段程序展示如何實現這個需求。
清單 3. 屏蔽 JRE 庫的源碼搜索
JarPackageFragmentRoot jarFile = … ;
IJavaProject project = … ;
/**
* 判斷此 Jar 是否在 JRE 庫中。如果是,將其屏蔽 , 以提高效率
*/
IClasspathEntry rawClasspathEntry = jarFile.getRawClasspathEntry ();
IClasspathContainer classpathContainer = JavaCore.getClasspathContainer(
rawClasspathEntry.getPath(), project);
/* 判斷此 jar 是否屬 於項目默認的 JRE 庫。如果是,不檢查該 jar*/
if (classpathContainer.getKind() == IClasspathContainer.K_DEFAULT_SYSTEM)
{
/* 跳過此 jar 的搜索 */
}
步驟 3. 獲得 JarPackageFragmentRoot 中的 IClassFile 列表
如果想獲得 .class 文 件的源碼,就需要獲得 .class 文件對應的 IClassFile 對象,下面的程序展示 了如何從 JarPackageFragmentRoot 對象開始遍歷,獲得其包含的 PackageFragment 對象,又從 PackageFragment 對象中獲得 IClassFile 對象 列表。
清單 4. 獲取所有類文件的代碼
JarPackageFragmentRoot root = …
IJavaElement[] children = root.getChildren();
if (children != null)
{
/* 遍歷 JarPackageFragmentRoot 下的 所有包元素 */
for (IJavaElement ele : children)
{
if (ele instanceof PackageFragment)
{
IJavaElement[] classes = ((PackageFragment) ele).getChildren();
/* 遍歷 PackageFragment 下的所有類元素 */
for (IJavaElement cls : children)
{
if (ele instanceof IClassFile)
{
/* 獲得 IClassFile 對象進行操作 */
}
}
}
}
}
需要注意的是,JDT 中 Jar 文件 中的包(package)對應的類為 JarPackageFragment,但是該類為 default 類 型,無法引用,可以先將它轉換為它的父類 PackageFragment,然後進行處理。
步驟 4. 獲得 IClassFile 源代碼並比較
得到 IClassFile 對象 後,需要獲得其源碼 .IClassFile 提供了非常方便的接口: getSource。應用 該方法可以獲得源碼字符串。如果該方法的輸出值為 null,說明這個類還未綁 定源代碼。這種情況下可以通過雙擊 .class 文件,點擊 Change Attached Source 按鈕進行源代碼的綁定。下面的程序展示了如何根據用戶輸入的正則表 達式進行比較搜索。
清單 5. 獲取類文件源碼並比較
IClassFile cf = … ;
Pattern searchPattern = Pattern.compile("用戶輸入的正則表達式");
/* 獲得 IClassFile 源碼 */
String source = cf.getSource ();
if (source != null)
{
Matcher matcher = searchPattern.matcher(source);
while (matcher.find())
{
/* 獲得偏移量 */
int offset = matcher.start();
String group = matcher.group();
/* 獲得長度 */
int length = group.length ();
}
}
步驟 5. 獲得 JarPackageFragmentRoot 中的非 JAVA 資源
Jar 文件中 JAVA 資源主要 是 .java 文件和 .class 文件。Jar 文件中非 JAVA 的資源,對應的類為 IJarEntryResource,比如 Jar 中的 .properties 文件、META-INF 文件夾、 META-INF 文件夾下的 MANIFEST.MF 等,都屬於非 JAVA 資源,這些非 JAVA 資 源可以存放於 JarPackageFragmentRoot 下,也可以存放於 JarPackageFragment 下。下面的程序展示如何遍歷獲得 JarPackageFragmentRoot 下的所有非 JAVA 資源。
清單 6. 獲得非類文 件的資源
/* 注:ele 也可以是 PackageFragment, 它們都擁 有 getNonJavaResources 方法 */
JarPackageFragmentRoot ele = … ;
Object[] nonjavares = ele.getNonJavaResources();
if (nonjavares != null)
{
for (Object o : nonjavares)
{
/* JarEntryDirectory 相當於 META-INF 文件夾 */
if (o instanceof JarEntryDirectory)
{
JarEntryDirectory ff = (JarEntryDirectory)o;
IJarEntryResource[] children = ff.getChildren();
for (IJarEntryResource e : children)
{
if (e instanceof JarEntryDirectory)
{
/* 這裡需要遞歸處 理 */
}
else if (e instanceof JarEntryFile)
{
/* 處理該文本文件 */
}
}
}
/* JarEntryFile 相當於 META-INF 文件夾下的 MANIFEST.MF 或者 .properties 文件等 */
else if (o instanceof JarEntryFile)
{
JarEntryFile ff = (JarEntryFile) o;
/* 處理該文本文件 */
}
}
}
步驟 6. 獲得非 JAVA 資源的源代碼並比較
在本文的代碼示例中只展示從 JarEntryFile 獲得源代碼的方法,如果需要使用其他類型的非 JAVA 資源的獲 取方法,請查看附件中的源碼。
清單 7. 獲得非類文件的文本內容並且 比較
/**
* 該方法用於獲得 JarEntryFile 源代碼並 且比較獲得結果
*/
private void cooperate (JarEntryFile ff, Pattern searchPattern)
{
ByteArrayInputStream contents;
try
{
contents = (ByteArrayInputStream) ff.getContents ();
byte[] bs = new byte[contents.available ()];
contents.read(bs);
String con = new String(bs);
Matcher matcher = searchPattern.matcher(con);
while (matcher.find())
{
/* 獲得結果的偏移和長度 */
int start = matcher.start();
int length = matcher.group().length();
}
}
catch (Exception e)
{
}
}
步驟 7. 輸出結果
為了簡單,程序會將結果打 印到控制台上,包括結果中的偏移量、長度、以及查找到源代碼路徑。
具體應用環境 ---RCP 中查找源碼
在 RCP 二次開發中,有時候非常需要 查看已有 UI 的源碼,供程序員參考使用。下面將使用前面開發的例子搜索包含 UI 上某字符串的源文件或源代碼。一般來說,界面上的字符串都被存放於 .properties 文件,方便修改和多語言處理。由於 .properties 中可能出現占 位符,界面顯示的是處理占位符後的結果,所以需要選取合適的字符串進行搜索 。搜索到 .properties 文件後,就根據該 .properties 文件所在的包名,和自 身的文件名,搜索引用該 properties 文件的類。如對於 com.ibm.wise.A_zh_CN.propertis 文件,搜索 com.ibm.wise.A 即可,具體原 因可以搜索 ResourceBundle 的相關資料進行查閱。下面展示這一搜索過程。
步驟 1. 根據 UI 上的字符串獲得其 properties 文件所在
圖 3. 要搜索的 UI 字符串
例子將試圖搜索包含“This section provides general information about” 字符串的 properties 文件,如果未搜索到,可以 適當縮短字符串長度。
圖 4. 輸入 UI 字符串的程序界面
點擊 OK 就可以進行搜索。
圖 5. 屬性文件搜索結果(用時 3.23 秒)
結果顯示了包含該字符串的 properties 文件路徑,以及字符串在該文件中 出現的起始位置和長度信息。該文件包含於 Jar 文件中。根據搜索到的路徑, 打開文件查看源碼。
圖 6. 屬性文件源代碼
圖中灰色背景部分就是需要查找的字符串。
步驟 2. 根據 properties 文件名獲得引用該 properties 文件的類
在獲得 properties 文件名之後,根據搜索到的 properties 文件名(在本文的例子中 是 gui.properties),搜索引用該屬性文件的 java 類 , 輸入查詢的字符串為 :com.ibm.btools.blm.ui.attributesview.resource.gui。
圖 7. 輸入 使用屬性文件的字符串程序界面
圖 8. 類搜索結果(用時 2.43 秒)
通過 Open Type 功能,輸入 BLMAttributesviewMessageKeys,就可 以找到此 .class 文件,打開 .class 文件就可以查看它的源代碼。
其 他接口簡單介紹
這裡簡單的介紹了 JDT 中與 Jar 相關的類的其他有用 接口,如表 1 所示。
表 1. 其他接口介紹
類
接口
IClassFile
isClass():boolean 判斷是否是 class 類型
isInterface():boolean 判斷是否是 interface 類型
PackageFragment
createCompilationUnit ():ICompilationUnit 新建 JAVA 文件
delete():void 刪除此包
getClassFile():IClassFile 得到包下某個 class 文件
getClassFiles():IClassFile[] 得到包下所有 class 文件
getCompilationUnit():ICompilationUnit 得到包下某個 java 文件
getCompilationUnits():ICompilationUnit[] 得到包下所有 java 文件
rename():void 重命名
JarPackageFragmentRoot
getJar():ZipFile 得到 jar 文件對應的 ZipFile
getKind ():int 可以是 IPackageFragmentRoot.K_SOURCE 或者 IPackageFragmentRoot.K_BINARY,表示是源代碼類型還是二進制類型
isArchive():boolean 判斷是否是壓縮文件
isReadOnly():boolean 判 斷是否只讀
結束語
在進行二次開發時,通 過查看源代碼可以很好的幫助程序員了解原有系統,同時對於查找和分析代碼漏 洞也有很大的幫助。該文章簡單的介紹了如何使用 JDT 提供的接口進行源代碼 的搜索,文章內容僅供參考,有興趣的朋友可以參考該文章的實現,進行進一步 的優化,提高搜索效率,或者做成實用的插件發布,相信會受到很多 Java 程序 員的喜愛。
原文地址:http://www.ibm.com/developerworks/cn/opensource/os-ecl- jdtsearch/