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   * &lt;CR&gt;&lt;LF&gt;
    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 }