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

提取代碼列表

編輯:關於JAVA

對於本書每一個完整的代碼列表(不是代碼段),大家無疑會注意到它們都用特殊的注釋記號起始與結束('//:'和'///:~')。之所以要包括這種標志信息,是為了能將代碼從本書自動提取到兼容的源碼文件中。在我的前一本書裡,我設計了一個系統,可將測試過的代碼文件自動合並到書中。但對於這本書,我發現一種更簡便的做法是一旦通過了最初的測試,就把代碼粘貼到書中。而且由於很難第一次就編譯通過,所以我在書的內部編輯代碼。但如何提取並測試代碼呢?這個程序就是關鍵。如果你打算解決一個文字處理的問題,那麼它也很有利用價值。該例也演示了String類的許多特性。
我首先將整本書都以ASCII文本格式保存成一個獨立的文件。CodePackager程序有兩種運行模式(在usageString有相應的描述):如果使用-p標志,程序就會檢查一個包含了ASCII文本(即本書的內容)的一個輸入文件。它會遍歷這個文件,按照注釋記號提取出代碼,並用位於第一行的文件名來決定創建文件使用什麼名字。除此以外,在需要將文件置入一個特殊目錄的時候,它還會檢查package語句(根據由package語句指定的路徑選擇)。
但這樣還不夠。程序還要對包(package)名進行跟蹤,從而監視章內發生的變化。由於每一章使用的所有包都以c02,c03,c04等等起頭,用於標記它們所屬的是哪一章(除那些以com起頭的以外,它們在對不同的章進行跟蹤的時候會被忽略)——只要每一章的第一個代碼列表包含了一個package,所以CodePackager程序能知道每一章發生的變化,並將後續的文件放到新的子目錄裡。
每個文件提取出來時,都會置入一個SourceCodeFile對象,隨後再將那個對象置入一個集合(後面還會詳盡講述這個過程)。這些SourceCodeFile對象可以簡單地保存在文件中,那正是本項目的第二個用途。如果直接調用CodePackager,不添加-p標志,它就會將一個“打包”文件作為輸入。那個文件隨後會被提取(釋放)進入單獨的文件。所以-p標志的意思就是提取出來的文件已被“打包”(packed)進入這個單一的文件。
但為什麼還要如此麻煩地使用打包文件呢?這是由於不同的計算機平台用不同的方式在文件裡保存文本信息。其中最大的問題是換行字符的表示方法;當然,還有可能存在另一些問題。然而,Java有一種特殊類型的IO數據流——DataOutputStream——它可以保證“無論數據來自何種機器,只要使用一個DataInputStream收取這些數據,就可用本機正確的格式保存它們”。也就是說,Java負責控制與不同平台有關的所有細節,而這正是Java最具魅力的一點。所以-p標志能將所有東西都保存到單一的文件裡,並采用通用的格式。用戶可從Web下載這個文件以及Java程序,然後對這個文件運行CodePackager,同時不指定-p標志,文件便會釋放到系統中正確的場所(亦可指定另一個子目錄;否則就在當前目錄創建子目錄)。為確保不會留下與特定平台有關的格式,凡是需要描述一個文件或路徑的時候,我們就使用File對象。除此以外,還有一項特別的安全措施:在每個子目錄裡都放入一個空文件;那個文件的名字指出在那個子目錄裡應找到多少個文件。
下面是完整的代碼,後面會對它進行詳細的說明:

 

//: CodePackager.java
// "Packs" and "unpacks" the code in "Thinking 
// in Java" for cross-platform distribution.
/* Commented so CodePackager sees it and starts
   a new chapter directory, but so you don't 
   have to worry about the directory where this
   program lives:
package c17;
*/
import java.util.*;
import java.io.*;

class Pr {
  static void error(String e) {
    System.err.println("ERROR: " + e);
    System.exit(1);
  }
}

class IO {
  static BufferedReader disOpen(File f) {
    BufferedReader in = null;
    try {
      in = new BufferedReader(
        new FileReader(f));
    } catch(IOException e) {
      Pr.error("could not open " + f);
    }
    return in;
  }
  static BufferedReader disOpen(String fname) {
    return disOpen(new File(fname));
  }
  static DataOutputStream dosOpen(File f) {
    DataOutputStream in = null;
    try {
      in = new DataOutputStream(
        new BufferedOutputStream(
          new FileOutputStream(f)));
    } catch(IOException e) {
      Pr.error("could not open " + f);
    }
    return in;
  }
  static DataOutputStream dosOpen(String fname) {
    return dosOpen(new File(fname));
  }
  static PrintWriter psOpen(File f) {
    PrintWriter in = null;
    try {
      in = new PrintWriter(
        new BufferedWriter(
          new FileWriter(f)));
    } catch(IOException e) {
      Pr.error("could not open " + f);
    }
    return in;
  }
  static PrintWriter psOpen(String fname) {
    return psOpen(new File(fname));
  }
  static void close(Writer os) {
    try {
      os.close();
    } catch(IOException e) {
      Pr.error("closing " + os);
    }
  }
  static void close(DataOutputStream os) {
    try {
      os.close();
    } catch(IOException e) {
      Pr.error("closing " + os);
    }
  }
  static void close(Reader os) {
    try {
      os.close();
    } catch(IOException e) {
      Pr.error("closing " + os);
    }
  }
}

class SourceCodeFile {
  public static final String 
    startMarker = "//:", // Start of source file
    endMarker = "} ///:~", // End of source
    endMarker2 = "}; ///:~", // C++ file end
    beginContinue = "} ///:Continued",
    endContinue = "///:Continuing",
    packMarker = "###", // Packed file header tag
    eol = // Line separator on current system
      System.getProperty("line.separator"),
    filesep = // System's file path separator
      System.getProperty("file.separator");
  public static String copyright = "";
  static {
    try {
      BufferedReader cr =
        new BufferedReader(
          new FileReader("Copyright.txt"));
      String crin;
      while((crin = cr.readLine()) != null)
        copyright += crin + "\n";
      cr.close();
    } catch(Exception e) {
      copyright = "";
    }
  }
  private String filename, dirname,
    contents = new String();
  private static String chapter = "c02";
  // The file name separator from the old system:
  public static String oldsep;
  public String toString() {
    return dirname + filesep + filename;
  }
  // Constructor for parsing from document file:
  public SourceCodeFile(String firstLine, 
      BufferedReader in) {
    dirname = chapter;
    // Skip past marker:
    filename = firstLine.substring(
        startMarker.length()).trim();
    // Find space that terminates file name:
    if(filename.indexOf(' ') != -1)
      filename = filename.substring(
          0, filename.indexOf(' '));
    System.out.println("found: " + filename);
    contents = firstLine + eol;
    if(copyright.length() != 0)
      contents += copyright + eol;
    String s;
    boolean foundEndMarker = false;
    try {
      while((s = in.readLine()) != null) {
        if(s.startsWith(startMarker))
          Pr.error("No end of file marker for " +
            filename);
        // For this program, no spaces before 
        // the "package" keyword are allowed
        // in the input source code:
        else if(s.startsWith("package")) {
          // Extract package name:
          String pdir = s.substring(
            s.indexOf(' ')).trim();
          pdir = pdir.substring(
            0, pdir.indexOf(';')).trim();
          // Capture the chapter from the package
          // ignoring the 'com' subdirectories:
          if(!pdir.startsWith("com")) {
            int firstDot = pdir.indexOf('.');
            if(firstDot != -1)
              chapter = 
                pdir.substring(0,firstDot);
            else
              chapter = pdir;
          }
          // Convert package name to path name:
          pdir = pdir.replace(
            '.', filesep.charAt(0));
          System.out.println("package " + pdir);
          dirname = pdir;
        }
        contents += s + eol;
        // Move past continuations:
        if(s.startsWith(beginContinue))
          while((s = in.readLine()) != null)
            if(s.startsWith(endContinue)) {
              contents += s + eol;
              break;
            }
        // Watch for end of code listing:
        if(s.startsWith(endMarker) ||
           s.startsWith(endMarker2)) {
          foundEndMarker = true;
          break;
        }
      }
      if(!foundEndMarker)
        Pr.error(
          "End marker not found before EOF");
      System.out.println("Chapter: " + chapter);
    } catch(IOException e) {
      Pr.error("Error reading line");
    }
  }
  // For recovering from a packed file:
  public SourceCodeFile(BufferedReader pFile) {
    try {
      String s = pFile.readLine();
      if(s == null) return;
      if(!s.startsWith(packMarker))
        Pr.error("Can't find " + packMarker
          + " in " + s);
      s = s.substring(
        packMarker.length()).trim();
      dirname = s.substring(0, s.indexOf('#'));
      filename = s.substring(s.indexOf('#') + 1);
      dirname = dirname.replace(
        oldsep.charAt(0), filesep.charAt(0));
      filename = filename.replace(
        oldsep.charAt(0), filesep.charAt(0));
      System.out.println("listing: " + dirname 
        + filesep + filename);
      while((s = pFile.readLine()) != null) {
        // Watch for end of code listing:
        if(s.startsWith(endMarker) ||
           s.startsWith(endMarker2)) {
          contents += s;
          break;
        }
        contents += s + eol;
      }
    } catch(IOException e) {
      System.err.println("Error reading line");
    }
  }
  public boolean hasFile() { 
    return filename != null; 
  }
  public String directory() { return dirname; }
  public String filename() { return filename; }
  public String contents() { return contents; }
  // To write to a packed file:
  public void writePacked(DataOutputStream out) {
    try {
      out.writeBytes(
        packMarker + dirname + "#" 
        + filename + eol);
      out.writeBytes(contents);
    } catch(IOException e) {
      Pr.error("writing " + dirname + 
        filesep + filename);
    }
  }
  // To generate the actual file:
  public void writeFile(String rootpath) {
    File path = new File(rootpath, dirname);
    path.mkdirs();
    PrintWriter p =
      IO.psOpen(new File(path, filename));
    p.print(contents);
    IO.close(p);
  }
}

class DirMap {
  private Hashtable t = new Hashtable();
  private String rootpath;
  DirMap() {
    rootpath = System.getProperty("user.dir");
  }
  DirMap(String alternateDir) {
    rootpath = alternateDir;
  }
  public void add(SourceCodeFile f){
    String path = f.directory();
    if(!t.containsKey(path))
      t.put(path, new Vector());
    ((Vector)t.get(path)).addElement(f);
  }
  public void writePackedFile(String fname) {
    DataOutputStream packed = IO.dosOpen(fname);
    try {
      packed.writeBytes("###Old Separator:" +
        SourceCodeFile.filesep + "###\n");
    } catch(IOException e) {
      Pr.error("Writing separator to " + fname);
    }
    Enumeration e = t.keys();
    while(e.hasMoreElements()) {
      String dir = (String)e.nextElement();
      System.out.println(
        "Writing directory " + dir);
      Vector v = (Vector)t.get(dir);
      for(int i = 0; i < v.size(); i++) {
        SourceCodeFile f = 
          (SourceCodeFile)v.elementAt(i);
        f.writePacked(packed);
      }
    }
    IO.close(packed);
  }
  // Write all the files in their directories:
  public void write() {
    Enumeration e = t.keys();
    while(e.hasMoreElements()) {
      String dir = (String)e.nextElement();
      Vector v = (Vector)t.get(dir);
      for(int i = 0; i < v.size(); i++) {
        SourceCodeFile f = 
          (SourceCodeFile)v.elementAt(i);
        f.writeFile(rootpath);
      }
      // Add file indicating file quantity
      // written to this directory as a check:
      IO.close(IO.dosOpen(
        new File(new File(rootpath, dir),
          Integer.toString(v.size())+".files")));
    }
  }
}

public class CodePackager {
  private static final String usageString =
  "usage: java CodePackager packedFileName" +
  "\nExtracts source code files from packed \n" +
  "version of Tjava.doc sources into " +
  "directories off current directory\n" +
  "java CodePackager packedFileName newDir\n" +
  "Extracts into directories off newDir\n" +
  "java CodePackager -p source.txt packedFile" +
  "\nCreates packed version of source files" +
  "\nfrom text version of Tjava.doc";
  private static void usage() {
    System.err.println(usageString);
    System.exit(1);
  }
  public static void main(String[] args) {
    if(args.length == 0) usage();
    if(args[0].equals("-p")) {
      if(args.length != 3)
        usage();
      createPackedFile(args);
    }
    else {
      if(args.length > 2)
        usage();
      extractPackedFile(args);
    }
  }
  private static String currentLine; 
  private static BufferedReader in;
  private static DirMap dm;
  private static void 
  createPackedFile(String[] args) {
    dm = new DirMap();
    in = IO.disOpen(args[1]);
    try {
      while((currentLine = in.readLine()) 
          != null) {
        if(currentLine.startsWith(
            SourceCodeFile.startMarker)) {
          dm.add(new SourceCodeFile(
                   currentLine, in));
        }
        else if(currentLine.startsWith(
            SourceCodeFile.endMarker))
          Pr.error("file has no start marker");
        // Else ignore the input line
      }
    } catch(IOException e) {
      Pr.error("Error reading " + args[1]);
    }
    IO.close(in);
    dm.writePackedFile(args[2]);
  }
  private static void 
  extractPackedFile(String[] args) {
    if(args.length == 2) // Alternate directory
      dm = new DirMap(args[1]);
    else // Current directory
      dm = new DirMap();
    in = IO.disOpen(args[0]);
    String s = null;
    try {
       s = in.readLine();
    } catch(IOException e) {
      Pr.error("Cannot read from " + in);
    }
    // Capture the separator used in the system
    // that packed the file:
    if(s.indexOf("###Old Separator:") != -1 ) {
      String oldsep = s.substring(
        "###Old Separator:".length());
      oldsep = oldsep.substring(
        0, oldsep. indexOf('#'));
      SourceCodeFile.oldsep = oldsep;
    }
    SourceCodeFile sf = new SourceCodeFile(in);
    while(sf.hasFile()) {
      dm.add(sf);
      sf = new SourceCodeFile(in);
    }
    dm.write();
  }
} ///:~


我們注意到package語句已經作為注釋標志出來了。由於這是本章的第一個程序,所以package語句是必需的,用它告訴CodePackager已改換到另一章。但是把它放入包裡卻會成為一個問題。當我們創建一個包的時候,需要將結果程序同一個特定的目錄結構聯系在一起,這一做法對本書的大多數例子都是適用的。但在這裡,CodePackager程序必須在一個專用的目錄裡編譯和運行,所以package語句作為注釋標記出去。但對CodePackager來說,它“看起來”依然象一個普通的package語句,因為程序還不是特別復雜,不能偵查到多行注釋(沒有必要做得這麼復雜,這裡只要求方便就行)。
頭兩個類是“支持/工具”類,作用是使程序剩余的部分在編寫時更加連貫,也更便於閱讀。第一個是Pr,它類似ANSI C的perror庫,兩者都能打印出一條錯誤提示消息(但同時也會退出程序)。第二個類將文件的創建過程封裝在內,這個過程已在第10章介紹過了;大家已經知道,這樣做很快就會變得非常累贅和麻煩。為解決這個問題,第10章提供的方案致力於新類的創建,但這兒的“靜態”方法已經使用過了。在那些方法中,正常的違例會被捕獲,並相應地進行處理。這些方法使剩余的代碼顯得更加清爽,更易閱讀。
幫助解決問題的第一個類是SourceCodeFile(源碼文件),它代表本書一個源碼文件包含的所有信息(內容、文件名以及目錄)。它同時還包含了一系列String常數,分別代表一個文件的開始與結束;在打包文件內使用的一個標記;當前系統的換行符;文件路徑分隔符(注意要用System.getProperty()偵查本地版本是什麼);以及一大段版權聲明,它是從下面這個Copyright.txt文件裡提取出來的:

 

//////////////////////////////////////////////////
// Copyright (c) Bruce Eckel, 1998
// Source code file from the book "Thinking in Java"
// All rights reserved EXCEPT as allowed by the
// following statements: You may freely use this file
// for your own work (personal or commercial),
// including modifications and distribution in
// executable form only. Permission is granted to use
// this file in classroom situations, including its
// use in presentation materials, as long as the book
// "Thinking in Java" is cited as the source. 
// Except in classroom situations, you may not copy
// and distribute this code; instead, the sole
// distribution point is http://www.BruceEckel.com 
// (and official mirror sites) where it is
// freely available. You may not remove this
// copyright and notice. You may not distribute
// modified versions of the source code in this
// package. You may not use this file in printed
// media without the express permission of the
// author. Bruce Eckel makes no representation about
// the suitability of this software for any purpose.
// It is provided "as is" without express or implied
// warranty of any kind, including any implied
// warranty of merchantability, fitness for a
// particular purpose or non-infringement. The entire
// risk as to the quality and performance of the
// software is with you. Bruce Eckel and the
// publisher shall not be liable for any damages
// suffered by you or any third party as a result of
// using or distributing software. In no event will
// Bruce Eckel or the publisher be liable for any
// lost revenue, profit, or data, or for direct,
// indirect, special, consequential, incidental, or
// punitive damages, however caused and regardless of
// the theory of liability, arising out of the use of
// or inability to use software, even if Bruce Eckel
// and the publisher have been advised of the
// possibility of such damages. Should the software
// prove defective, you assume the cost of all
// necessary servicing, repair, or correction. If you
// think you've found an error, please email all
// modified files with clearly commented changes to:
// [email protected]. (please use the same
// address for non-code errors found in the book).
//////////////////////////////////////////////////


從一個打包文件中提取文件時,當初所用系統的文件分隔符也會標注出來,以便用本地系統適用的符號替換它。
當前章的子目錄保存在chapter字段中,它初始化成c02(大家可注意一下第2章的列表正好沒有包含一個打包語句)。只有在當前文件裡發現一個package(打包)語句時,chapter字段才會發生改變。

1. 構建一個打包文件
第一個構建器用於從本書的ASCII文本版裡提取出一個文件。發出調用的代碼(在列表裡較深的地方)會讀入並檢查每一行,直到找到與一個列表的開頭相符的為止。在這個時候,它就會新建一個SourceCodeFile對象,將第一行的內容(已經由調用代碼讀入了)傳遞給它,同時還要傳遞BufferedReader對象,以便在這個緩沖區中提取源碼列表剩余的內容。
從這時起,大家會發現String方法被頻繁運用。為提取出文件名,需調用substring()的過載版本,令其從一個起始偏移開始,一直讀到字串的末尾,從而形成一個“子串”。為算出這個起始索引,先要用length()得出startMarker的總長,再用trim()刪除字串頭尾多余的空格。第一行在文件名後也可能有一些字符;它們是用indexOf()偵測出來的。若沒有發現找到我們想尋找的字符,就返回-1;若找到那些字符,就返回它們第一次出現的位置。注意這也是indexOf()的一個過載版本,采用一個字串作為參數,而非一個字符。
解析出並保存好文件名後,第一行會被置入字串contents中(該字串用於保存源碼清單的完整正文)。隨後,將剩余的代碼行讀入,並合並進入contents字串。當然事情並沒有想象的那麼簡單,因為特定的情況需加以特別的控制。一種情況是錯誤檢查:若直接遇到一個startMarker(起始標記),表明當前操作的這個代碼列表沒有設置一個結束標記。這屬於一個出錯條件,需要退出程序。
另一種特殊情況與package關鍵字有關。盡管Java是一種自由形式的語言,但這個程序要求package關鍵字必須位於行首。若發現package關鍵字,就通過檢查位於開頭的空格以及位於末尾的分號,從而提取出包名(注意亦可一次單獨的操作實現,方法是使用過載的substring(),令其同時檢查起始和結束索引位置)。隨後,將包名中的點號替換成特定的文件分隔符——當然,這裡要假設文件分隔符僅有一個字符的長度。盡管這個假設可能對目前的所有系統都是適用的,但一旦遇到問題,一定不要忘了檢查一下這裡。
默認操作是將每一行都連接到contents裡,同時還有換行字符,直到遇到一個endMarker(結束標記)為止。該標記指出構建器應當停止了。若在endMarker之前遇到了文件結尾,就認為存在一個錯誤。

2. 從打包文件中提取
第二個構建器用於將源碼文件從打包文件中恢復(提取)出來。在這兒,作為調用者的方法不必擔心會跳過一些中間文本。打包文件包含了所有源碼文件,它們相互間緊密地靠在一起。需要傳遞給該構建器的僅僅是一個BufferedReader,它代表著“信息源”。構建器會從中提取出自己需要的信息。但在每個代碼列表開始的地方還有一些配置信息,它們的身份是用packMarker(打包標記)指出的。若packMarker不存在,意味著調用者試圖用錯誤的方法來使用這個構建器。
一旦發現packMarker,就會將其剝離出來,並提取出目錄名(用一個'#'結尾)以及文件名(直到行末)。不管在哪種情況下,舊分隔符都會被替換成本地適用的一個分隔符,這是用String replace()方法實現的。老的分隔符被置於打包文件的開頭,在代碼列表稍靠後的一部分即可看到是如何把它提取出來的。
構建器剩下的部分就非常簡單了。它讀入每一行,把它合並到contents裡,直到遇見endMarker為止。

3. 程序列表的存取
接下來的一系列方法是簡單的訪問器:directory()、filename()(注意方法可能與字段有相同的拼寫和大小寫形式)和contents()。而hasFile()用於指出這個對象是否包含了一個文件(很快就會知道為什麼需要這個)。
最後三個方法致力於將這個代碼列表寫進一個文件——要麼通過writePacked()寫入一個打包文件,要麼通過writeFile()寫入一個Java源碼文件。writePacked()需要的唯一東西就是DataOutputStream,它是在別的地方打開的,代表著准備寫入的文件。它先把頭信息置入第一行,再調用writeBytes()將contents(內容)寫成一種“通用”格式。
准備寫Java源碼文件時,必須先把文件建好。這是用IO.psOpen()實現的。我們需要向它傳遞一個File對象,其中不僅包含了文件名,也包含了路徑信息。但現在的問題是:這個路徑實際存在嗎?用戶可能決定將所有源碼目錄都置入一個完全不同的子目錄,那個目錄可能是尚不存在的。所以在正式寫每個文件之前,都要調用File.mkdirs()方法,建好我們想向其中寫入文件的目錄路徑。它可一次性建好整個路徑。

4. 整套列表的包容
以子目錄的形式組織代碼列表是非常方便的,盡管這要求先在內存中建好整套列表。之所以要這樣做,還有另一個很有說服力的原因:為了構建更“健康”的系統。也就是說,在創建代碼列表的每個子目錄時,都會加入一個額外的文件,它的名字包含了那個目錄內應有的文件數目。
DirMap類可幫助我們實現這一效果,並有效地演示了一個“多重映射”的概述。這是通過一個散列表(Hashtable)實現的,它的“鍵”是准備創建的子目錄,而“值”是包含了那個特定目錄中的SourceCodeFile對象的Vector對象。所以,我們在這兒並不是將一個“鍵”映射(或對應)到一個值,而是通過對應的Vector,將一個鍵“多重映射”到一系列值。盡管這聽起來似乎很復雜,但具體實現時卻是非常簡單和直接的。大家可以看到,DirMap類的大多數代碼都與向文件中的寫入有關,而非與“多重映射”有關。與它有關的代碼僅極少數而已。
可通過兩種方式建立一個DirMap(目錄映射或對應)關系:默認構建器假定我們希望目錄從當前位置向下展開,而另一個構建器讓我們為起始目錄指定一個備用的“絕對”路徑。
add()方法是一個采取的行動比較密集的場所。首先將directory()從我們想添加的SourceCodeFile裡提取出來,然後檢查散列表(Hashtable),看看其中是否已經包含了那個鍵。如果沒有,就向散列表加入一個新的Vector,並將它同那個鍵關聯到一起。到這時,不管采取的是什麼途徑,Vector都已經就位了,可以將它提取出來,以便添加SourceCodeFile。由於Vector可象這樣同散列表方便地合並到一起,所以我們從兩方面都能感覺得非常方便。
寫一個打包文件時,需打開一個准備寫入的文件(當作DataOutputStream打開,使數據具有“通用”性),並在第一行寫入與老的分隔符有關的頭信息。接著產生對Hashtable鍵的一個Enumeration(枚舉),並遍歷其中,選擇每一個目錄,並取得與那個目錄有關的Vector,使那個Vector中的每個SourceCodeFile都能寫入打包文件中。
用write()將Java源碼文件寫入它們對應的目錄時,采用的方法幾乎與writePackedFile()完全一致,因為兩個方法都只需簡單調用SourceCodeFile中適當的方法。但在這裡,根路徑會傳遞給SourceCodeFile.writeFile()。所有文件都寫好後,名字中指定了已寫文件數量的那個附加文件也會被寫入。

5. 主程序
前面介紹的那些類都要在CodePackager中用到。大家首先看到的是用法字串。一旦最終用戶不正確地調用了程序,就會打印出介紹正確用法的這個字串。調用這個字串的是usage()方法,同時還要退出程序。main()唯一的任務就是判斷我們希望創建一個打包文件,還是希望從一個打包文件中提取什麼東西。隨後,它負責保證使用的是正確的參數,並調用適當的方法。
創建一個打包文件時,它默認位於當前目錄,所以我們用默認構建器創建DirMap。打開文件後,其中的每一行都會讀入,並檢查是否符合特殊的條件:
(1) 若行首是一個用於源碼列表的起始標記,就新建一個SourceCodeFile對象。構建器會讀入源碼列表剩下的所有內容。結果產生的句柄將直接加入DirMap。
(2) 若行首是一個用於源碼列表的結束標記,表明某個地方出現錯誤,因為結束標記應當只能由SourceCodeFile構建器發現。

提取/釋放一個打包文件時,提取出來的內容可進入當前目錄,亦可進入另一個備用目錄。所以需要相應地創建DirMap對象。打開文件,並將第一行讀入。老的文件路徑分隔符信息將從這一行中提取出來。隨後根據輸入來創建第一個SourceCodeFile對象,它會加入DirMap。只要包含了一個文件,新的SourceCodeFile對象就會創建並加入(創建的最後一個用光輸入內容後,會簡單地返回,然後hasFile()會返回一個錯誤)。

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