    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.net.io;
    019
    020    import java.io.IOException;
    021    import java.io.Writer;
    022
    023    /***
    024     * DotTerminatedMessageWriter is a class used to write messages to a
    025     * server that are terminated by a single dot followed by a
    026     * &lt;CR&gt;&lt;LF&gt;
    027     * sequence and with double dots appearing at the begining of lines which
    028     * do not signal end of message yet start with a dot.  Various Internet
    029     * protocols such as NNTP and POP3 produce messages of this type.
    030     * <p>
    031     * This class handles the doubling of line-starting periods,
    032     * converts single linefeeds to NETASCII newlines, and on closing
    033     * will send the final message terminator dot and NETASCII newline
    034     * sequence.
    035     * <p>
    036     * <p>
    037     ***/
    038
    039    public final class DotTerminatedMessageWriter extends Writer
    040    {
    041        private static final int __NOTHING_SPECIAL_STATE = 0;
    042        private static final int __LAST_WAS_CR_STATE = 1;
    043        private static final int __LAST_WAS_NL_STATE = 2;
    044
    045        private int __state;
    046        private Writer __output;
    047
    048
    049        /***
    050         * Creates a DotTerminatedMessageWriter that wraps an existing Writer
    051         * output destination.
    052         * <p>
    053         * @param output  The Writer output destination to write the message.
    054         ***/
    055        public DotTerminatedMessageWriter(Writer output)
    056        {
    057            super(output);
    058            __output = output;
    059            __state = __NOTHING_SPECIAL_STATE;
    060        }
    061
    062
    063        /***
    064         * Writes a character to the output.  Note that a call to this method
    065         * may result in multiple writes to the underling Writer in order to
    066         * convert naked linefeeds to NETASCII line separators and to double
    067         * line-leading periods.  This is transparent to the programmer and
    068         * is only mentioned for completeness.
    069         * <p>
    070         * @param ch  The character to write.
    071         * @exception IOException  If an error occurs while writing to the
    072         *            underlying output.
    073         ***/
    074        @Override
    075        public void write(int ch) throws IOException
    076        {
    077            synchronized (lock)
    078            {
    079                switch (ch)
    080                {
    081                case '\r':
    082                    __state = __LAST_WAS_CR_STATE;
    083                    __output.write('\r');
    084                    return ;
    085                case '\n':
    086                    if (__state != __LAST_WAS_CR_STATE) {
    087                        __output.write('\r');
    088                    }
    089                    __output.write('\n');
    090                    __state = __LAST_WAS_NL_STATE;
    091                    return ;
    092                case '.':
    093                    // Double the dot at the beginning of a line
    094                    if (__state == __LAST_WAS_NL_STATE) {
    095                        __output.write('.');
    096                    }
    097                    //$FALL-THROUGH$
    098                default:
    099                    __state = __NOTHING_SPECIAL_STATE;
    100                    __output.write(ch);
    101                    return ;
    102                }
    103            }
    104        }
    105
    106
    107        /***
    108         * Writes a number of characters from a character array to the output
    109         * starting from a given offset.
    110         * <p>
    111         * @param buffer  The character array to write.
    112         * @param offset  The offset into the array at which to start copying data.
    113         * @param length  The number of characters to write.
    114         * @exception IOException If an error occurs while writing to the underlying
    115         *            output.
    116         ***/
    117        @Override
    118        public void write(char[] buffer, int offset, int length) throws IOException
    119        {
    120            synchronized (lock)
    121            {
    122                while (length-- > 0) {
    123                    write(buffer[offset++]);
    124                }
    125            }
    126        }
    127
    128
    129        /***
    130         * Writes a character array to the output.
    131         * <p>
    132         * @param buffer  The character array to write.
    133         * @exception IOException If an error occurs while writing to the underlying
    134         *            output.
    135         ***/
    136        @Override
    137        public void write(char[] buffer) throws IOException
    138        {
    139            write(buffer, 0, buffer.length);
    140        }
    141
    142
    143        /***
    144         * Writes a String to the output.
    145         * <p>
    146         * @param string  The String to write.
    147         * @exception IOException If an error occurs while writing to the underlying
    148         *            output.
    149         ***/
    150        @Override
    151        public void write(String string) throws IOException
    152        {
    153            write(string.toCharArray());
    154        }
    155
    156
    157        /***
    158         * Writes part of a String to the output starting from a given offset.
    159         * <p>
    160         * @param string  The String to write.
    161         * @param offset  The offset into the String at which to start copying data.
    162         * @param length  The number of characters to write.
    163         * @exception IOException If an error occurs while writing to the underlying
    164         *            output.
    165         ***/
    166        @Override
    167        public void write(String string, int offset, int length) throws IOException
    168        {
    169            write(string.toCharArray(), offset, length);
    170        }
    171
    172
    173        /***
    174         * Flushes the underlying output, writing all buffered output.
    175         * <p>
    176         * @exception IOException If an error occurs while writing to the underlying
    177         *            output.
    178         ***/
    179        @Override
    180        public void flush() throws IOException
    181        {
    182            synchronized (lock)
    183            {
    184                __output.flush();
    185            }
    186        }
    187
    188
    189        /***
    190         * Flushes the underlying output, writing all buffered output, but doesn't
    191         * actually close the underlying stream.  The underlying stream may still
    192         * be used for communicating with the server and therefore is not closed.
    193         * <p>
    194         * @exception IOException If an error occurs while writing to the underlying
    195         *            output or closing the Writer.
    196         ***/
    197        @Override
    198        public void close() throws IOException
    199        {
    200            synchronized (lock)
    201            {
    202                if (__output == null) {
    203                    return ;
    204                }
    205
    206                if (__state == __LAST_WAS_CR_STATE) {
    207                    __output.write('\n');
    208                } else if (__state != __LAST_WAS_NL_STATE) {
    209                    __output.write("\r\n");
    210                }
    211
    212                __output.write(".\r\n");
    213
    214                __output.flush();
    215                __output = null;
    216            }
    217        }
    218
    219    }