    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                    __output.write('\n');
    089                    __state = __LAST_WAS_NL_STATE;
    090                    return ;
    091                case '.':
    092                    // Double the dot at the beginning of a line
    093                    if (__state == __LAST_WAS_NL_STATE)
    094                        __output.write('.');
    095                    //$FALL-THROUGH$
    096                default:
    097                    __state = __NOTHING_SPECIAL_STATE;
    098                    __output.write(ch);
    099                    return ;
    100                }
    101            }
    102        }
    103
    104
    105        /***
    106         * Writes a number of characters from a character array to the output
    107         * starting from a given offset.
    108         * <p>
    109         * @param buffer  The character array to write.
    110         * @param offset  The offset into the array at which to start copying data.
    111         * @param length  The number of characters to write.
    112         * @exception IOException If an error occurs while writing to the underlying
    113         *            output.
    114         ***/
    115        @Override
    116        public void write(char[] buffer, int offset, int length) throws IOException
    117        {
    118            synchronized (lock)
    119            {
    120                while (length-- > 0)
    121                    write(buffer[offset++]);
    122            }
    123        }
    124
    125
    126        /***
    127         * Writes a character array to the output.
    128         * <p>
    129         * @param buffer  The character array to write.
    130         * @exception IOException If an error occurs while writing to the underlying
    131         *            output.
    132         ***/
    133        @Override
    134        public void write(char[] buffer) throws IOException
    135        {
    136            write(buffer, 0, buffer.length);
    137        }
    138
    139
    140        /***
    141         * Writes a String to the output.
    142         * <p>
    143         * @param string  The String to write.
    144         * @exception IOException If an error occurs while writing to the underlying
    145         *            output.
    146         ***/
    147        @Override
    148        public void write(String string) throws IOException
    149        {
    150            write(string.toCharArray());
    151        }
    152
    153
    154        /***
    155         * Writes part of a String to the output starting from a given offset.
    156         * <p>
    157         * @param string  The String to write.
    158         * @param offset  The offset into the String at which to start copying data.
    159         * @param length  The number of characters to write.
    160         * @exception IOException If an error occurs while writing to the underlying
    161         *            output.
    162         ***/
    163        @Override
    164        public void write(String string, int offset, int length) throws IOException
    165        {
    166            write(string.toCharArray(), offset, length);
    167        }
    168
    169
    170        /***
    171         * Flushes the underlying output, writing all buffered output.
    172         * <p>
    173         * @exception IOException If an error occurs while writing to the underlying
    174         *            output.
    175         ***/
    176        @Override
    177        public void flush() throws IOException
    178        {
    179            synchronized (lock)
    180            {
    181                __output.flush();
    182            }
    183        }
    184
    185
    186        /***
    187         * Flushes the underlying output, writing all buffered output, but doesn't
    188         * actually close the underlying stream.  The underlying stream may still
    189         * be used for communicating with the server and therefore is not closed.
    190         * <p>
    191         * @exception IOException If an error occurs while writing to the underlying
    192         *            output or closing the Writer.
    193         ***/
    194        @Override
    195        public void close() throws IOException
    196        {
    197            synchronized (lock)
    198            {
    199                if (__output == null)
    200                    return ;
    201
    202                if (__state == __LAST_WAS_CR_STATE)
    203                    __output.write('\n');
    204                else if (__state != __LAST_WAS_NL_STATE)
    205                    __output.write("\r\n");
    206
    207                __output.write(".\r\n");
    208
    209                __output.flush();
    210                __output = null;
    211            }
    212        }
    213
    214    }