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 }