View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.io;
19  
20  import java.io.IOException;
21  import java.io.PushbackReader;
22  import java.io.Reader;
23  
24  /**
25   * DotTerminatedMessageReader is a class used to read messages from a
26   * server that are terminated by a single dot followed by a
27   * <CR><LF>
28   * sequence and with double dots appearing at the begining of lines which
29   * do not signal end of message yet start with a dot.  Various Internet
30   * protocols such as NNTP and POP3 produce messages of this type.
31   * <p>
32   * This class handles stripping of the duplicate period at the beginning
33   * of lines starting with a period, converts NETASCII newlines to the
34   * local line separator format, truncates the end of message indicator,
35   * and ensures you cannot read past the end of the message.
36   * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
37   * @version $Id: DotTerminatedMessageReader.java 636825 2008-03-13 18:34:52Z sebb $
38   */
39  public final class DotTerminatedMessageReader extends Reader
40  {
41      private static final String LS;
42      private static final char[] LS_CHARS;
43  
44      static
45      {
46          LS = System.getProperty("line.separator");
47          LS_CHARS = LS.toCharArray();
48      }
49  
50      private boolean atBeginning;
51      private boolean eof;
52      private int pos;
53      private char[] internalBuffer;
54      private PushbackReader internalReader;
55  
56      /**
57       * Creates a DotTerminatedMessageReader that wraps an existing Reader
58       * input source.
59       * @param reader  The Reader input source containing the message.
60       */
61      public DotTerminatedMessageReader(Reader reader)
62      {
63          super(reader);
64          internalBuffer = new char[LS_CHARS.length + 3];
65          pos = internalBuffer.length;
66          // Assumes input is at start of message
67          atBeginning = true;
68          eof = false;
69          internalReader = new PushbackReader(reader);
70      }
71  
72      /**
73       * Reads and returns the next character in the message.  If the end of the
74       * message has been reached, returns -1.  Note that a call to this method
75       * may result in multiple reads from the underlying input stream to decode
76       * the message properly (removing doubled dots and so on).  All of
77       * this is transparent to the programmer and is only mentioned for
78       * completeness.
79       * @return The next character in the message. Returns -1 if the end of the
80       *          message has been reached.
81       * @exception IOException If an error occurs while reading the underlying
82       *            stream.
83       */
84      @Override
85      public int read() throws IOException
86      {
87          int ch;
88  
89          synchronized (lock)
90          {
91              if (pos < internalBuffer.length)
92              {
93                  return internalBuffer[pos++];
94              }
95  
96              if (eof)
97              {
98                  return -1;
99              }
100 
101             if ((ch = internalReader.read()) == -1)
102             {
103                 eof = true;
104                 return -1;
105             }
106 
107             if (atBeginning)
108             {
109                 atBeginning = false;
110                 if (ch == '.')
111                 {
112                     ch = internalReader.read();
113 
114                     if (ch != '.')
115                     {
116                         // read newline
117                         eof = true;
118                         internalReader.read();
119                         return -1;
120                     }
121                     else
122                     {
123                         return '.';
124                     }
125                 }
126             }
127 
128             if (ch == '\r')
129             {
130                 ch = internalReader.read();
131 
132                 if (ch == '\n')
133                 {
134                     ch = internalReader.read();
135 
136                     if (ch == '.')
137                     {
138                         ch = internalReader.read();
139 
140                         if (ch != '.')
141                         {
142                             // read newline and indicate end of file
143                             internalReader.read();
144                             eof = true;
145                         }
146                         else
147                         {
148                             internalBuffer[--pos] = (char) ch;
149                         }
150                     }
151                     else
152                     {
153                         internalReader.unread(ch);
154                     }
155 
156                     pos -= LS_CHARS.length;
157                     System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
158                                      LS_CHARS.length);
159                     ch = internalBuffer[pos++];
160                 }
161                 else
162                 {
163                     internalBuffer[--pos] = (char) ch;
164                     return '\r';
165                 }
166             }
167 
168             return ch;
169         }
170     }
171 
172     /**
173      * Reads the next characters from the message into an array and
174      * returns the number of characters read.  Returns -1 if the end of the
175      * message has been reached.
176      * @param buffer  The character array in which to store the characters.
177      * @return The number of characters read. Returns -1 if the
178      *          end of the message has been reached.
179      * @exception IOException If an error occurs in reading the underlying
180      *            stream.
181      */
182     @Override
183     public int read(char[] buffer) throws IOException
184     {
185         return read(buffer, 0, buffer.length);
186     }
187 
188     /**
189      * Reads the next characters from the message into an array and
190      * returns the number of characters read.  Returns -1 if the end of the
191      * message has been reached.  The characters are stored in the array
192      * starting from the given offset and up to the length specified.
193      * @param buffer  The character array in which to store the characters.
194      * @param offset   The offset into the array at which to start storing
195      *              characters.
196      * @param length   The number of characters to read.
197      * @return The number of characters read. Returns -1 if the
198      *          end of the message has been reached.
199      * @exception IOException If an error occurs in reading the underlying
200      *            stream.
201      */
202     @Override
203     public int read(char[] buffer, int offset, int length) throws IOException
204     {
205         int ch, off;
206         synchronized (lock)
207         {
208             if (length < 1)
209             {
210                 return 0;
211             }
212             if ((ch = read()) == -1)
213             {
214                 return -1;
215             }
216             off = offset;
217 
218             do
219             {
220                 buffer[offset++] = (char) ch;
221             }
222             while (--length > 0 && (ch = read()) != -1);
223 
224             return (offset - off);
225         }
226     }
227 
228     /**
229      * Determines if the message is ready to be read.
230      * @return True if the message is ready to be read, false if not.
231      * @exception IOException If an error occurs while checking the underlying
232      *            stream.
233      */
234     @Override
235     public boolean ready() throws IOException
236     {
237         synchronized (lock)
238         {
239             return (pos < internalBuffer.length || internalReader.ready());
240         }
241     }
242 
243     /**
244      * Closes the message for reading.  This doesn't actually close the
245      * underlying stream.  The underlying stream may still be used for
246      * communicating with the server and therefore is not closed.
247      * <p>
248      * If the end of the message has not yet been reached, this method
249      * will read the remainder of the message until it reaches the end,
250      * so that the underlying stream may continue to be used properly
251      * for communicating with the server.  If you do not fully read
252      * a message, you MUST close it, otherwise your program will likely
253      * hang or behave improperly.
254      * @exception IOException  If an error occurs while reading the
255      *            underlying stream.
256      */
257     @Override
258     public void close() throws IOException
259     {
260         synchronized (lock)
261         {
262             if (internalReader == null)
263             {
264                 return;
265             }
266 
267             if (!eof)
268             {
269                 while (read() != -1)
270                 {
271                     ;
272                 }
273             }
274             eof = true;
275             atBeginning = false;
276             pos = internalBuffer.length;
277             internalReader = null;
278         }
279     }
280 }