001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2013 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube 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 * SonarQube 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 License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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   * @return the current line of the cursor
131   */
132  public final int getLinePosition() {
133    return cursor.line;
134  }
135
136  public final Cursor getCursor() {
137    return cursor;
138  }
139
140  /**
141   * @return the current column of the cursor
142   */
143  public final int getColumnPosition() {
144    return cursor.column;
145  }
146
147  /**
148   * Overrides the current column position
149   */
150  public final CodeBuffer setColumnPosition(int cp) {
151    this.cursor.column = cp;
152    return this;
153  }
154
155  /**
156   * Overrides the current line position
157   */
158  public final void setLinePosition(int lp) {
159    this.cursor.line = lp;
160  }
161
162  public final void startRecording() {
163    recordingMode = true;
164  }
165
166  public final CharSequence stopRecording() {
167    recordingMode = false;
168    CharSequence result = recordedCharacters;
169    recordedCharacters = new StringBuilder();
170    return result;
171  }
172
173  /**
174   * Returns the character at the specified index after the cursor without consuming it
175   * 
176   * @param index
177   *          the relative index of the character to be returned
178   * @return the desired character
179   * @see java.lang.CharSequence#charAt(int)
180   */
181  public final char charAt(int index) {
182    return (char)intAt(index);
183  }
184
185  protected final int intAt(int index) {
186    if (bufferPosition + index >= buffer.length) {
187      return -1;
188    }
189    return buffer[bufferPosition + index];
190  }
191
192  /**
193   * Returns the relative length of the string (i.e. excluding the popped chars)
194   */
195  public final int length() {
196    return buffer.length - bufferPosition;
197  }
198
199  public final CharSequence subSequence(int start, int end) {
200    throw new UnsupportedOperationException();
201  }
202
203  @Override
204  public final String toString() {
205    StringBuilder result = new StringBuilder();
206    result.append("CodeReader(");
207    result.append("line:").append(cursor.line);
208    result.append("|column:").append(cursor.column);
209    result.append("|cursor value:'").append((char) peek()).append("'");
210    result.append(")");
211    return result.toString();
212  }
213
214  public final class Cursor implements Cloneable {
215
216    private int line = 1;
217    private int column = 0;
218
219    public int getLine() {
220      return line;
221    }
222
223    public int getColumn() {
224      return column;
225    }
226
227    @Override
228    public Cursor clone() {
229      Cursor clone = new Cursor();
230      clone.column = column;
231      clone.line = line;
232      return clone;
233    }
234  }
235
236  /**
237   * Bridge class between CodeBuffer and CodeReaderFilter
238   */
239  static final class Filter extends FilterReader {
240
241    private CodeReaderFilter<?> codeReaderFilter;
242
243    public Filter(Reader in, CodeReaderFilter<?> codeReaderFilter, CodeReaderConfiguration configuration) {
244      super(in);
245      this.codeReaderFilter = codeReaderFilter;
246      this.codeReaderFilter.setConfiguration(configuration.cloneWithoutCodeReaderFilters());
247      this.codeReaderFilter.setReader(in);
248    }
249
250    @Override
251    public int read() throws IOException {
252      throw new UnsupportedOperationException();
253    }
254
255    @Override
256    public int read(char[] cbuf, int off, int len) throws IOException {
257      int read = codeReaderFilter.read(cbuf, off, len);
258      return read == 0 ? -1 : read;
259    }
260
261    @Override
262    public long skip(long n) throws IOException {
263      throw new UnsupportedOperationException();
264    }
265
266  }
267}