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 */
020package org.sonar.channel;
021
022import java.io.FilterReader;
023import java.io.IOException;
024import java.io.Reader;
025import java.io.StringReader;
026
027import 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 */
039public 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}