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 &lt; 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}