个人项目作业—WCProject

GitHub项目地址https://github.com/oMIZUCHIo/WCProject

1.Word Count 项目要求:

wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。

实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。

具体功能要求:
程序处理用户需求的模式为:

wc.exe [parameter] [file_name]

基本功能列表:

wc.exe -c file.c     //返回文件 file.c 的字符数

wc.exe -w file.c    //返回文件 file.c 的词的数目  

wc.exe -l file.c      //返回文件 file.c 的行数

扩展功能:
    -s   递归处理目录下符合条件的文件。
    -a   返回更复杂的数据(代码行 / 空行 / 注释行)。

空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。

代码行:本行包括多于一个字符的代码。

注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:

    } //注释
在这种情况下,这一行属于注释行。

[file_name]: 文件或目录名,可以处理一般通配符。


高级功能:

 -x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。

需求举例:
  wc.exe -s -a *.c ===> 返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数。

2.预计开发时间 PSP

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

 20

 

· Estimate

· 估计这个任务需要多少时间

20

 

Development

开发

300

 

· Analysis

· 需求分析 (包括学习新技术)

60

 

· Design Spec

· 生成设计文档

 10

 

· Design Review

· 设计复审 (和同事审核设计文档)

 0

 

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

 10

 

· Design

· 具体设计

50

 

· Coding

· 具体编码

 80

 

· Code Review

· 代码复审

 30

 

· Test

· 测试(自我测试,修改代码,提交修改)

 60

 

Reporting

报告

60

 

· Test Report

· 测试报告

 30

 

· Size Measurement

· 计算工作量

 20

 

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

 10

 

合计

 

 760

 

3.解题思路

  采用JAVA语言进行编写,并使用IO流进行文件信息的读入;将输入参数和输出结果进行封装,便于后期的更改;使用正则表达式进行部分信息的判断;使用javafx做图形化界面。

  遇到的问题:对JAVA正则等部分语法不太熟悉,需要边做项目边不断地查找资料;觉得项目的需求有点模棱两可,后选定我认为的理解进行实现,可能会理解错。

4.设计实现过程

  主要组成为:一个主函数类,作为整个项目的入口;一个WC实现类,用于各信息的计算;一个图形化类,用于调起图形化窗口。

  其中因字符数,词数,行数的计算可方便的在一次文件IO读取中便进行计算,因此决定将它们在一个方法中实现,再根据要求进行输出,基本不会影响性能

函数:

 process:实现类的对外调度口,用于进行参数判断,方法实现,以及结果信息拼接的函数
 searchFiles:用于递归处理文件目录
 countingSimply:计算基础信息:字符数,词数,行数
 countingComplex:计算代码行数,空行数,注释行数
 

5.代码说明

  (1)主函数

    

public static void main(String[] args) {

        WCUtil wcUtil = new WCUtil();
        Parameter parameter = new Parameter();

        if(args == null || args.length == 0){
            System.out.println("\n请输入指令");
            Scanner scanner = new Scanner(System.in);
            args = scanner.nextLine().split("\\s+");
        }

        if(!args[0].equals("wc.exe")){
            System.out.println("\n当前只可运行wc.exe");
            return ;
        }

        //最后一个路径为文件路径
        String lastParam = args[args.length - 1];
        if(!lastParam.equals("-x")){
            parameter.setFilePath(lastParam);
        }else{
            parameter.setFrame(true);
        }

        for (int i = 1; i < args.length - 1; i++) {
            switch (args[i]) {
                case "-c": //计算 file.c 的字符数
                    parameter.setCountChar(true);
                    break;
                case "-w": //计算 file.c 的单词数
                    parameter.setCountWord(true);
                    break;
                case "-l": //计算 file.c 的行数
                    parameter.setCountLine(true);
                    break;
                case "-s": //使用递归处理目录下符合条件的文件
                    parameter.setRecurrence(true);
                    break;
                case "-a": //返回更复杂的数据(代码行 / 空行 / 注释行)
                    parameter.setCountComplex(true);
                    break;
                case "-x": //调用图形化界面
                    parameter.setFrame(true);
                    break;
                default:
                    System.out.println("\n指令出错");
            }
        }
        if(parameter.isFrame()){
            Frame.main(args);
        }else{
            System.out.println(wcUtil.process(parameter));
        }
    }

  (2)实现类调用方法

 

/**
     * @description 对外调用接口
     * @param parameter 请求参数
     */
    String process(Parameter parameter){

        //对输入参数进行预处理,判断 参数是否合法,是否含有通配符
        String errormsg = judgeFilePath(parameter);
        if(errormsg != null)
            return errormsg;

        //各文件的查询结果集合
        List<Count> countList = new ArrayList<>();

        //查询文件相应信息
        searchFiles(parameter,countList);

        if(countList.size() == 0)
            return "\n未有符合条件的文件";

        StringBuilder stringBuffer = new StringBuilder();

        for(Count count : countList){

            stringBuffer.append("\n文件名:").append(count.getFileName());

            if(parameter.isCountChar()){
                stringBuffer.append("\n字符数:").append(count.getCharNum());
            }
            if(parameter.isCountWord()){
                stringBuffer.append("\n词数:").append(count.getWordNum());
            }
            if(parameter.isCountLine()){
                stringBuffer.append("\n行数:").append(count.getLineNum());
            }
            if(parameter.isCountComplex()){
                stringBuffer.append("\n代码行数:").append(count.getCodeLineNum());
                stringBuffer.append("\n空行数:").append(count.getEmptyLineNum());
                stringBuffer.append("\n注释行数:").append(count.getNoteLineNum());
            }

            stringBuffer.append("\n");
        }
        return stringBuffer.toString();
    }

  (3)递归查询文件

/**
     * @description 查询文件
     * @param parameter 命令与文件路径信息封装
     */
    private void searchFiles(Parameter parameter, List<Count> countList){

        File file = new File(parameter.getFilePath());

        if (file.isFile() && file.exists()) { //为文件

            //当输入文件名不含通配符 或 含通配符且文件名匹配时才查询
            if(parameter.getMatchName() == null ||
                    (parameter.getMatchName() != null && compareName(file.getName(),parameter.getMatchName()))){

                //获取基础信息
                Count count = countingSimply(file.getPath());
                count.setFileName(file.getName());

                //当含有 -s 命令时额外添加 代码行数 等信息
                if(parameter.isCountComplex()){
                    toComplexCount(count,countingComplex(file.getPath()));
                }

                countList.add(count);
            }

        } else if (! file.exists()){

            System.out.println("\n文件不存在");

        } else if(file.isDirectory() && !parameter.isRecurrence()){

            if(parameter.getMatchName() == null){
                System.out.println("\n输入文件夹时,请加入-s命令进行递归查询");
            }else{
                System.out.println("\n输入文件含通配符时,请加入-s命令对目录进行递归查询");
            }

        }else if (file.isDirectory() && parameter.isRecurrence()) {   //为文件夹且需要递归时

            File[] files = file.listFiles();  //获取文件列表

            if(files != null && files.length != 0) {

                for (File f : files) {

                    if (f.isDirectory()) {   //为文件夹且需要递归

                        //递归查询子文件
                        parameter.setFilePath(f.getPath());
                        searchFiles(parameter,countList);

                    } else if (f.isFile()) {  //为文件

                        //当输入文件名不含通配符 或 含通配符且文件名匹配时才查询
                        if(parameter.getMatchName() == null ||
                                (parameter.getMatchName() != null && compareName(f.getName(),parameter.getMatchName()))) {

                            //获取基础信息
                            Count count = countingSimply(f.getPath());
                            count.setFileName(f.getName());

                            //当含有 -s 命令时额外添加 代码行数 等信息
                            if(parameter.isCountComplex()){
                                toComplexCount(count,countingComplex(f.getPath()));
                            }
                            countList.add(count);
                        }
                    }
                }
            }
        }
    }

  (4)计算字符数,词数,行数

/**
     * @description 获取字符数,词数,行数信息
     * @param filePath 文件路径
     * @return Count 结果封装
     */
    private Count countingSimply(String filePath){

        int charNum = 0;
        int wordNum = 0;
        int lineNum = 0;

        try {
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            String line;
            boolean flag = false;  //为false表示 一个单词的结束
            //IO逐行读取
            while ((line = reader.readLine()) != null) {

                ++ lineNum;

                //逐个获取字符
                for (int i = 0; i < line.length(); i++) {

                    char c = line.charAt(i);

                    //跳过空格类字符
                    if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
                       continue;
                    }
                    ++ charNum;

                    //如果字符为  空格、换行等 则为一个单词的结束
                    if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z')) {

                        flag = false;

                    //当flag为false时,表示上一个单词的结束,也代表新单词的开始
                    }else if (!flag) {

                        flag = true;
                        ++ wordNum;
                    }
                }
            }
            reader.close();
        } catch (IOException e) {
            System.out.println("IO执行出错:" + e.getMessage());
        }
        return new Count(charNum,wordNum,lineNum);
    }

  (5)计算代码行数,空行数,注释行数

      其中按需求,多于一个字符的代码的行称为代码行,而注释行的前提是“本行不是代码行”,因此认为代码行优先级高于注释行的判断,因此如: "“code end  //note"为代码行而不是注释行(奇怪.jpg)    

 /**
     * @description 查询代码行数,空行数,注释行数
     * @param filePath 文件路径
     */
    private Count countingComplex(String filePath){

        //单行注释               在单字符后的注释(多字符后的注释算做代码行,注释行的前提是 不是代码行)
        String singleLineNote01 = "(\\s*)([{};]?)(\\s*)(/{2})(.*)";
        /*单行注释*/             //在单字符后的注释(多字符后的注释算做代码行,注释行的前提是 不是代码行)
        String singleLineNote02 = "(\\s*)([{};]?)(\\s*)(/\\*)(.*)(\\*/)(\\s*)";
        //   多行注释开头   /*
        String muiltNoteStart = "(\\s*)(/\\*)(.*)";
        //   多行注释结尾   */
        String muiltNoteEnd = "(\\s*)(\\*/)(.*)";

        Count count = new Count();
        int codeLineNum = 0;
        int emptyLineNum = 0;
        int noteLineNum = 0;

        try{
            BufferedReader reader = new BufferedReader(new FileReader(filePath));

            String line = null;

            while ((line = reader.readLine()) != null) {

                int notBlankNum = 0; // 判断非空格类字符数量

                //如果当行匹配注释行,则注释行加一并跳过后面操作,防止影响代码行和空行的计算
                if (line.matches(singleLineNote01) || line.matches(singleLineNote02)) {
                    // 单行注释统计
                    ++ noteLineNum;
                    continue;
                }
                // 多行注释统计
                if (line.matches(muiltNoteStart)) {
                    //第一行  /*  算做注释行
                    while (!line.matches(muiltNoteEnd)){
                        ++ noteLineNum;
                        line = reader.readLine();
                    }
                    //最后一行   */  也算做注释行
                    ++ noteLineNum;
                    continue;
                }
                //逐个获取字符,此时已确定此行不是注释行,再根据字符数量判断是否为代码行或空行
                for (int i = 0; i < line.length(); i++) {

                    char c = line.charAt(i);
                    ++ notBlankNum;

                    if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
                        -- notBlankNum;  //减去多计算的非空格类数
                    }
                }
                if(notBlankNum > 1){
                    ++ codeLineNum;
                }else{
                    ++ emptyLineNum;
                }
            }
            reader.close();
            count.setCodeLineNum(codeLineNum);
            count.setEmptyLineNum(emptyLineNum);
            count.setNoteLineNum(noteLineNum);
        } catch (IOException e) {
            System.out.println("IO执行出错:" + e.getMessage());
        }
        return count;
    }

  (6)其他

/**
     * @description 参数判断
     */
    private String judgeFilePath(Parameter parameter) {

        if(parameter.getFilePath() == null){
            return "\n文件路径不能为空";
        }else if(!(parameter.isCountChar() || parameter.isCountWord() || parameter.isCountLine()
                    || parameter.isCountComplex() || parameter.isFrame())){
            return "\n请输入 -c -w -l -a -x 中至少一个命令";
        }
        //获取目录路径
        String[] paths = parameter.getFilePath().split("\\\\");

        String fileName = paths[paths.length - 1];

        boolean flag = false;

        //先判断文件名中是否含有通配符(因为文件夹名中不含?,*字符,所以若含有通配符则为文件类型)
        for(int i = 0 ; i < fileName.length() ; i ++){
            if(fileName.toCharArray()[i] == '*' || fileName.toCharArray()[i] == '?'){
                flag = true;
            }
        }
        if(flag){

            //含有 ?, * 即为文件类型
            StringBuilder sb = new StringBuilder();

            for(int i = 0 ; i < paths.length - 1 ; i ++) {
                if (i == paths.length - 2) {
                    sb.append(paths[i]);
                } else {
                    sb.append(paths[i]).append("\\");
                }
            }
            //判断目录合法性
            File dirFile = new File(sb.toString());
            if(!dirFile.exists() || (dirFile.exists() && !dirFile.isDirectory())){
                return "\n文件路径出错";
            }
            //文件路径改为目录路径
            parameter.setFilePath(sb.toString());
            //设置需匹配的文件名
            parameter.setMatchName(fileName);

            return null;
        }
        File file = new File(parameter.getFilePath());

        //文件名中不含有通配符时再判断文件是否存在,防止通配符对文件存在判断造成影响
        if(!file.exists()){
            return "\n文件或文件夹不存在";
        }
        return null;
    }

    /**
     * @description 文件名是否匹配
     * @param fileName 实际文件名
     * @param matchName 含通配符的文件名
     */
    private boolean compareName(String fileName, String matchName){

        matchName = matchName.replaceAll("\\?","(.?)").replaceAll("\\*","(.*)");

        matchName = "^" + matchName + "$";

        return fileName.matches(matchName);
    }

6. 测试运行

测试文件:

    

        

测试类:

public class WCTest {
    
    @Test
    public void TestAll(){
        
        System.out.println("________TestBase________");
        TestBase();
        System.out.println("________TestEx________");
        TestEx();
        System.out.println("________TestDir________");
        TestDir();
        System.out.println("________TestMatch________");
        TestMatch();
    }

    @Test
    public void TestBase(){

        String param = "wc.exe -c D:\\tmp\\WCTest.txt";

        Main.main(param.split("\\s+"));
    }

    @Test
    public void TestEx(){

        String param = "wc.exe -c -w -l -a D:\\tmp\\WCTest.txt";

        Main.main(param.split("\\s+"));
    }

    @Test
    public void TestDir(){

        String param = "wc.exe -c -w -l -a -s D:\\tmp";

        Main.main(param.split("\\s+"));
    }

    @Test
    public void TestMatch(){

        String param = "wc.exe -c -w -l -a -s D:\\tmp\\WC*.txt";

        Main.main(param.split("\\s+"));
    }

    @Test
    public void TestFrame(){

        String param = "wc.exe -x";

        Main.main(param.split("\\s+"));
    }
}

测试运行:

________TestBase________

文件名:WCTest.txt
字符数:69
词数:5
行数:13

________TestEx________

文件名:WCTest.txt
字符数:69
词数:5
行数:13
代码行数:2
空行数:6
注释行数:5

________TestDir________

文件名:Test.txt
字符数:8
词数:1
行数:1
代码行数:1
空行数:0
注释行数:0

文件名:WCTest02.txt
字符数:19
词数:2
行数:4
代码行数:1
空行数:2
注释行数:1

文件名:WCTest.txt
字符数:69
词数:5
行数:13
代码行数:2
空行数:6
注释行数:5

________TestMatch________

文件名:WCTest02.txt
字符数:19
词数:2
行数:4
代码行数:1
空行数:2
注释行数:1

文件名:WCTest.txt
字符数:69
词数:5
行数:13
代码行数:2
空行数:6
注释行数:5

图形化界面测试:

   

7.PSP实际花费时间

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

 20

 15

· Estimate

· 估计这个任务需要多少时间

20

 15

Development

开发

300

 430

· Analysis

· 需求分析 (包括学习新技术)

60

 90

· Design Spec

· 生成设计文档

 10

 10

· Design Review

· 设计复审 (和同事审核设计文档)

 0

 0

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

 10

 10

· Design

· 具体设计

50

 80

· Coding

· 具体编码

 80

 100

· Code Review

· 代码复审

 30

 40

· Test

· 测试(自我测试,修改代码,提交修改)

 60

 100

Reporting

报告

60

 80

· Test Report

· 测试报告

 30

 50

· Size Measurement

· 计算工作量

 20

 25

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

 10

 15

合计

 

 760

 1060

8.项目小结

  本次项目我花了较多的时间用于代码的测试和学习新知识上,因此花了远比我认为多的时间,并且最后的结果也仍有一定的问题。  

  同时在开发前没有很好地进行项目结构的设计,导致在具体的代码实现时出现了将原有的部分结构推翻重弄的情况,虽然因为这个项目不会很复杂,没有花多少时间重弄,但如果今后遇到的结构更为复杂的项目,必会给我带来很大的麻烦,因此我在今后的学习中应多注重项目流程的完整实现。

猜你喜欢

转载自www.cnblogs.com/oMIZUCHIo/p/12499624.html
今日推荐