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     * <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    
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    }