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 * <p>
036 * <p>
037 * @author Daniel F. Savarese
038 ***/
039
040public 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}