查抄巨细写样式
尽量对涉及文字处理惩罚的一些项目来说,前例显得较量利便,但下面要先容的项目却能当即发挥浸染,因为它执行的是一个样式查抄,以确保我们的巨细写形式切合“事实上”的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"); } } ///:~
#p#分页标题#e#
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()要领将列表写入一个文件,同时用一个字串提供文件头信息。