001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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    }