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 * <CR><LF>
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(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     *
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(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(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(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(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(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     *
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}