/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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 3 of the License, or (at your option) any later version.
*
* SonarQube 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 Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.channel;
import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.apache.commons.io.IOUtils;
/**
* The CodeBuffer class provides all the basic features required to manipulate a source code character stream. Those features are :
*
* - Read and consume next source code character : pop()
* - Retrieve last consumed character : lastChar()
* - Read without consuming next source code character : peek()
* - Read without consuming character at the specified index after the cursor
* - Position of the pending cursor : line and column
*
*/
public class CodeBuffer implements CharSequence {
private int lastChar = -1;
private Cursor cursor;
private char[] buffer;
private int bufferPosition = 0;
private static final char LF = '\n';
private static final char CR = '\r';
private int tabWidth;
private boolean recordingMode = false;
private StringBuilder recordedCharacters = new StringBuilder();
protected CodeBuffer(String code, CodeReaderConfiguration configuration) {
this(new StringReader(code), configuration);
}
/**
* Note that this constructor will read everything from reader and will close it.
*/
protected CodeBuffer(Reader initialCodeReader, CodeReaderConfiguration configuration) {
Reader reader = null;
try {
lastChar = -1;
cursor = new Cursor();
tabWidth = configuration.getTabWidth();
/* Setup the filters on the reader */
reader = initialCodeReader;
for (CodeReaderFilter> codeReaderFilter : configuration.getCodeReaderFilters()) {
reader = new Filter(reader, codeReaderFilter, configuration);
}
buffer = IOUtils.toCharArray(reader);
} catch (IOException e) {
throw new ChannelException(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(reader);
}
}
/**
* Read and consume the next character
*
* @return the next character or -1 if the end of the stream is reached
*/
public final int pop() {
if (bufferPosition >= buffer.length) {
return -1;
}
int character = buffer[bufferPosition++];
updateCursorPosition(character);
if (recordingMode) {
recordedCharacters.append((char)character);
}
lastChar = character;
return character;
}
private void updateCursorPosition(int character) {
// see Java Language Specification : http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.4
if (character == LF || (character == CR && peek() != LF)) {
cursor.line++;
cursor.column = 0;
} else if (character == '\t') {
cursor.column += tabWidth;
} else {
cursor.column++;
}
}
/**
* Looks at the last consumed character
*
* @return the last character or -1 if the no character has been yet consumed
*/
public final int lastChar() {
return lastChar;
}
/**
* Looks at the next character without consuming it
*
* @return the next character or -1 if the end of the stream has been reached
*/
public final int peek() {
return intAt(0);
}
/**
* @deprecated in 2.12, do not use anymore.
*/
@Deprecated
public final void close() {
}
/**
* @return the current line of the cursor
*/
public final int getLinePosition() {
return cursor.line;
}
public final Cursor getCursor() {
return cursor;
}
/**
* @return the current column of the cursor
*/
public final int getColumnPosition() {
return cursor.column;
}
/**
* Overrides the current column position
*/
public final CodeBuffer setColumnPosition(int cp) {
this.cursor.column = cp;
return this;
}
/**
* Overrides the current line position
*/
public final void setLinePosition(int lp) {
this.cursor.line = lp;
}
public final void startRecording() {
recordingMode = true;
}
public final CharSequence stopRecording() {
recordingMode = false;
CharSequence result = recordedCharacters;
recordedCharacters = new StringBuilder();
return result;
}
/**
* Returns the character at the specified index after the cursor without consuming it
*
* @param index
* the relative index of the character to be returned
* @return the desired character
* @see java.lang.CharSequence#charAt(int)
*/
public final char charAt(int index) {
return (char)intAt(index);
}
protected final int intAt(int index) {
if (bufferPosition + index >= buffer.length) {
return -1;
}
return buffer[bufferPosition + index];
}
/**
* Returns the relative length of the string (i.e. excluding the popped chars)
*/
public final int length() {
return buffer.length - bufferPosition;
}
public final CharSequence subSequence(int start, int end) {
throw new UnsupportedOperationException();
}
@Override
public final String toString() {
StringBuilder result = new StringBuilder();
result.append("CodeReader(");
result.append("line:").append(cursor.line);
result.append("|column:").append(cursor.column);
result.append("|cursor value:'").append((char) peek()).append("'");
result.append(")");
return result.toString();
}
public final class Cursor implements Cloneable {
private int line = 1;
private int column = 0;
public int getLine() {
return line;
}
public int getColumn() {
return column;
}
@Override
public Cursor clone() {
Cursor clone = new Cursor();
clone.column = column;
clone.line = line;
return clone;
}
}
/**
* Bridge class between CodeBuffer and CodeReaderFilter
*/
static final class Filter extends FilterReader {
private CodeReaderFilter> codeReaderFilter;
public Filter(Reader in, CodeReaderFilter> codeReaderFilter, CodeReaderConfiguration configuration) {
super(in);
this.codeReaderFilter = codeReaderFilter;
this.codeReaderFilter.setConfiguration(configuration.cloneWithoutCodeReaderFilters());
this.codeReaderFilter.setReader(in);
}
@Override
public int read() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int read = codeReaderFilter.read(cbuf, off, len);
return read == 0 ? -1 : read;
}
@Override
public long skip(long n) throws IOException {
throw new UnsupportedOperationException();
}
}
}