View Javadoc

1   /*
2    * Copyright 2001-2005 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.net.io;
17  
18  import java.io.IOException;
19  import java.io.PushbackReader;
20  import java.io.Reader;
21  
22  /**
23   * DotTerminatedMessageReader is a class used to read messages from a
24   * server that are terminated by a single dot followed by a
25   * <CR><LF>
26   * sequence and with double dots appearing at the begining of lines which
27   * do not signal end of message yet start with a dot.  Various Internet
28   * protocols such as NNTP and POP3 produce messages of this type.
29   * <p>
30   * This class handles stripping of the duplicate period at the beginning
31   * of lines starting with a period, converts NETASCII newlines to the
32   * local line separator format, truncates the end of message indicator,
33   * and ensures you cannot read past the end of the message.
34   * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
35   * @version $Id: DotTerminatedMessageReader.java 165675 2005-05-02 20:09:55Z rwinston $
36   */
37  public final class DotTerminatedMessageReader extends Reader
38  {
39      private static final String LS;
40      private static final char[] LS_CHARS;
41  
42      static
43      {
44          LS = System.getProperty("line.separator");
45          LS_CHARS = LS.toCharArray();
46      }
47  
48      private boolean atBeginning;
49      private boolean eof;
50      private int pos;
51      private char[] internalBuffer;
52      private PushbackReader internalReader;
53  
54      /**
55       * Creates a DotTerminatedMessageReader that wraps an existing Reader
56       * input source.
57       * @param reader  The Reader input source containing the message.
58       */
59      public DotTerminatedMessageReader(Reader reader)
60      {
61          super(reader);
62          internalBuffer = new char[LS_CHARS.length + 3];
63          pos = internalBuffer.length;
64          // Assumes input is at start of message
65          atBeginning = true;
66          eof = false;
67          internalReader = new PushbackReader(reader);
68      }
69  
70      /**
71       * Reads and returns the next character in the message.  If the end of the
72       * message has been reached, returns -1.  Note that a call to this method
73       * may result in multiple reads from the underlying input stream to decode
74       * the message properly (removing doubled dots and so on).  All of
75       * this is transparent to the programmer and is only mentioned for
76       * completeness.
77       * @return The next character in the message. Returns -1 if the end of the
78       *          message has been reached.
79       * @exception IOException If an error occurs while reading the underlying
80       *            stream.
81       */
82      public int read() throws IOException
83      {
84          int ch;
85  
86          synchronized (lock)
87          {
88              if (pos < internalBuffer.length)
89              {
90                  return internalBuffer[pos++];
91              }
92  
93              if (eof)
94              {
95                  return -1;
96              }
97  
98              if ((ch = internalReader.read()) == -1)
99              {
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 }