001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.util.text;
016
017 import java.io.IOException;
018 import java.io.Reader;
019
020 /**
021 * A Reader that provides some additional functionality, such as peek().
022 *
023 * @author mb
024 * @since 4.0
025 */
026 public class ExtendedReader extends Reader
027 {
028 private Reader _reader;
029 private boolean _hasBufferedChar = false;
030 private char _bufferedChar;
031
032 /**
033 * Creates a new extended reader that reads from the provided object.
034 *
035 * @param in the Reader to get data from
036 */
037 public ExtendedReader(Reader in)
038 {
039 _reader = in;
040 }
041
042 /**
043 * Returns the next character in the stream without actually comitting the read.
044 * Multiple consequtive invocations of this method should return the same value.
045 *
046 * @return the next character waiting in the stream or -1 if the end of the stream is reached
047 * @throws IOException if an error occurs
048 */
049 public synchronized int peek() throws IOException
050 {
051 if (!_hasBufferedChar) {
052 int bufferedChar = read();
053 if (bufferedChar < 0)
054 return bufferedChar;
055 _bufferedChar = (char) bufferedChar;
056 _hasBufferedChar = true;
057 }
058 return _bufferedChar;
059 }
060
061 /**
062 * Determines whether the end of the stream is reached.
063 *
064 * @return true if at the end of stream
065 * @throws IOException if an error occurs
066 */
067 public synchronized boolean isEndOfStream() throws IOException
068 {
069 return peek() < 0;
070 }
071
072 /**
073 * Skips the next characters until a character that does not match the provided rule is reached.
074 *
075 * @param matcher the object determining whether a character should be skipped
076 * @throws IOException if an error occurs
077 */
078 public synchronized void skipCharacters(ICharacterMatcher matcher) throws IOException
079 {
080 while (true) {
081 if (isEndOfStream())
082 break;
083 char ch = (char) peek();
084 if (!matcher.matches(ch))
085 break;
086 read();
087 }
088 }
089
090 /**
091 * Reads the next characters until a character that does not match the provided rule is reached.
092 *
093 * @param matcher the object determining whether a character should be read
094 * @return the string of characters read
095 * @throws IOException if an error occurs
096 */
097 public synchronized String readCharacters(ICharacterMatcher matcher) throws IOException
098 {
099 StringBuffer buf = new StringBuffer();
100 while (true) {
101 if (isEndOfStream())
102 break;
103 char ch = (char) peek();
104 if (!matcher.matches(ch))
105 break;
106 buf.append(read());
107 }
108 return buf.toString();
109 }
110
111 /**
112 * @see java.io.FilterReader#read(char[], int, int)
113 */
114 public synchronized int read(char[] cbuf, int off, int len) throws IOException
115 {
116 int offset = off;
117 if (len <= 0)
118 return 0;
119 int readLength = len;
120
121 boolean extraChar = _hasBufferedChar;
122 if (_hasBufferedChar) {
123 _hasBufferedChar = false;
124 cbuf[offset++] = _bufferedChar;
125 readLength--;
126 }
127
128 int read = _reader.read(cbuf, offset, readLength);
129 if (extraChar)
130 read++;
131 return read;
132 }
133
134 /**
135 * @see java.io.FilterReader#ready()
136 */
137 public synchronized boolean ready() throws IOException
138 {
139 if (_hasBufferedChar)
140 return true;
141 return _reader.ready();
142 }
143
144 /**
145 * @see java.io.FilterReader#markSupported()
146 */
147 public synchronized boolean markSupported()
148 {
149 return false;
150 }
151
152 /**
153 * @see java.io.FilterReader#reset()
154 */
155 public synchronized void reset() throws IOException
156 {
157 _hasBufferedChar = false;
158 _reader.reset();
159 }
160
161 /**
162 * @see java.io.FilterReader#skip(long)
163 */
164 public synchronized long skip(long n) throws IOException
165 {
166 long skipChars = n;
167 if (_hasBufferedChar && skipChars > 0) {
168 _hasBufferedChar = false;
169 skipChars--;
170 }
171 return _reader.skip(skipChars);
172 }
173
174 /**
175 * @see java.io.Reader#close()
176 */
177 public synchronized void close() throws IOException
178 {
179 _hasBufferedChar = false;
180 _reader.close();
181 }
182
183 }