Java编程思想第4版[中文版](PDF格式)-第162部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
不管在哪种情况下,旧分隔符都会被替换成本地适用的一个分隔符,这是用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 (枚举),并遍
632
…………………………………………………………Page 634……………………………………………………………
历其中,选择每一个目录,并取得与那个目录有关的Vector,使那个Vector 中的每个SourceCodeFile 都能
写入打包文件中。
用write()将Java 源码文件写入它们对应的目录时,采用的方法几乎与 writePackedFile()完全一致,因为
两个方法都只需简单调用SourceCodeFile 中适当的方法。但在这里,根路径会传递给
SourceCodeFile。writeFile()。所有文件都写好后,名字中指定了已写文件数量的那个附加文件也会被写
入。
5。 主程序
前面介绍的那些类都要在CodePackager 中用到。大家首先看到的是用法字串。一旦最终用户不正确地调用了
程序,就会打印出介绍正确用法的这个字串。调用这个字串的是usage()方法,同时还要退出程序。main()
唯一的任务就是判断我们希望创建一个打包文件,还是希望从一个打包文件中提取什么东西。随后,它负责
保证使用的是正确的参数,并调用适当的方法。
创建一个打包文件时,它默认位于当前目录,所以我们用默认构建器创建DirMap。打开文件后,其中的每一
行都会读入,并检查是否符合特殊的条件:
(1) 若行首是一个用于源码列表的起始标记,就新建一个SourceCodeFile 对象。构建器会读入源码列表剩下
的所有内容。结果产生的句柄将直接加入DirMap。
(2) 若行首是一个用于源码列表的结束标记,表明某个地方出现错误,因为结束标记应当只能由
SourceCodeFile 构建器发现。
提取/释放一个打包文件时,提取出来的内容可进入当前目录,亦可进入另一个备用目录。所以需要相应地
创建DirMap 对象。打开文件,并将第一行读入。老的文件路径分隔符信息将从这一行中提取出来。随后根据
输入来创建第一个 SourceCodeFile 对象,它会加入DirMap。只要包含了一个文件,新的 SourceCodeFile 对
象就会创建并加入(创建的最后一个用光输入内容后,会简单地返回,然后hasFile()会返回一个错误)。
17。1。2 检查大小写样式
尽管对涉及文字处理的一些项目来说,前例显得比较方便,但下面要介绍的项目却能立即发挥作用,因为它
执行的是一个样式检查,以确保我们的大小写形式符合“事实上”的 Java 样式标准。它会在当前目录中打开
每个。java 文件,并提取出所有类名以及标识符。若发现有不符合Java 样式的情况,就向我们提出报告。
为了让这个程序正确运行,首先必须构建一个类名,将它作为一个“仓库”,负责容纳标准 Java 库中的所有
类名。为达到这个目的,需遍历用于标准 Java 库的所有源码子目录,并在每个子目录都运行
ClassScanner。至于参数,则提供仓库文件的名字(每次都用相同的路径和名字)和命令行开关…a,指出类
名应当添加到该仓库文件中。
为了用程序检查自己的代码,需要运行它,并向它传递要使用的仓库文件的路径与名字。它会检查当前目录
中的所有类和标识符,并告诉我们哪些没有遵守典型的Java 大写写规范。
要注意这个程序并不是十全十美的。有些时候,它可能报告自己查到一个问题。但当我们仔细检查代码的时
候,却发现没有什么需要更改的。尽管这有点儿烦人,但仍比自己动手检查代码中的所有错误强得多。
下面列出源代码,后面有详细的解释:
//: ClassScanner。java
// Scans all files in directory for classes
// and identifiers; to check capitalization。
// Assumes properly piling 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);
633
…………………………………………………………Page 635……………………………………………………………
}
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。slashStarments(true);
// in。slashSlashments(true);
in。ordinaryChar('/');
in。ordinaryChar('。');
in。wordChars('_'; '_');
in。eolIsSignificant(true);
while(in。nextToken() !=
StreamTokenizer。TT_EOF) {
if(in。ttype == '/')
eatments();
634
…………………………………………………………Page 636……………………………………………………………
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 ment removal seemed
// to be broken。 This extracts them:
void eatments() {
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() !=
635
…………………………………………………………Page 637……………………………………………………………
StreamTokenizer。TT_EOF
&& in。ttype == '/')
break;
}
}
} catch(IOException e) {
e。printStackTrace();
}