程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 基於JDT的JAR源代碼搜索

基於JDT的JAR源代碼搜索

編輯:關於JAVA

引言

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/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved