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

檢查大小寫樣式

編輯:關於JAVA

盡管對涉及文字處理的一些項目來說,前例顯得比較方便,但下面要介紹的項目卻能立即發揮作用,因為它執行的是一個樣式檢查,以確保我們的大小寫形式符合“事實上”的Java樣式標准。它會在當前目錄中打開每個.java文件,並提取出所有類名以及標識符。若發現有不符合Java樣式的情況,就向我們提出報告。
為了讓這個程序正確運行,首先必須構建一個類名,將它作為一個“倉庫”,負責容納標准Java庫中的所有類名。為達到這個目的,需遍歷用於標准Java庫的所有源碼子目錄,並在每個子目錄都運行ClassScanner。至於參數,則提供倉庫文件的名字(每次都用相同的路徑和名字)和命令行開關-a,指出類名應當添加到該倉庫文件中。
為了用程序檢查自己的代碼,需要運行它,並向它傳遞要使用的倉庫文件的路徑與名字。它會檢查當前目錄中的所有類和標識符,並告訴我們哪些沒有遵守典型的Java大寫寫規范。
要注意這個程序並不是十全十美的。有些時候,它可能報告自己查到一個問題。但當我們仔細檢查代碼的時候,卻發現沒有什麼需要更改的。盡管這有點兒煩人,但仍比自己動手檢查代碼中的所有錯誤強得多。
下面列出源代碼,後面有詳細的解釋:

 

//: ClassScanner.java
// Scans all files in directory for classes
// and identifiers, to check capitalization.
// Assumes properly compiling code listings.
// Doesn't do everything right, but is a very
// useful aid.
import java.io.*;
import java.util.*;

class MultiStringMap extends Hashtable {
  public void add(String key, String value) {
    if(!containsKey(key))
      put(key, new Vector());
    ((Vector)get(key)).addElement(value);
  }
  public Vector getVector(String key) {
    if(!containsKey(key)) {
      System.err.println(
        "ERROR: can't find key: " + key);
      System.exit(1);
    }
    return (Vector)get(key);
  }
  public void printValues(PrintStream p) {
    Enumeration k = keys();
    while(k.hasMoreElements()) {
      String oneKey = (String)k.nextElement();
      Vector val = getVector(oneKey);
      for(int i = 0; i < val.size(); i++)
        p.println((String)val.elementAt(i));
    }
  }
}

public class ClassScanner {
  private File path;
  private String[] fileList;
  private Properties classes = new Properties();
  private MultiStringMap 
    classMap = new MultiStringMap(),
    identMap = new MultiStringMap();
  private StreamTokenizer in;
  public ClassScanner() {
    path = new File(".");
    fileList = path.list(new JavaFilter());
    for(int i = 0; i < fileList.length; i++) {
      System.out.println(fileList[i]);
      scanListing(fileList[i]);
    }
  }
  void scanListing(String fname) {
    try {
      in = new StreamTokenizer(
          new BufferedReader(
            new FileReader(fname)));
      // Doesn't seem to work:
      // in.slashStarComments(true);
      // in.slashSlashComments(true);
      in.ordinaryChar('/');
      in.ordinaryChar('.');
      in.wordChars('_', '_');
      in.eolIsSignificant(true);
      while(in.nextToken() != 
            StreamTokenizer.TT_EOF) {
        if(in.ttype == '/')
          eatComments();
        else if(in.ttype == 
                StreamTokenizer.TT_WORD) {
          if(in.sval.equals("class") || 
             in.sval.equals("interface")) {
            // Get class name:
               while(in.nextToken() != 
                     StreamTokenizer.TT_EOF
                     && in.ttype != 
                     StreamTokenizer.TT_WORD)
                 ;
               classes.put(in.sval, in.sval);
               classMap.add(fname, in.sval);
          }
          if(in.sval.equals("import") ||
             in.sval.equals("package"))
            discardLine();
          else // It's an identifier or keyword
            identMap.add(fname, in.sval);
        }
      }
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
  void discardLine() {
    try {
      while(in.nextToken() != 
            StreamTokenizer.TT_EOF
            && in.ttype != 
            StreamTokenizer.TT_EOL)
        ; // Throw away tokens to end of line
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
  // StreamTokenizer's comment removal seemed
  // to be broken. This extracts them:
  void eatComments() {
    try {
      if(in.nextToken() != 
         StreamTokenizer.TT_EOF) {
        if(in.ttype == '/')
          discardLine();
        else if(in.ttype != '*')
          in.pushBack();
        else 
          while(true) {
            if(in.nextToken() == 
              StreamTokenizer.TT_EOF)
              break;
            if(in.ttype == '*')
              if(in.nextToken() != 
                StreamTokenizer.TT_EOF
                && in.ttype == '/')
                break;
          }
      }
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
  public String[] classNames() {
    String[] result = new String[classes.size()];
    Enumeration e = classes.keys();
    int i = 0;
    while(e.hasMoreElements())
      result[i++] = (String)e.nextElement();
    return result;
  }
  public void checkClassNames() {
    Enumeration files = classMap.keys();
    while(files.hasMoreElements()) {
      String file = (String)files.nextElement();
      Vector cls = classMap.getVector(file);
      for(int i = 0; i < cls.size(); i++) {
        String className = 
          (String)cls.elementAt(i);
        if(Character.isLowerCase(
             className.charAt(0)))
          System.out.println(
            "class capitalization error, file: "
            + file + ", class: " 
            + className);
      }
    }
  }
  public void checkIdentNames() {
    Enumeration files = identMap.keys();
    Vector reportSet = new Vector();
    while(files.hasMoreElements()) {
      String file = (String)files.nextElement();
      Vector ids = identMap.getVector(file);
      for(int i = 0; i < ids.size(); i++) {
        String id = 
          (String)ids.elementAt(i);
        if(!classes.contains(id)) {
          // Ignore identifiers of length 3 or
          // longer that are all uppercase
          // (probably static final values):
          if(id.length() >= 3 &&
             id.equals(
               id.toUpperCase()))
            continue;
          // Check to see if first char is upper:
          if(Character.isUpperCase(id.charAt(0))){
            if(reportSet.indexOf(file + id)
                == -1){ // Not reported yet
              reportSet.addElement(file + id);
              System.out.println(
                "Ident capitalization error in:"
                + file + ", ident: " + id);
            }
          }
        }
      }
    }
  }
  static final String usage =
    "Usage: \n" + 
    "ClassScanner classnames -a\n" +
    "\tAdds all the class names in this \n" +
    "\tdirectory to the repository file \n" +
    "\tcalled 'classnames'\n" +
    "ClassScanner classnames\n" +
    "\tChecks all the java files in this \n" +
    "\tdirectory for capitalization errors, \n" +
    "\tusing the repository file 'classnames'";
  private static void usage() {
    System.err.println(usage);
    System.exit(1);
  }
  public static void main(String[] args) {
    if(args.length < 1 || args.length > 2)
      usage();
    ClassScanner c = new ClassScanner();
    File old = new File(args[0]);
    if(old.exists()) {
      try {
        // Try to open an existing 
        // properties file:
        InputStream oldlist =
          new BufferedInputStream(
            new FileInputStream(old));
        c.classes.load(oldlist);
        oldlist.close();
      } catch(IOException e) {
        System.err.println("Could not open "
          + old + " for reading");
        System.exit(1);
      }
    }
    if(args.length == 1) {
      c.checkClassNames();
      c.checkIdentNames();
    }
    // Write the class names to a repository:
    if(args.length == 2) {
      if(!args[1].equals("-a"))
        usage();
      try {
        BufferedOutputStream out =
          new BufferedOutputStream(
            new FileOutputStream(args[0]));
        c.classes.save(out,
          "Classes found by ClassScanner.java");
        out.close();
      } catch(IOException e) {
        System.err.println(
          "Could not write " + args[0]);
        System.exit(1);
      }
    }
  }
}

class JavaFilter implements FilenameFilter {
  public boolean accept(File dir, String name) {
    // Strip path information:
    String f = new File(name).getName();
    return f.trim().endsWith(".java");
  }
} ///:~


MultiStringMap類是個特殊的工具,允許我們將一組字串與每個鍵項對應(映射)起來。和前例一樣,這裡也使用了一個散列表(Hashtable),不過這次設置了繼承。該散列表將鍵作為映射成為Vector值的單一的字串對待。add()方法的作用很簡單,負責檢查散列表裡是否存在一個鍵。如果不存在,就在其中放置一個。getVector()方法為一個特定的鍵產生一個Vector;而printValues()將所有值逐個Vector地打印出來,這對程序的調試非常有用。
為簡化程序,來自標准Java庫的類名全都置入一個Properties(屬性)對象中(來自標准Java庫)。記住Properties對象實際是個散列表,其中只容納了用於鍵和值項的String對象。然而僅需一次方法調用,我們即可把它保存到磁盤,或者從磁盤中恢復。實際上,我們只需要一個名字列表,所以為鍵和值都使用了相同的對象。
針對特定目錄中的文件,為找出相應的類與標識符,我們使用了兩個MultiStringMap:classMap以及identMap。此外在程序啟動的時候,它會將標准類名倉庫裝載到名為classes的Properties對象中。一旦在本地目錄發現了一個新類名,也會將其加入classes以及classMap。這樣一來,classMap就可用於在本地目錄的所有類間遍歷,而且可用classes檢查當前標記是不是一個類名(它標記著對象或方法定義的開始,所以收集接下去的記號——直到碰到一個分號——並將它們都置入identMap)。
ClassScanner的默認構建器會創建一個由文件名構成的列表(采用FilenameFilter的JavaFilter實現形式,參見第10章)。隨後會為每個文件名都調用scanListing()。
在scanListing()內部,會打開源碼文件,並將其轉換成一個StreamTokenizer。根據Java幫助文檔,將true傳遞給slashStartComments()和slashSlashComments()的本意應當是剝除那些注釋內容,但這樣做似乎有些問題(在Java 1.0中幾乎無效)。所以相反,那些行被當作注釋標記出去,並用另一個方法來提取注釋。為達到這個目的,'/'必須作為一個原始字符捕獲,而不是讓StreamTokeinzer將其當作注釋的一部分對待。此時要用ordinaryChar()方法指示StreamTokenizer采取正確的操作。同樣的道理也適用於點號('.'),因為我們希望讓方法調用分離出單獨的標識符。但對下劃線來說,它最初是被StreamTokenizer當作一個單獨的字符對待的,但此時應把它留作標識符的一部分,因為它在static final值中以TT_EOF等等形式使用。當然,這一點只對目前這個特殊的程序成立。wordChars()方法需要取得我們想添加的一系列字符,把它們留在作為一個單詞看待的記號中。最後,在解析單行注釋或者放棄一行的時候,我們需要知道一個換行動作什麼時候發生。所以通過調用eollsSignificant(true),換行符(EOL)會被顯示出來,而不是被StreamTokenizer吸收。
scanListing()剩余的部分將讀入和檢查記號,直至文件尾。一旦nextToken()返回一個final static值——StreamTokenizer.TT_EOF,就標志著已經抵達文件尾部。
若記號是個'/',意味著它可能是個注釋,所以就調用eatComments(),對這種情況進行處理。我們在這兒唯一感興趣的其他情況是它是否為一個單詞,當然還可能存在另一些特殊情況。
如果單詞是class(類)或interface(接口),那麼接著的記號就應當代表一個類或接口名字,並將其置入classes和classMap。若單詞是import或者package,那麼我們對這一行剩下的東西就沒什麼興趣了。其他所有東西肯定是一個標識符(這是我們感興趣的),或者是一個關鍵字(對此不感興趣,但它們采用的肯定是小寫形式,所以不必興師動眾地檢查它們)。它們將加入到identMap。
discardLine()方法是一個簡單的工具,用於查找行末位置。注意每次得到一個新記號時,都必須檢查行末。
只要在主解析循環中碰到一個正斜槓,就會調用eatComments()方法。然而,這並不表示肯定遇到了一條注釋,所以必須將接著的記號提取出來,檢查它是一個正斜槓(那麼這一行會被丟棄),還是一個星號。但假如兩者都不是,意味著必須在主解析循環中將剛才取出的記號送回去!幸運的是,pushBack()方法允許我們將當前記號“壓回”輸入數據流。所以在主解析循環調用nextToken()的時候,它能正確地得到剛才送回的東西。
為方便起見,classNames()方法產生了一個數組,其中包含了classes集合中的所有名字。這個方法未在程序中使用,但對代碼的調試非常有用。
接下來的兩個方法是實際進行檢查的地方。在checkClassNames()中,類名從classMap提取出來(請記住,classMap只包含了這個目錄內的名字,它們按文件名組織,所以文件名可能伴隨錯誤的類名打印出來)。為做到這一點,需要取出每個關聯的Vector,並遍歷其中,檢查第一個字符是否為小寫。若確實為小寫,則打印出相應的出錯提示消息。
在checkIdentNames()中,我們采用了一種類似的方法:每個標識符名字都從identMap中提取出來。如果名字不在classes列表中,就認為它是一個標識符或者關鍵字。此時會檢查一種特殊情況:如果標識符的長度等於3或者更長,而且所有字符都是大寫的,則忽略此標識符,因為它可能是一個static final值,比如TT_EOF。當然,這並不是一種完美的算法,但它假定我們最終會注意到任何全大寫標識符都是不合適的。
這個方法並不是報告每一個以大寫字符開頭的標識符,而是跟蹤那些已在一個名為reportSet()的Vector中報告過的。它將Vector當作一個“集合”對待,告訴我們一個項目是否已在那個集合中。該項目是通過將文件名和標識符連接起來生成的。若元素不在集合中,就加入它,然後產生報告。
程序列表剩下的部分由main()構成,它負責控制命令行參數,並判斷我們是准備在標准Java庫的基礎上構建由一系列類名構成的“倉庫”,還是想檢查已寫好的那些代碼的正確性。不管在哪種情況下,都會創建一個ClassScanner對象。
無論准備構建一個“倉庫”,還是准備使用一個現成的,都必須嘗試打開現有倉庫。通過創建一個File對象並測試是否存在,就可決定是否打開文件並在ClassScanner中裝載classes這個Properties列表(使用load())。來自倉庫的類將追加到由ClassScanner構建器發現的類後面,而不是將其覆蓋。如果僅提供一個命令行參數,就意味著自己想對類名和標識符名字進行一次檢查。但假如提供兩個參數(第二個是"-a"),就表明自己想構成一個類名倉庫。在這種情況下,需要打開一個輸出文件,並用Properties.save()方法將列表寫入一個文件,同時用一個字串提供文件頭信息。

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