001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.io; 018 019import java.io.IOException; 020import java.io.Reader; 021import java.text.ParseException; 022 023/** 024 * This is not a reader like e.g. FileReader. It rather reads the whole data until the end from a 025 * source reader into memory and besides that it maintains the current position (like a reader) it 026 * provides String like methods which conveniently let you navigate (usually forward) in the stream. 027 * <p> 028 * Because the source data are expected to be text, the line and column numbers are maintained as 029 * well for location precise error messages. But it does NOT automatically update the line and 030 * column numbers. You must call {@link #countLinesTo(int)} 031 * 032 * @author Juergen Donnerstag 033 */ 034public final class FullyBufferedReader 035{ 036 /** All the chars from the resource */ 037 private final String input; 038 039 /** Position in parse. */ 040 private int inputPosition; 041 042 /** Current line number */ 043 private int lineNumber = 1; 044 045 /** current column number. */ 046 private int columnNumber = 1; 047 048 /** Last place we counted lines from. */ 049 private int lastLineCountIndex; 050 051 /** A variable to remember a certain position in the markup */ 052 private int positionMarker; 053 054 /** 055 * Read all the data from the resource into memory. 056 * 057 * @param reader 058 * The source reader to load the data from 059 * @throws IOException 060 */ 061 public FullyBufferedReader(final Reader reader) throws IOException 062 { 063 this(Streams.readString(reader)); 064 } 065 066 /** 067 * Construct. 068 * 069 * @param input 070 * The source string 071 */ 072 public FullyBufferedReader(String input) 073 { 074 this.input = input; 075 } 076 077 /** 078 * Get the characters from the position marker to toPos. 079 * <p> 080 * If toPos < 0, than get all data from the position marker until the end. If toPos less than 081 * the current position marker than return an empty string "" 082 * 083 * @param toPos 084 * Index of first character not included 085 * @return Raw markup (a string) in between these two positions. 086 */ 087 public final CharSequence getSubstring(int toPos) 088 { 089 if (toPos < 0) 090 { 091 toPos = input.length(); 092 } 093 else if (toPos < positionMarker) 094 { 095 return ""; 096 } 097 return input.subSequence(positionMarker, toPos); 098 } 099 100 /** 101 * Get the characters from in between both positions including the char at fromPos, excluding 102 * the char at toPos 103 * 104 * @param fromPos 105 * first index 106 * @param toPos 107 * second index 108 * @return the string (raw markup) in between both positions 109 */ 110 public final CharSequence getSubstring(final int fromPos, final int toPos) 111 { 112 return input.subSequence(fromPos, toPos); 113 } 114 115 /** 116 * Gets the current input position 117 * 118 * @return input position 119 */ 120 public final int getPosition() 121 { 122 return inputPosition; 123 } 124 125 /** 126 * Remember the current position in markup 127 * 128 * @param pos 129 */ 130 public final void setPositionMarker(final int pos) 131 { 132 positionMarker = pos; 133 } 134 135 /** 136 * @return The markup to be parsed 137 */ 138 @Override 139 public String toString() 140 { 141 return input; 142 } 143 144 /** 145 * Counts lines starting where we last left off up to the index provided. 146 * 147 * @param end 148 * End index 149 */ 150 public final void countLinesTo(final int end) 151 { 152 for (int i = lastLineCountIndex; i < end; i++) 153 { 154 final char ch = input.charAt(i); 155 if (ch == '\n') 156 { 157 columnNumber = 1; 158 lineNumber++; 159 } 160 else if (ch != '\r') 161 { 162 columnNumber++; 163 } 164 } 165 166 lastLineCountIndex = end; 167 } 168 169 /** 170 * Find a char starting at the current input position 171 * 172 * @param ch 173 * The char to search for 174 * @return -1 if not found 175 */ 176 public final int find(final char ch) 177 { 178 return input.indexOf(ch, inputPosition); 179 } 180 181 /** 182 * Find a char starting at the position provided 183 * 184 * @param ch 185 * The char to search for 186 * @param startPos 187 * The index to start at 188 * @return -1 if not found 189 */ 190 public final int find(final char ch, final int startPos) 191 { 192 return input.indexOf(ch, startPos); 193 } 194 195 /** 196 * Find the string starting at the current input position 197 * 198 * @param str 199 * The string to search for 200 * @return -1 if not found 201 */ 202 public final int find(final String str) 203 { 204 return input.indexOf(str, inputPosition); 205 } 206 207 /** 208 * Find the string starting at the position provided 209 * 210 * @param str 211 * The string to search for 212 * @param startPos 213 * The index to start at 214 * @return -1 if not found 215 */ 216 public final int find(final String str, final int startPos) 217 { 218 return input.indexOf(str, startPos); 219 } 220 221 /** 222 * Find a char starting at the position provided. The char must not be inside a quoted string 223 * (single or double) 224 * 225 * @param ch 226 * The char to search for 227 * @param startPos 228 * The index to start at 229 * @return -1 if not found 230 * @throws ParseException 231 */ 232 public int findOutOfQuotes(final char ch, int startPos) throws ParseException 233 { 234 return findOutOfQuotes(ch, startPos, (char)0); 235 } 236 237 /** 238 * Find a char starting at the position provided. The char must not be inside a quoted string 239 * (single or double) 240 * 241 * @param ch 242 * The char to search for 243 * @param startPos 244 * The index to start at 245 * @param quotationChar 246 * The current quotation char. Must be ' or ", otherwise will be ignored. 247 * @return -1 if not found 248 * @throws ParseException 249 */ 250 public int findOutOfQuotes(final char ch, int startPos, char quotationChar) 251 throws ParseException 252 { 253 int closeBracketIndex = find(ch, startPos + 1); 254 255 if (closeBracketIndex != -1) 256 { 257 CharSequence tagCode = getSubstring(startPos, closeBracketIndex + 1); 258 259 for (int i = 0; i < tagCode.length(); i++) 260 { 261 char currentChar = tagCode.charAt(i); 262 char previousTag = tagCode.charAt(i > 0 ? i - 1 : 0); 263 264 if (quotationChar == 0 && (currentChar == '\'' || currentChar == '\"')) 265 {// I'm entering inside a quoted string. Set quotationChar 266 quotationChar = currentChar; 267 countLinesTo(startPos + i); 268 } 269 else if (currentChar == quotationChar && previousTag != '\\') 270 { // I'm out of quotes, reset quotationChar 271 quotationChar = 0; 272 } 273 // I've found character but I'm inside quotes 274 if (currentChar == ch && quotationChar != 0) 275 { 276 return findOutOfQuotes(ch, closeBracketIndex + 1, quotationChar); 277 } 278 } 279 } 280 else if (quotationChar != 0) 281 { 282 // quotes not balanced! 283 throw new ParseException("Opening/closing quote not found for quote at " + "(line " + 284 getLineNumber() + ", column " + getColumnNumber() + ")", startPos); 285 } 286 287 return closeBracketIndex; 288 } 289 290 /** 291 * Position the reader at the index provided. Could be anywhere within the data 292 * 293 * @param pos 294 * The new current position 295 */ 296 public final void setPosition(final int pos) 297 { 298 inputPosition = pos; 299 } 300 301 /** 302 * Get the column number. Note: The column number depends on you calling countLinesTo(pos). It 303 * is not necessarily the column number matching the current position in the stream. 304 * 305 * @return column number 306 */ 307 public final int getColumnNumber() 308 { 309 return columnNumber; 310 } 311 312 /** 313 * Get the line number. Note: The line number depends on you calling countLinesTo(pos). It is 314 * not necessarily the line number matching the current position in the stream. 315 * 316 * @return line number 317 */ 318 public final int getLineNumber() 319 { 320 return lineNumber; 321 } 322 323 /** 324 * Get the number of character read from the source resource. The whole content, not just until 325 * the current position. 326 * 327 * @return Size of the data 328 */ 329 public final int size() 330 { 331 return input.length(); 332 } 333 334 /** 335 * Get the character at the position provided 336 * 337 * @param pos 338 * The position 339 * @return char at position 340 */ 341 public final char charAt(final int pos) 342 { 343 return input.charAt(pos); 344 } 345}