package com.e2u.fsm; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; /** * The expected formats include: c/c++, java */ public class CCByFSM { /** * All the states in the FSM. */ private static final int STAT_NONE = 0; private static final int STAT_FIRST_SLASH = 1; private static final int STAT_SECOND_SLASH = 2; private static final int STAT_STAR_AFTER_SLASH = 3; private static final int STAT_STAR_IN_BLOCK_COMMENT = 4; private static final int STAT_ESC_AFTER_SECOND_STAR = 5; private static final int STAT_FIRST_QM = 6; private static final int STAT_ESC_AFTER_FIRST_QM = 7; private static final int STAT_FIRST_SQM = 8; private static final int STAT_ESC_AFTER_FIRST_SQM = 9; /** * The first state and the last state */ public static final int STAT_FIRST = STAT_NONE; public static final int STAT_LAST = STAT_ESC_AFTER_FIRST_SQM; /** * The mode to determine how to count when code and comment are in the same one line */ public static final int COUNT_0_CODE_0_COMMENT = 0; public static final int COUNT_0_CODE_1_COMMENT = 1; public static final int COUNT_1_CODE_0_COMMENT = 2; public static final int COUNT_1_CODE_1_COMMENT = 3; /** * The current used mode for one line contains both code and comment */ private int modeCodeCommentInOneLine = COUNT_0_CODE_0_COMMENT; /** * The mode to determine the blank to be which type */ public static final int COUNT_BLANK_LINE_AS_BLANK_IN_COMMENT_BLOCK = 0; public static final int COUNT_BLANK_LINE_AS_COMMENT_IN_COMMENT_BLOCK = 1; /** * The current mode used to determine the blank line type */ private int modeCountBlankLine = COUNT_BLANK_LINE_AS_BLANK_IN_COMMENT_BLOCK; /** * The following is the processing result */ //Total line count private int totalLine = 0; //Code line count private int codeLine = 0; //Comment line count private int commentLine = 0; //Mixed line count ( the line contains both comment and code ) private int mixedLine = 0; //Blank line count private int blankLine = 0; /** * Internal variables */ //Current state private int curStat = STAT_NONE; //Code segment count encountered in this line private int codeSeg = 0; //Comment segment count encountered in this line private int commentSeg = 0; //In the non-comment segment, is it all blank? //In the comment segment, is the previous code segment all blank? private boolean isAllBlank = true; /** * FSM */ //State transition chart private String[][] grammar = { {"/", "\"", "'", ""}, // STAT_NONE {"/", "*", ""}, // STAT_FIRST_SLASH {""}, // STAT_SECOND_SLASH {"*", ""}, // STAT_STAR_AFTER_SLASH {"/", "\\", "*", ""}, // STAT_STAR_IN_BLOCK_COMMENT {""}, // STAT_ESC_AFTER_SECOND_STAR {"\\", "\"", ""}, // STAT_FIRST_QM {""}, // STAT_ESC_AFTER_FIRST_QM {"\\", "'", ""}, // STAT_FIRST_SQM {""} // STAT_ESC_AFTER_FIRST_SQM }; //Next state private int[][] successor = { {STAT_FIRST_SLASH, STAT_FIRST_QM, STAT_FIRST_SQM, STAT_NONE}, {STAT_SECOND_SLASH, STAT_STAR_AFTER_SLASH, STAT_NONE}, {STAT_SECOND_SLASH}, {STAT_STAR_IN_BLOCK_COMMENT, STAT_STAR_AFTER_SLASH}, {STAT_NONE, STAT_ESC_AFTER_SECOND_STAR, STAT_STAR_IN_BLOCK_COMMENT, STAT_STAR_AFTER_SLASH}, {STAT_STAR_AFTER_SLASH}, {STAT_ESC_AFTER_FIRST_QM, STAT_NONE, STAT_FIRST_QM}, {STAT_FIRST_QM}, {STAT_ESC_AFTER_FIRST_SQM, STAT_NONE, STAT_FIRST_SQM}, {STAT_FIRST_SQM} }; private int[] nextLineState = { STAT_NONE, // STAT_NONE STAT_NONE, // STAT_FIRST_SLASH STAT_NONE, // STAT_SECOND_SLASH STAT_STAR_AFTER_SLASH, // STAT_STAR_AFTER_SLASH STAT_STAR_AFTER_SLASH, // STAT_STAR_IN_BLOCK_COMMENT STAT_STAR_AFTER_SLASH, // STAT_ESC_AFTER_SECOND_STAR STAT_FIRST_QM, // STAT_FIRST_QM STAT_FIRST_QM, // STAT_ESC_AFTER_FIRST_QM STAT_FIRST_SQM, // STAT_FIRST_SQM STAT_FIRST_SQM // STAT_ESC_AFTER_FIRST_SQM }; private OnAction cbmsAction = new CodeBlankMarkSetAction(); private OnAction csiAction = new CommentSegIncAction(); private OnAction iCommentAction = new IncCommentOnLastCharAction(); private OnAction iCodeAction = new IncCodeOnLastCharAction(); private OnAction commentEndAction = new CommentEndAction(); private OnAction[][] actions = { {cbmsAction, cbmsAction, cbmsAction, cbmsAction}, {csiAction, csiAction, csiAction}, {iCommentAction}, {iCommentAction, iCommentAction}, {commentEndAction, iCommentAction, iCommentAction, iCommentAction}, {iCommentAction}, {iCodeAction, iCodeAction, iCodeAction}, {iCodeAction}, {iCodeAction, iCodeAction, iCodeAction}, {iCodeAction} }; private interface OnAction { public void action(String line, int chIndex); } private class CodeBlankMarkSetAction extends IncCodeOnLastCharAction { public void action(String line, int chIndex) { if(curStat != STAT_NONE) { return; } int ch = line.charAt(chIndex); String[] theGrammar = grammar[curStat]; for(int i = 0; i < theGrammar.length; i++) { //No need to set if(!theGrammar[i].isEmpty() && theGrammar[i].indexOf(ch) != -1) { break; } else if(theGrammar[i].isEmpty()) { if(!isSpace(ch)) { isAllBlank = false; } break; } } super.action(line, chIndex); } } private class CommentSegIncAction extends IncCommentOnLastCharAction { public void action(String line, int chIndex) { if(curStat != STAT_FIRST_SLASH) { return; } int ch = line.charAt(chIndex); String[] theGrammar = grammar[curStat]; for(int i = 0; i < theGrammar.length; i++) { if(!theGrammar[i].isEmpty() && theGrammar[i].indexOf(ch) != -1) { if(!isAllBlank) { codeSeg++; isAllBlank = true; } commentSeg++; break; } else if(theGrammar[i].isEmpty()) { isAllBlank = false; break; } } //Last char if(chIndex == (line.length() - 1)) { if(codeSeg > 0 && commentSeg > 0) { countCodeCommentInOneLine(); } else { for(int i = 0; i < theGrammar.length; i++) { if(!theGrammar[i].isEmpty() && theGrammar[i].indexOf(ch) != -1) { commentLine++; break; } else if(theGrammar[i].isEmpty()) { codeLine++; break; } } } } } } private class CommentEndAction extends IncCommentOnLastCharAction { public void action(String line, int chIndex) { if(commentSeg == 0) { commentSeg++; } isAllBlank = true; super.action(line, chIndex); } } private class IncCommentOnLastCharAction implements OnAction { public void action(String line, int chIndex) { //Last char if(chIndex == (line.length() - 1)) { if(codeSeg > 0) { countCodeCommentInOneLine(); } else { commentLine++; } } } } private class IncCodeOnLastCharAction implements OnAction { public void action(String line, int chIndex) { //Last char if(chIndex == (line.length() - 1)) { if(commentSeg > 0) { countCodeCommentInOneLine(); } else { codeLine++; } } } } public CCByFSM() { curStat = STAT_NONE; codeSeg = 0; commentSeg = 0; isAllBlank = true; totalLine = 0; codeLine = 0; commentLine = 0; mixedLine = 0; blankLine = 0; } /** * Start to work, count the code lines * @param reader */ public void parse(BufferedReader reader) { curStat = STAT_NONE; String line = null; try { while( (line = reader.readLine()) != null) { codeSeg = 0; commentSeg = 0; isAllBlank = true; totalLine++; line = line.trim(); if(line.isEmpty()) { countBlankLineInCommentBlock(); continue; } for(int i = 0, size = line.length(); i < size; i++) { curStat = transition(line, i, curStat); } } } catch(Exception e) { e.printStackTrace(); } } /** * State transition function * @param line * @param chIndex * @param state * @return */ private int transition(String line, int chIndex, int state) { int nextState = -1; String[] grammars = grammar[state]; int ch = line.charAt(chIndex); for(int i = 0; i < grammars.length; i++) { if(grammars[i].isEmpty() || grammars[i].indexOf(ch) != -1) { if(actions[state][i] != null) { actions[state][i].action(line, chIndex); } nextState = successor[state][i]; //Last char if(chIndex == line.length() - 1) { nextState = nextLineState[nextState]; } break; } } if(nextState < STAT_FIRST || nextState > STAT_LAST) { throw new IllegalArgumentException("Fatal Error: result = " + nextState); } return nextState; } private void countCodeCommentInOneLine() { countCodeCommentInOneLine(modeCodeCommentInOneLine); } private void countCodeCommentInOneLine(int mode) { mixedLine++; switch(mode) { case COUNT_0_CODE_0_COMMENT: { break; } case COUNT_0_CODE_1_COMMENT: { commentLine++; break; } case COUNT_1_CODE_0_COMMENT: { codeLine++; break; } case COUNT_1_CODE_1_COMMENT: { codeLine++; commentLine++; break; } default: { System.out.println("Unknown mode : " + mode); break; } } } private void countBlankLineInCommentBlock() { if(curStat == STAT_STAR_AFTER_SLASH) { switch(modeCountBlankLine) { case COUNT_BLANK_LINE_AS_BLANK_IN_COMMENT_BLOCK: { blankLine++; break; } case COUNT_BLANK_LINE_AS_COMMENT_IN_COMMENT_BLOCK: { commentLine++; break; } default: { System.out.println("Unknown mode : " + modeCountBlankLine); break; } } } else { blankLine++; } } public int getModeCodeCommentInOneLine() { return modeCodeCommentInOneLine; } public void setModeCodeCommentInOneLine( int modeCodeCommentInOneLine) { this.modeCodeCommentInOneLine = modeCodeCommentInOneLine; } public int getModeCountBlankLine() { return modeCountBlankLine; } public void setModeCountBlankLine(int modeCountBlankLine) { this.modeCountBlankLine = modeCountBlankLine; } private boolean isSpace(int ch) { if(ch == ' ' || ch == '\t') { return true; } return false; } public void showResult() { System.out.printf("Total Line Count = %d\n", totalLine); System.out.printf("Code Line Count = %d\n", codeLine); System.out.printf("Comment Line Count = %d\n", commentLine); System.out.printf("Mixed Line Count = %d\n", mixedLine); System.out.printf("Blank Line Count = %d\n", blankLine); } public static void main(String[] args) { CCByFSM cc = new CCByFSM(); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(new File("test.java"))); cc.parse(reader); cc.showResult(); reader.close(); } catch(Exception e) { e.printStackTrace(); } } }