    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     * @author Daniel F. Savarese
    038     ***/
    039
    040    public final class DotTerminatedMessageWriter extends Writer
    041    {
    042        private static final int __NOTHING_SPECIAL_STATE = 0;
    043        private static final int __LAST_WAS_CR_STATE = 1;
    044        private static final int __LAST_WAS_NL_STATE = 2;
    045
    046        private int __state;
    047        private Writer __output;
    048
    049
    050        /***
    051         * Creates a DotTerminatedMessageWriter that wraps an existing Writer
    052         * output destination.
    053         * <p>
    054         * @param output  The Writer output destination to write the message.
    055         ***/
    056        public DotTerminatedMessageWriter(Writer output)
    057        {
    058            super(output);
    059            __output = output;
    060            __state = __NOTHING_SPECIAL_STATE;
    061        }
    062
    063
    064        /***
    065         * Writes a character to the output.  Note that a call to this method
    066         * may result in multiple writes to the underling Writer in order to
    067         * convert naked linefeeds to NETASCII line separators and to double
    068         * line-leading periods.  This is transparent to the programmer and
    069         * is only mentioned for completeness.
    070         * <p>
    071         * @param ch  The character to write.
    072         * @exception IOException  If an error occurs while writing to the
    073         *            underlying output.
    074         ***/
    075        @Override
    076        public void write(int ch) throws IOException
    077        {
    078            synchronized (lock)
    079            {
    080                switch (ch)
    081                {
    082                case '\r':
    083                    __state = __LAST_WAS_CR_STATE;
    084                    __output.write('\r');
    085                    return ;
    086                case '\n':
    087                    if (__state != __LAST_WAS_CR_STATE)
    088                        __output.write('\r');
    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                    //$FALL-THROUGH$
    097                default:
    098                    __state = __NOTHING_SPECIAL_STATE;
    099                    __output.write(ch);
    100                    return ;
    101                }
    102            }
    103        }
    104
    105
    106        /***
    107         * Writes a number of characters from a character array to the output
    108         * starting from a given offset.
    109         * <p>
    110         * @param buffer  The character array to write.
    111         * @param offset  The offset into the array at which to start copying data.
    112         * @param length  The number of characters to write.
    113         * @exception IOException If an error occurs while writing to the underlying
    114         *            output.
    115         ***/
    116        @Override
    117        public void write(char[] buffer, int offset, int length) throws IOException
    118        {
    119            synchronized (lock)
    120            {
    121                while (length-- > 0)
    122                    write(buffer[offset++]);
    123            }
    124        }
    125
    126
    127        /***
    128         * Writes a character array to the output.
    129         * <p>
    130         * @param buffer  The character array to write.
    131         * @exception IOException If an error occurs while writing to the underlying
    132         *            output.
    133         ***/
    134        @Override
    135        public void write(char[] buffer) throws IOException
    136        {
    137            write(buffer, 0, buffer.length);
    138        }
    139
    140
    141        /***
    142         * Writes a String to the output.
    143         * <p>
    144         * @param string  The String to write.
    145         * @exception IOException If an error occurs while writing to the underlying
    146         *            output.
    147         ***/
    148        @Override
    149        public void write(String string) throws IOException
    150        {
    151            write(string.toCharArray());
    152        }
    153
    154
    155        /***
    156         * Writes part of a String to the output starting from a given offset.
    157         * <p>
    158         * @param string  The String to write.
    159         * @param offset  The offset into the String at which to start copying data.
    160         * @param length  The number of characters to write.
    161         * @exception IOException If an error occurs while writing to the underlying
    162         *            output.
    163         ***/
    164        @Override
    165        public void write(String string, int offset, int length) throws IOException
    166        {
    167            write(string.toCharArray(), offset, length);
    168        }
    169
    170
    171        /***
    172         * Flushes the underlying output, writing all buffered output.
    173         * <p>
    174         * @exception IOException If an error occurs while writing to the underlying
    175         *            output.
    176         ***/
    177        @Override
    178        public void flush() throws IOException
    179        {
    180            synchronized (lock)
    181            {
    182                __output.flush();
    183            }
    184        }
    185
    186
    187        /***
    188         * Flushes the underlying output, writing all buffered output, but doesn't
    189         * actually close the underlying stream.  The underlying stream may still
    190         * be used for communicating with the server and therefore is not closed.
    191         * <p>
    192         * @exception IOException If an error occurs while writing to the underlying
    193         *            output or closing the Writer.
    194         ***/
    195        @Override
    196        public void close() throws IOException
    197        {
    198            synchronized (lock)
    199            {
    200                if (__output == null)
    201                    return ;
    202
    203                if (__state == __LAST_WAS_CR_STATE)
    204                    __output.write('\n');
    205                else if (__state != __LAST_WAS_NL_STATE)
    206                    __output.write("\r\n");
    207
    208                __output.write(".\r\n");
    209
    210                __output.flush();
    211                __output = null;
    212            }
    213        }
    214
    215    }