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     ***/
038    
039    public 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         * <p>
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         * <p>
070         * @param ch  The character to write.
071         * @exception 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         * <p>
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         * @exception 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         * <p>
132         * @param buffer  The character array to write.
133         * @exception 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         * <p>
146         * @param string  The String to write.
147         * @exception 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         * <p>
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         * @exception 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         * <p>
176         * @exception 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         * <p>
194         * @exception 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    }