001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.channel;
021
022 import java.io.FilterReader;
023 import java.io.IOException;
024 import java.io.Reader;
025 import java.io.StringReader;
026
027 import org.apache.commons.io.IOUtils;
028
029 /**
030 * The CodeBuffer class provides all the basic features required to manipulate a source code character stream. Those features are :
031 * <ul>
032 * <li>Read and consume next source code character : pop()</li>
033 * <li>Retrieve last consumed character : lastChar()</li>
034 * <li>Read without consuming next source code character : peek()</li>
035 * <li>Read without consuming character at the specified index after the cursor</li>
036 * <li>Position of the pending cursor : line and column</li>
037 * </ul>
038 */
039 public class CodeBuffer implements CharSequence {
040
041 private int lastChar = -1;
042 private Cursor cursor;
043 private char[] buffer;
044 private int bufferPosition = 0;
045 private static final char LF = '\n';
046 private static final char CR = '\r';
047 private int tabWidth;
048
049 private boolean recordingMode = false;
050 private StringBuilder recordedCharacters = new StringBuilder();
051
052 protected CodeBuffer(String code, CodeReaderConfiguration configuration) {
053 this(new StringReader(code), configuration);
054 }
055
056 /**
057 * Note that this constructor will read everything from reader and will close it.
058 */
059 protected CodeBuffer(Reader initialCodeReader, CodeReaderConfiguration configuration) {
060 Reader reader = null;
061
062 try {
063 lastChar = -1;
064 cursor = new Cursor();
065 tabWidth = configuration.getTabWidth();
066
067 /* Setup the filters on the reader */
068 reader = initialCodeReader;
069 for (CodeReaderFilter<?> codeReaderFilter : configuration.getCodeReaderFilters()) {
070 reader = new Filter(reader, codeReaderFilter, configuration);
071 }
072
073 buffer = IOUtils.toCharArray(reader);
074 } catch (IOException e) {
075 throw new ChannelException(e.getMessage(), e);
076 } finally {
077 IOUtils.closeQuietly(reader);
078 }
079 }
080
081 /**
082 * Read and consume the next character
083 *
084 * @return the next character or -1 if the end of the stream is reached
085 */
086 public final int pop() {
087 if (bufferPosition >= buffer.length) {
088 return -1;
089 }
090 int character = buffer[bufferPosition++];
091 updateCursorPosition(character);
092 if (recordingMode) {
093 recordedCharacters.append((char)character);
094 }
095 lastChar = character;
096 return character;
097 }
098
099 private void updateCursorPosition(int character) {
100 // see Java Language Specification : http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.4
101 if (character == LF || (character == CR && peek() != LF)) {
102 cursor.line++;
103 cursor.column = 0;
104 } else if (character == '\t') {
105 cursor.column += tabWidth;
106 } else {
107 cursor.column++;
108 }
109 }
110
111 /**
112 * Looks at the last consumed character
113 *
114 * @return the last character or -1 if the no character has been yet consumed
115 */
116 public final int lastChar() {
117 return lastChar;
118 }
119
120 /**
121 * Looks at the next character without consuming it
122 *
123 * @return the next character or -1 if the end of the stream has been reached
124 */
125 public final int peek() {
126 return intAt(0);
127 }
128
129 /**
130 * @deprecated in 2.12, do not use anymore.
131 */
132 @Deprecated
133 public final void close() {
134 }
135
136 /**
137 * @return the current line of the cursor
138 */
139 public final int getLinePosition() {
140 return cursor.line;
141 }
142
143 public final Cursor getCursor() {
144 return cursor;
145 }
146
147 /**
148 * @return the current column of the cursor
149 */
150 public final int getColumnPosition() {
151 return cursor.column;
152 }
153
154 /**
155 * Overrides the current column position
156 */
157 public final CodeBuffer setColumnPosition(int cp) {
158 this.cursor.column = cp;
159 return this;
160 }
161
162 /**
163 * Overrides the current line position
164 */
165 public final void setLinePosition(int lp) {
166 this.cursor.line = lp;
167 }
168
169 public final void startRecording() {
170 recordingMode = true;
171 }
172
173 public final CharSequence stopRecording() {
174 recordingMode = false;
175 CharSequence result = recordedCharacters;
176 recordedCharacters = new StringBuilder();
177 return result;
178 }
179
180 /**
181 * Returns the character at the specified index after the cursor without consuming it
182 *
183 * @param index
184 * the relative index of the character to be returned
185 * @return the desired character
186 * @see java.lang.CharSequence#charAt(int)
187 */
188 public final char charAt(int index) {
189 return (char)intAt(index);
190 }
191
192 protected final int intAt(int index) {
193 if (bufferPosition + index >= buffer.length) {
194 return -1;
195 }
196 return buffer[bufferPosition + index];
197 }
198
199 /**
200 * Returns the relative length of the string (i.e. excluding the popped chars)
201 */
202 public final int length() {
203 return buffer.length - bufferPosition;
204 }
205
206 public final CharSequence subSequence(int start, int end) {
207 throw new UnsupportedOperationException();
208 }
209
210 @Override
211 public final String toString() {
212 StringBuilder result = new StringBuilder();
213 result.append("CodeReader(");
214 result.append("line:").append(cursor.line);
215 result.append("|column:").append(cursor.column);
216 result.append("|cursor value:'").append((char) peek()).append("'");
217 result.append(")");
218 return result.toString();
219 }
220
221 public final class Cursor implements Cloneable {
222
223 private int line = 1;
224 private int column = 0;
225
226 public int getLine() {
227 return line;
228 }
229
230 public int getColumn() {
231 return column;
232 }
233
234 @Override
235 public Cursor clone() {
236 Cursor clone = new Cursor();
237 clone.column = column;
238 clone.line = line;
239 return clone;
240 }
241 }
242
243 /**
244 * Bridge class between CodeBuffer and CodeReaderFilter
245 */
246 static final class Filter extends FilterReader {
247
248 private CodeReaderFilter<?> codeReaderFilter;
249
250 public Filter(Reader in, CodeReaderFilter<?> codeReaderFilter, CodeReaderConfiguration configuration) {
251 super(in);
252 this.codeReaderFilter = codeReaderFilter;
253 this.codeReaderFilter.setConfiguration(configuration.cloneWithoutCodeReaderFilters());
254 this.codeReaderFilter.setReader(in);
255 }
256
257 @Override
258 public int read() throws IOException {
259 throw new UnsupportedOperationException();
260 }
261
262 @Override
263 public int read(char[] cbuf, int off, int len) throws IOException {
264 int read = codeReaderFilter.read(cbuf, off, len);
265 return read == 0 ? -1 : read;
266 }
267
268 @Override
269 public long skip(long n) throws IOException {
270 throw new UnsupportedOperationException();
271 }
272
273 }
274 }