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                    __output.write('\n');
089                    __state = __LAST_WAS_NL_STATE;
090                    return ;
091                case '.':
092                    // Double the dot at the beginning of a line
093                    if (__state == __LAST_WAS_NL_STATE)
094                        __output.write('.');
095                    //$FALL-THROUGH$
096                default:
097                    __state = __NOTHING_SPECIAL_STATE;
098                    __output.write(ch);
099                    return ;
100                }
101            }
102        }
103    
104    
105        /***
106         * Writes a number of characters from a character array to the output
107         * starting from a given offset.
108         * <p>
109         * @param buffer  The character array to write.
110         * @param offset  The offset into the array at which to start copying data.
111         * @param length  The number of characters to write.
112         * @exception IOException If an error occurs while writing to the underlying
113         *            output.
114         ***/
115        @Override
116        public void write(char[] buffer, int offset, int length) throws IOException
117        {
118            synchronized (lock)
119            {
120                while (length-- > 0)
121                    write(buffer[offset++]);
122            }
123        }
124    
125    
126        /***
127         * Writes a character array to the output.
128         * <p>
129         * @param buffer  The character array to write.
130         * @exception IOException If an error occurs while writing to the underlying
131         *            output.
132         ***/
133        @Override
134        public void write(char[] buffer) throws IOException
135        {
136            write(buffer, 0, buffer.length);
137        }
138    
139    
140        /***
141         * Writes a String to the output.
142         * <p>
143         * @param string  The String to write.
144         * @exception IOException If an error occurs while writing to the underlying
145         *            output.
146         ***/
147        @Override
148        public void write(String string) throws IOException
149        {
150            write(string.toCharArray());
151        }
152    
153    
154        /***
155         * Writes part of a String to the output starting from a given offset.
156         * <p>
157         * @param string  The String to write.
158         * @param offset  The offset into the String at which to start copying data.
159         * @param length  The number of characters to write.
160         * @exception IOException If an error occurs while writing to the underlying
161         *            output.
162         ***/
163        @Override
164        public void write(String string, int offset, int length) throws IOException
165        {
166            write(string.toCharArray(), offset, length);
167        }
168    
169    
170        /***
171         * Flushes the underlying output, writing all buffered output.
172         * <p>
173         * @exception IOException If an error occurs while writing to the underlying
174         *            output.
175         ***/
176        @Override
177        public void flush() throws IOException
178        {
179            synchronized (lock)
180            {
181                __output.flush();
182            }
183        }
184    
185    
186        /***
187         * Flushes the underlying output, writing all buffered output, but doesn't
188         * actually close the underlying stream.  The underlying stream may still
189         * be used for communicating with the server and therefore is not closed.
190         * <p>
191         * @exception IOException If an error occurs while writing to the underlying
192         *            output or closing the Writer.
193         ***/
194        @Override
195        public void close() throws IOException
196        {
197            synchronized (lock)
198            {
199                if (__output == null)
200                    return ;
201    
202                if (__state == __LAST_WAS_CR_STATE)
203                    __output.write('\n');
204                else if (__state != __LAST_WAS_NL_STATE)
205                    __output.write("\r\n");
206    
207                __output.write(".\r\n");
208    
209                __output.flush();
210                __output = null;
211            }
212        }
213    
214    }