项目要求

  wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。
  实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
  •   基本要求
  • -c  统计文件字符数(实现)
  • -w 统计文件单词数(实现)
  • -l   统计文件行数   (实现)
  •       扩展要求
    • -s  递归处理目录下符合条件的文件     (实现)
    • -a  返回文件代码行、空行、注视行数  (实现)
  •       高级要求
    • -x  程序图形界面化  (未实现)
 

PSP表格预估

PSP2.1
Personal Software Process Stages
预估耗时(分钟)
实际耗时(分钟)
Planning
计划
40 60
· Estimate
· 估计这个任务需要多少时间
40
60
Development
开发
1200 1620
· Analysis
· 需求分析 (包括学习新技术)
120 150
· Design Spec
· 生成设计文档
40 60
· Design Review
· 设计复审 (和同事审核设计文档)
40 55
· Coding Standard
· 代码规范 (为目前的开发制定合适的规范)
35 45
· Design
· 具体设计
130 160
· Coding
· 具体编码
685 960
· Code Review
· 代码复审
70 90
· Test
· 测试(自我测试,修改代码,提交修改)
80
100
Reporting
报告
240
400
· Test Report
· 测试报告
120 220
· Size Measurement
· 计算工作量
60 80
· Postmortem & Process Improvement Plan
· 事后总结, 并提出过程改进计划
60 100
合计
  1480 2080
 
 
 

解题思路

        
  根据需求,应将总体分割为“实现功能的函数”和“负责运行的主函数”两个。
  设计过程中由于很多个环节都需要用到检验功能,检验文件、检验命令、检验文件夹等等,于是我将其检验功能单独取出来做成一个函数,方便其余各函数调用以及减少代码重复率。
  同时,由于递归函数功能跟其他功能相比之下有点特殊,所以我又将其独立成一个函数。
 

代码说明

 
  Count函数共有6个方法,其中一个为传入参数的构造方法
  charNumber、wordNumber、lineNumber   相对应着  “ -c ”  “ -w ”   “-l ”  三个基本功能
  additionlineNumber  则是实现了  “ -a ”  这个扩展功能
  runFunc   则是根据输入指令来选择相应功能的运行
package wc;

import com.mysql.jdbc.StringUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Count {

    int charNum ;
    int wordNum ;
    int lineNum ;
    int spacelineNum;
    int notelineNum;
    int codelineNum;
    String[] cms;
    String contentLine;
    File file;
    BufferedReader br;


    public Count(String[] commands , File file){
        cms = commands.clone();
        this.file = file;
        System.out.println("文件:"+file.getAbsolutePath());
    }

    public void charNumber() throws IOException{
        charNum = 0;
        br = new BufferedReader(new FileReader(file));
        while(( contentLine = br.readLine())!=null){
            String str = contentLine.replaceAll("\\s","");
            charNum += str.length();
        }
        System.out.println("字符数:"+charNum);
    }

    public void wordNumber() throws IOException{
        wordNum = 0;
        br = new BufferedReader(new FileReader(file));
        //用空格替换掉非单词的字符,并用StringUtils类将其切割
        while(( contentLine = br.readLine())!=null){
            String str = contentLine.replaceAll("\\W"," ");
            wordNum += StringUtils.split(str," ",true).size();
        }
        System.out.println("单词数:"+wordNum);
    }

    public void lineNumber() throws IOException{
        lineNum =0;
        br = new BufferedReader(new FileReader(file));
        while(( contentLine = br.readLine())!=null){
            lineNum++;
        }
        System.out.println("行数:"+lineNum);
    }

    //统计空白行、代码行和注释行的数目
    public void additionlineNumber() throws IOException{
        spacelineNum = 0;
        notelineNum = 0;
        codelineNum = 0;
        boolean flag = false;
        br = new BufferedReader(new FileReader(file));
        while(( contentLine = br.readLine())!=null){
            contentLine = contentLine.trim();
            if(contentLine.matches("^\\s*$")){
                spacelineNum++;
            }else if(contentLine.startsWith("/*")&&!contentLine.endsWith("*/")){
                notelineNum++;
                flag = true;
            }else if (flag == true){
                notelineNum++;
                if(contentLine.endsWith("*/")){
                    flag = false;
                }
            }else if(contentLine.startsWith("//")||contentLine.endsWith("//")){
                notelineNum++;
            }else{
                codelineNum++;
            }
        }
        System.out.println("空白行:"+spacelineNum);
        System.out.println("注释行:"+notelineNum);
        System.out.println("代码行:"+codelineNum);
    }


    public void runFunc() throws IOException{
        for(String command : cms) {
            switch (command) {
                case "-c":
                    charNumber();
                    break;
                case "-w":
                    wordNumber();
                    break;
                case "-l":
                    lineNumber();
                    break;
                case "-a":
                    additionlineNumber();
                    break;
                default:
                    System.out.println("输入功能有误");
                    break;
            }
        }
    }
}

 

 
 
        CheckOut函数,关于检验文件、文件夹、代码文件和指令的功能皆包含在内
package wc;

import java.io.*;
import java.util.Arrays;

public class CheckOut {

    String[] cms;
    File file;
    String[] cmCol = {"-c","-w","-l","-s","-a"}; //指令数组,用于检验输入指令是否正确

    public CheckOut(String[] command,File file){
        cms = command.clone();
        this.file = file;
    }

    public boolean checkFile() {
        if (file.isFile() && file.exists()) {
            return true;
        } else {
            System.out.println("该文件不存在!请检查路径是否输入错误!");
            return false;
        }
    }

    public boolean checkCommand(){
        for(String cm : cms){
            if(!Arrays.asList(cmCol).contains(cm)){
                System.out.println("输入命令有误,请重新输入!");
                return false;
            }
        }
        return true;
    }

    public static boolean checkFile(File file){
        if(file.isFile()&&file.exists()){
            return true;
        }else{
            return false;
        }
    }

    public static boolean checkFileDir(File file){
        if(file.isDirectory()){
            if(file.list().length>0){
                return true;
            }else{
                System.out.println("该目录为空目录!");
                return false;
            }
        }else{
            return false;
        }
    }

    public static boolean checkCodeFile(File file){
        String str = file.getName();
        if(str.endsWith(".java")||str.endsWith(".cpp")||str.endsWith(".c")){
            return true;
        }else{
            return false;
        }
    }

}

 

 
 
        RecFile函数,对输入文件夹进行递归处理,查询目录下符合条件的所有代码文件
 
package wc;

import java.io.File;
import java.io.IOException;

public class RecFile {

     public static void RecF(String[] cms,File file) throws IOException{
        if(CheckOut.checkFile(file)&&CheckOut.checkCodeFile(file)){
            Count count = new Count(cms,file);
            count.runFunc();
        }else if(file.isDirectory()) {
            File[] files = file.listFiles();
            for (File fs : files) {
                RecF(cms, fs);
            }
        }
    }
}

      主函数Main,整个程序的执行函数

package wc;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) throws IOException{

        System.out.println("\n\t 输入 “-c 文件完整路径” 表示查询文件的字符数 ");
        System.out.println("\n\t 输入 “-w 文件完整路径” 表示查询文件的词的数目");
        System.out.println("\n\t 输入 “-l 文件完整路径” 表示查询文件的行数");
        System.out.println("\n\t 输入 “-a 文件完整路径” 表示查询文件的空行、注释行、代码行数");
        System.out.println("\n\t 输入 “-s 其他功能指令 文件完整路径” 表示以特定功能查询文件夹下所有代码文件");
        System.out.println("\n\t 输入 “-e” 表示退出程序");
        System.out.println("\n\t 以上除了退出功能外,其余皆可叠加指令查询,如-c -w -l 文件完整路径");
        while(true){
            System.out.println("\n请输入相应命令:");
            Scanner sc = new Scanner(System.in);
            //按空格切割成字符串数组,并将其分为指令组和文件路径
            String commands[] = sc.nextLine().toString().split("\\s");
            //判断是否为退出指令
            if(commands[0].equals("-e")){
                break;
            }
           
            String cms[] = Arrays.copyOf(commands,commands.length-1);
            String filePath = commands[commands.length-1];
            File file = new File(filePath);

            
            //递归查询
            if(cms[0].equals("-s")) {
                //检验输入指令除了‘-s’外,还含有其他功能指令
                if(cms.length>1){
                    String newcms[] = Arrays.copyOfRange(cms,1,cms.length);
                    if(CheckOut.checkFileDir(file)) {
                        RecFile.RecF(newcms, file);
                    }
                }else{
                    System.out.println("输入指令有误,请重新输入!");
                }
            }
            //普通查询
            else{
                CheckOut co = new CheckOut(cms,file);
                if(co.checkCommand()&&co.checkFile()) {
                    if(CheckOut.checkCodeFile(file)) {
                        Count count = new Count(cms, file);
                        count.runFunc();
                    }else{
                        System.out.println("该文件不是代码文件,请重新输入!");
                    }
                }else{
                    continue;
                }
            }
        }
    }

}

 

 

测试

  程序已打包成exe文件并上传至github

     
  
  
  
 

代码覆盖率

  

 

总结

   虽说图形界面没有完成,但还是勉强强将其他功能给实现了。在实现的过程中,由于之前的java知识遗忘掉蛮多的,所以在编程中都是边想边打码边百度的。

     其中也碰到不少坑,例如为了单词的正则表达式烦恼了许久,最后还是决定用\\W这种并不算严谨的方式匹配;还有exe4j的坑,由于我在Count类wordNumber方法里切割单词时,采用的是StringUtils的切割方法,所以要用到工具包,于是在将项目打包成jar时吃了不少亏,最后的解决方法是已导出的、将没有工具包的项目jar跟工具包jar解压到同一个文件,同时让项目jar的MANIFEST.MF文件覆盖掉后者的文件,最后再将其压缩成一个jar文件。

  这个项目的不足之处还有不少,例如检验功能并没有能完全独立出来,代码的耦合性有点高、不太易于维护和升级,倘若要改进的话,需先从这两处着手才行。