下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

java中如何检查大小写样式

作者:课课家教育     来源: http://www.kokojia.com点击数:837发布时间: 2016-02-29 20:49:56

标签: java样式标准java程序java继承

大神带你学编程,欢迎选课

  尽管对涉及文字处理的一些项目来说,前例显得比较方便,但下面要介绍的项目却能立即发挥作用,因为它执行的是一个样式检查,以确保我们的大小写形式符合“事实上”的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()方法将列表写入一个文件,同时用一个字串提供文件头信息。

赞(19)
踩(3)
分享到:
华为认证网络工程师 HCIE直播课视频教程