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 }