import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; /** * * A subclass of BufferedReader which skip the comment block * * @author <a href="hoang281283@gmail.com">Minh Hoang TO</a> * @date 6/27/11 */ public class SkipCommentReader extends BufferedReader { private BufferedReader bufferedReader; private final StringBuilder pushbackCache; private final static int EOF = -1; private State cursorState; public SkipCommentReader(Reader reader) { super(reader); pushbackCache = new StringBuilder(); cursorState = State.ENCOUNTING_ORDINARY_CHARACTER; } /** * Recursive method that read a single character from underlying reader. Encountered comment block * is escaped automatically. * * @return * @throws IOException */ public int readSingleCharacter() throws IOException { int readingChar = readLikePushbackReader(); if(readingChar == EOF) { return EOF; } switch (readingChar) { case '/': int nextCharToRead = read(); if (nextCharToRead == '*') { this.cursorState = SkipCommentReader.State.ENCOUNTING_COMMENT_BLOCK_OPENING_TAG; advanceToEscapeCommentBlock(); return readSingleCharacter(); } else { this.cursorState = SkipCommentReader.State.ENCOUNTING_FORWARD_SLASH; pushbackCache.append((char)nextCharToRead); return '/'; } case '*': if (this.cursorState == SkipCommentReader.State.ENCOUNTING_FORWARD_SLASH) { this.cursorState = SkipCommentReader.State.ENCOUNTING_COMMENT_BLOCK_OPENING_TAG; advanceToEscapeCommentBlock(); return readSingleCharacter(); } else { this.cursorState = SkipCommentReader.State.ENCOUNTING_ASTERIK; return '*'; } default: this.cursorState = SkipCommentReader.State.ENCOUNTING_ORDINARY_CHARACTER; return readingChar; } } /** * Read from the pushback cache first, then underlying reader */ private int readLikePushbackReader() throws IOException { if(pushbackCache.length() > 0) { int readingChar = pushbackCache.charAt(0); pushbackCache.deleteCharAt(0); return readingChar; } return read(); } /** * Advance in comment block until we reach a comment block closing tag */ private void advanceToEscapeCommentBlock() throws IOException { if(cursorState != SkipCommentReader.State.ENCOUNTING_COMMENT_BLOCK_OPENING_TAG) { throw new IllegalStateException("This method should be invoked only if we are entering a comment block"); } int readingChar = read(); whileLoop: while(readingChar != EOF) { if(readingChar == '/') { if(this.cursorState == SkipCommentReader.State.ENCOUNTING_ASTERIK) { break whileLoop; //We 've just escape the comment block } else { this.cursorState = SkipCommentReader.State.ENCOUNTING_FORWARD_SLASH; } } else { this.cursorState = (readingChar == '*')? SkipCommentReader.State.ENCOUNTING_ASTERIK : SkipCommentReader.State.ENCOUNTING_ORDINARY_CHARACTER; } readingChar = read(); } } @Override public String readLine() throws IOException { StringBuilder builder = new StringBuilder(); int nextChar = readSingleCharacter(); while(nextChar != EOF) { if(nextChar == '\n' || nextChar == '\r') { break; } builder.append((char)nextChar); nextChar = readSingleCharacter(); } String line = builder.toString().trim(); //Below recursive call is necessary to skip blank between two consecutive comment blocks if(line.length() > 0) { return line; } else { return readLine(); } } public State getCursorState() { return this.cursorState; } public enum State { ENCOUNTING_FORWARD_SLASH, ENCOUNTING_ASTERIK, ENCOUNTING_COMMENT_BLOCK_OPENING_TAG, ENCOUNTING_COMMENT_BLOCK_CLOSING_TAG, ENCOUNTING_ORDINARY_CHARACTER } }