001 /*
002 * Copyright 2001-2005 The Apache Software Foundation
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.apache.commons.net.io;
017
018 import java.io.IOException;
019 import java.io.PushbackReader;
020 import java.io.Reader;
021
022 /**
023 * DotTerminatedMessageReader is a class used to read messages from a
024 * server that are terminated by a single dot followed by a
025 * <CR><LF>
026 * sequence and with double dots appearing at the begining of lines which
027 * do not signal end of message yet start with a dot. Various Internet
028 * protocols such as NNTP and POP3 produce messages of this type.
029 * <p>
030 * This class handles stripping of the duplicate period at the beginning
031 * of lines starting with a period, converts NETASCII newlines to the
032 * local line separator format, truncates the end of message indicator,
033 * and ensures you cannot read past the end of the message.
034 * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
035 * @version $Id: DotTerminatedMessageReader.java 165675 2005-05-02 20:09:55Z rwinston $
036 */
037 public final class DotTerminatedMessageReader extends Reader
038 {
039 private static final String LS;
040 private static final char[] LS_CHARS;
041
042 static
043 {
044 LS = System.getProperty("line.separator");
045 LS_CHARS = LS.toCharArray();
046 }
047
048 private boolean atBeginning;
049 private boolean eof;
050 private int pos;
051 private char[] internalBuffer;
052 private PushbackReader internalReader;
053
054 /**
055 * Creates a DotTerminatedMessageReader that wraps an existing Reader
056 * input source.
057 * @param reader The Reader input source containing the message.
058 */
059 public DotTerminatedMessageReader(Reader reader)
060 {
061 super(reader);
062 internalBuffer = new char[LS_CHARS.length + 3];
063 pos = internalBuffer.length;
064 // Assumes input is at start of message
065 atBeginning = true;
066 eof = false;
067 internalReader = new PushbackReader(reader);
068 }
069
070 /**
071 * Reads and returns the next character in the message. If the end of the
072 * message has been reached, returns -1. Note that a call to this method
073 * may result in multiple reads from the underlying input stream to decode
074 * the message properly (removing doubled dots and so on). All of
075 * this is transparent to the programmer and is only mentioned for
076 * completeness.
077 * @return The next character in the message. Returns -1 if the end of the
078 * message has been reached.
079 * @exception IOException If an error occurs while reading the underlying
080 * stream.
081 */
082 public int read() throws IOException
083 {
084 int ch;
085
086 synchronized (lock)
087 {
088 if (pos < internalBuffer.length)
089 {
090 return internalBuffer[pos++];
091 }
092
093 if (eof)
094 {
095 return -1;
096 }
097
098 if ((ch = internalReader.read()) == -1)
099 {
100 eof = true;
101 return -1;
102 }
103
104 if (atBeginning)
105 {
106 atBeginning = false;
107 if (ch == '.')
108 {
109 ch = internalReader.read();
110
111 if (ch != '.')
112 {
113 // read newline
114 eof = true;
115 internalReader.read();
116 return -1;
117 }
118 else
119 {
120 return '.';
121 }
122 }
123 }
124
125 if (ch == '\r')
126 {
127 ch = internalReader.read();
128
129 if (ch == '\n')
130 {
131 ch = internalReader.read();
132
133 if (ch == '.')
134 {
135 ch = internalReader.read();
136
137 if (ch != '.')
138 {
139 // read newline and indicate end of file
140 internalReader.read();
141 eof = true;
142 }
143 else
144 {
145 internalBuffer[--pos] = (char) ch;
146 }
147 }
148 else
149 {
150 internalReader.unread(ch);
151 }
152
153 pos -= LS_CHARS.length;
154 System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
155 LS_CHARS.length);
156 ch = internalBuffer[pos++];
157 }
158 else
159 {
160 internalBuffer[--pos] = (char) ch;
161 return '\r';
162 }
163 }
164
165 return ch;
166 }
167 }
168
169 /**
170 * Reads the next characters from the message into an array and
171 * returns the number of characters read. Returns -1 if the end of the
172 * message has been reached.
173 * @param buffer The character array in which to store the characters.
174 * @return The number of characters read. Returns -1 if the
175 * end of the message has been reached.
176 * @exception IOException If an error occurs in reading the underlying
177 * stream.
178 */
179 public int read(char[] buffer) throws IOException
180 {
181 return read(buffer, 0, buffer.length);
182 }
183
184 /**
185 * Reads the next characters from the message into an array and
186 * returns the number of characters read. Returns -1 if the end of the
187 * message has been reached. The characters are stored in the array
188 * starting from the given offset and up to the length specified.
189 * @param buffer The character array in which to store the characters.
190 * @param offset The offset into the array at which to start storing
191 * characters.
192 * @param length The number of characters to read.
193 * @return The number of characters read. Returns -1 if the
194 * end of the message has been reached.
195 * @exception IOException If an error occurs in reading the underlying
196 * stream.
197 */
198 public int read(char[] buffer, int offset, int length) throws IOException
199 {
200 int ch, off;
201 synchronized (lock)
202 {
203 if (length < 1)
204 {
205 return 0;
206 }
207 if ((ch = read()) == -1)
208 {
209 return -1;
210 }
211 off = offset;
212
213 do
214 {
215 buffer[offset++] = (char) ch;
216 }
217 while (--length > 0 && (ch = read()) != -1);
218
219 return (offset - off);
220 }
221 }
222
223 /**
224 * Determines if the message is ready to be read.
225 * @return True if the message is ready to be read, false if not.
226 * @exception IOException If an error occurs while checking the underlying
227 * stream.
228 */
229 public boolean ready() throws IOException
230 {
231 synchronized (lock)
232 {
233 return (pos < internalBuffer.length || internalReader.ready());
234 }
235 }
236
237 /**
238 * Closes the message for reading. This doesn't actually close the
239 * underlying stream. The underlying stream may still be used for
240 * communicating with the server and therefore is not closed.
241 * <p>
242 * If the end of the message has not yet been reached, this method
243 * will read the remainder of the message until it reaches the end,
244 * so that the underlying stream may continue to be used properly
245 * for communicating with the server. If you do not fully read
246 * a message, you MUST close it, otherwise your program will likely
247 * hang or behave improperly.
248 * @exception IOException If an error occurs while reading the
249 * underlying stream.
250 */
251 public void close() throws IOException
252 {
253 synchronized (lock)
254 {
255 if (internalReader == null)
256 {
257 return;
258 }
259
260 if (!eof)
261 {
262 while (read() != -1)
263 {
264 ;
265 }
266 }
267 eof = true;
268 atBeginning = false;
269 pos = internalBuffer.length;
270 internalReader = null;
271 }
272 }
273 }