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 }