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    }