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
015package org.apache.tapestry.util.text;
016
017import java.io.IOException;
018import 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 */
026public 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}