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 if (len <= 0)
117 return 0;
118
119 boolean extraChar = _hasBufferedChar;
120 if (_hasBufferedChar) {
121 _hasBufferedChar = false;
122 cbuf[off++] = _bufferedChar;
123 len--;
124 }
125
126 int read = _reader.read(cbuf, off, len);
127 if (extraChar)
128 read++;
129 return read;
130 }
131
132 /**
133 * @see java.io.FilterReader#ready()
134 */
135 public synchronized boolean ready() throws IOException
136 {
137 if (_hasBufferedChar)
138 return true;
139 return _reader.ready();
140 }
141
142 /**
143 * @see java.io.FilterReader#markSupported()
144 */
145 public synchronized boolean markSupported()
146 {
147 return false;
148 }
149
150 /**
151 * @see java.io.FilterReader#reset()
152 */
153 public synchronized void reset() throws IOException
154 {
155 _hasBufferedChar = false;
156 _reader.reset();
157 }
158
159 /**
160 * @see java.io.FilterReader#skip(long)
161 */
162 public synchronized long skip(long n) throws IOException
163 {
164 if (_hasBufferedChar && n > 0) {
165 _hasBufferedChar = false;
166 n--;
167 }
168 return _reader.skip(n);
169 }
170
171 /**
172 * @see java.io.Reader#close()
173 */
174 public synchronized void close() throws IOException
175 {
176 _hasBufferedChar = false;
177 _reader.close();
178 }
179
180 }