    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
    018package org.apache.commons.net.io;
    019
    020import java.io.IOException;
    021import 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 *
    036 *
    037 ***/
    038
    039public 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     *
    053     * @param output  The Writer output destination to write the message.
    054     ***/
    055    public DotTerminatedMessageWriter(final Writer output)
    056    {
    057        super(output);
    058        this.output = output;
    059        this.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     *
    070     * @param ch  The character to write.
    071     * @throws IOException  If an error occurs while writing to the
    072     *            underlying output.
    073     ***/
    074    @Override
    075    public void write(final 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     *
    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     * @throws IOException If an error occurs while writing to the underlying
    115     *            output.
    116     ***/
    117    @Override
    118    public void write(final 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     *
    132     * @param buffer  The character array to write.
    133     * @throws IOException If an error occurs while writing to the underlying
    134     *            output.
    135     ***/
    136    @Override
    137    public void write(final char[] buffer) throws IOException
    138    {
    139        write(buffer, 0, buffer.length);
    140    }
    141
    142
    143    /***
    144     * Writes a String to the output.
    145     *
    146     * @param string  The String to write.
    147     * @throws IOException If an error occurs while writing to the underlying
    148     *            output.
    149     ***/
    150    @Override
    151    public void write(final 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     *
    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     * @throws IOException If an error occurs while writing to the underlying
    164     *            output.
    165     ***/
    166    @Override
    167    public void write(final String string, final int offset, final 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     *
    176     * @throws 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     *
    194     * @throws 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}