/*
* Copyright (C) 2011 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.portal.resource;
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 final StringBuilder pushbackCache;
private static final int EOF = -1;
private State cursorState;
private CommentBlockHandler commentBlockHandler;
/* The number of next comming characters that won't be skipped even if they are in a comment block */
private int numberOfCommingEscapes;
public SkipCommentReader(Reader reader) {
this(reader, null);
}
public SkipCommentReader(Reader reader, CommentBlockHandler handler) {
super(reader);
pushbackCache = new StringBuilder();
cursorState = State.ENCOUNTING_ORDINARY_CHARACTER;
this.commentBlockHandler = handler;
}
/**
* 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;
}
if (numberOfCommingEscapes > 0) {
numberOfCommingEscapes--;
return readingChar;
}
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();
StringBuilder commentBlock = new StringBuilder("/*");
LOOP: while (readingChar != EOF) {
commentBlock.append((char) readingChar);
if (readingChar == '/') {
if (this.cursorState == SkipCommentReader.State.ENCOUNTING_ASTERIK) {
this.cursorState = SkipCommentReader.State.ENCOUNTING_COMMENT_BLOCK_CLOSING_TAG;
break LOOP; // We 've just escaped 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();
}
if (commentBlockHandler != null) {
commentBlockHandler.handle(commentBlock, this);
}
}
@Override
public String readLine() throws IOException {
StringBuilder builder = new StringBuilder();
int nextChar = readSingleCharacter();
if (nextChar == EOF) {
return null;
}
while (nextChar != EOF) {
if (nextChar == '\n' || nextChar == '\r') {
break;
}
builder.append((char) nextChar);
nextChar = readSingleCharacter();
}
return builder.toString().trim();
}
/**
* Used for JUnit tests
*
* @return
*/
public State getCursorState() {
return this.cursorState;
}
public void setCommentBlockHandler(CommentBlockHandler commentBlockHandler) {
this.commentBlockHandler = commentBlockHandler;
}
public void setNumberOfCommingEscapes(int numberOfCommingEscapes) {
this.numberOfCommingEscapes = numberOfCommingEscapes;
}
public void pushback(CharSequence sequence) {
this.pushbackCache.append(sequence);
}
public enum State {
ENCOUNTING_FORWARD_SLASH,
ENCOUNTING_ASTERIK,
ENCOUNTING_COMMENT_BLOCK_OPENING_TAG,
ENCOUNTING_COMMENT_BLOCK_CLOSING_TAG,
ENCOUNTING_ORDINARY_CHARACTER
}
}