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: private static final String LS;
039: private static final char[] LS_CHARS;
040:
041: static {
042: LS = System.getProperty("line.separator");
043: LS_CHARS = LS.toCharArray();
044: }
045:
046: private boolean atBeginning;
047: private boolean eof;
048: private int pos;
049: private char[] internalBuffer;
050: private PushbackReader internalReader;
051:
052: /**
053: * Creates a DotTerminatedMessageReader that wraps an existing Reader
054: * input source.
055: * @param reader The Reader input source containing the message.
056: */
057: public DotTerminatedMessageReader(Reader reader) {
058: super (reader);
059: internalBuffer = new char[LS_CHARS.length + 3];
060: pos = internalBuffer.length;
061: // Assumes input is at start of message
062: atBeginning = true;
063: eof = false;
064: internalReader = new PushbackReader(reader);
065: }
066:
067: /**
068: * Reads and returns the next character in the message. If the end of the
069: * message has been reached, returns -1. Note that a call to this method
070: * may result in multiple reads from the underlying input stream to decode
071: * the message properly (removing doubled dots and so on). All of
072: * this is transparent to the programmer and is only mentioned for
073: * completeness.
074: * @return The next character in the message. Returns -1 if the end of the
075: * message has been reached.
076: * @exception IOException If an error occurs while reading the underlying
077: * stream.
078: */
079: public int read() throws IOException {
080: int ch;
081:
082: synchronized (lock) {
083: if (pos < internalBuffer.length) {
084: return internalBuffer[pos++];
085: }
086:
087: if (eof) {
088: return -1;
089: }
090:
091: if ((ch = internalReader.read()) == -1) {
092: eof = true;
093: return -1;
094: }
095:
096: if (atBeginning) {
097: atBeginning = false;
098: if (ch == '.') {
099: ch = internalReader.read();
100:
101: if (ch != '.') {
102: // read newline
103: eof = true;
104: internalReader.read();
105: return -1;
106: } else {
107: return '.';
108: }
109: }
110: }
111:
112: if (ch == '\r') {
113: ch = internalReader.read();
114:
115: if (ch == '\n') {
116: ch = internalReader.read();
117:
118: if (ch == '.') {
119: ch = internalReader.read();
120:
121: if (ch != '.') {
122: // read newline and indicate end of file
123: internalReader.read();
124: eof = true;
125: } else {
126: internalBuffer[--pos] = (char) ch;
127: }
128: } else {
129: internalReader.unread(ch);
130: }
131:
132: pos -= LS_CHARS.length;
133: System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
134: LS_CHARS.length);
135: ch = internalBuffer[pos++];
136: } else {
137: internalBuffer[--pos] = (char) ch;
138: return '\r';
139: }
140: }
141:
142: return ch;
143: }
144: }
145:
146: /**
147: * Reads the next characters from the message into an array and
148: * returns the number of characters read. Returns -1 if the end of the
149: * message has been reached.
150: * @param buffer The character array in which to store the characters.
151: * @return The number of characters read. Returns -1 if the
152: * end of the message has been reached.
153: * @exception IOException If an error occurs in reading the underlying
154: * stream.
155: */
156: public int read(char[] buffer) throws IOException {
157: return read(buffer, 0, buffer.length);
158: }
159:
160: /**
161: * Reads the next characters from the message into an array and
162: * returns the number of characters read. Returns -1 if the end of the
163: * message has been reached. The characters are stored in the array
164: * starting from the given offset and up to the length specified.
165: * @param buffer The character array in which to store the characters.
166: * @param offset The offset into the array at which to start storing
167: * characters.
168: * @param length The number of characters to read.
169: * @return The number of characters read. Returns -1 if the
170: * end of the message has been reached.
171: * @exception IOException If an error occurs in reading the underlying
172: * stream.
173: */
174: public int read(char[] buffer, int offset, int length)
175: throws IOException {
176: int ch, off;
177: synchronized (lock) {
178: if (length < 1) {
179: return 0;
180: }
181: if ((ch = read()) == -1) {
182: return -1;
183: }
184: off = offset;
185:
186: do {
187: buffer[offset++] = (char) ch;
188: } while (--length > 0 && (ch = read()) != -1);
189:
190: return (offset - off);
191: }
192: }
193:
194: /**
195: * Determines if the message is ready to be read.
196: * @return True if the message is ready to be read, false if not.
197: * @exception IOException If an error occurs while checking the underlying
198: * stream.
199: */
200: public boolean ready() throws IOException {
201: synchronized (lock) {
202: return (pos < internalBuffer.length || internalReader
203: .ready());
204: }
205: }
206:
207: /**
208: * Closes the message for reading. This doesn't actually close the
209: * underlying stream. The underlying stream may still be used for
210: * communicating with the server and therefore is not closed.
211: * <p>
212: * If the end of the message has not yet been reached, this method
213: * will read the remainder of the message until it reaches the end,
214: * so that the underlying stream may continue to be used properly
215: * for communicating with the server. If you do not fully read
216: * a message, you MUST close it, otherwise your program will likely
217: * hang or behave improperly.
218: * @exception IOException If an error occurs while reading the
219: * underlying stream.
220: */
221: public void close() throws IOException {
222: synchronized (lock) {
223: if (internalReader == null) {
224: return;
225: }
226:
227: if (!eof) {
228: while (read() != -1) {
229: ;
230: }
231: }
232: eof = true;
233: atBeginning = false;
234: pos = internalBuffer.length;
235: internalReader = null;
236: }
237: }
238: }
|