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}