View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.io;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  
23  /**
24   * DotTerminatedMessageWriter is a class used to write messages to a server that are terminated by a single dot followed by a <CR><LF> sequence and
25   * with double dots appearing at the beginning of lines which do not signal end of message yet start with a dot. Various Internet protocols such as
26   * NNTP and POP3 produce messages of this type.
27   * <p>
28   * This class handles the doubling of line-starting periods, converts single linefeeds to NETASCII newlines, and on closing will send the final message
29   * terminator dot and NETASCII newline sequence.
30   */
31  public final class DotTerminatedMessageWriter extends Writer {
32      private static final int NOTHING_SPECIAL_STATE = 0;
33      private static final int LAST_WAS_CR_STATE = 1;
34      private static final int LAST_WAS_NL_STATE = 2;
35  
36      private int state;
37      private Writer output;
38  
39      /**
40       * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination.
41       *
42       * @param output The Writer output destination to write the message.
43       */
44      public DotTerminatedMessageWriter(final Writer output) {
45          super(output);
46          this.output = output;
47          this.state = NOTHING_SPECIAL_STATE;
48      }
49  
50      /**
51       * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for
52       * communicating with the server and therefore is not closed.
53       *
54       * @throws IOException If an error occurs while writing to the underlying output or closing the Writer.
55       */
56      @Override
57      public void close() throws IOException {
58          synchronized (lock) {
59              if (output == null) {
60                  return;
61              }
62  
63              if (state == LAST_WAS_CR_STATE) {
64                  output.write('\n');
65              } else if (state != LAST_WAS_NL_STATE) {
66                  output.write("\r\n");
67              }
68  
69              output.write(".\r\n");
70  
71              output.flush();
72              output = null;
73          }
74      }
75  
76      /**
77       * Flushes the underlying output, writing all buffered output.
78       *
79       * @throws IOException If an error occurs while writing to the underlying output.
80       */
81      @Override
82      public void flush() throws IOException {
83          synchronized (lock) {
84              output.flush();
85          }
86      }
87  
88      /**
89       * Writes a character array to the output.
90       *
91       * @param buffer The character array to write.
92       * @throws IOException If an error occurs while writing to the underlying output.
93       */
94      @Override
95      public void write(final char[] buffer) throws IOException {
96          write(buffer, 0, buffer.length);
97      }
98  
99      /**
100      * Writes a number of characters from a character array to the output starting from a given offset.
101      *
102      * @param buffer The character array to write.
103      * @param offset The offset into the array at which to start copying data.
104      * @param length The number of characters to write.
105      * @throws IOException If an error occurs while writing to the underlying output.
106      */
107     @Override
108     public void write(final char[] buffer, int offset, int length) throws IOException {
109         synchronized (lock) {
110             while (length-- > 0) {
111                 write(buffer[offset++]);
112             }
113         }
114     }
115 
116     /**
117      * Writes a character to the output. Note that a call to this method may result in multiple writes to the underling Writer in order to convert naked
118      * linefeeds to NETASCII line separators and to double line-leading periods. This is transparent to the programmer and is only mentioned for completeness.
119      *
120      * @param ch The character to write.
121      * @throws IOException If an error occurs while writing to the underlying output.
122      */
123     @Override
124     public void write(final int ch) throws IOException {
125         synchronized (lock) {
126             switch (ch) {
127             case '\r':
128                 state = LAST_WAS_CR_STATE;
129                 output.write('\r');
130                 return;
131             case '\n':
132                 if (state != LAST_WAS_CR_STATE) {
133                     output.write('\r');
134                 }
135                 output.write('\n');
136                 state = LAST_WAS_NL_STATE;
137                 return;
138             case '.':
139                 // Double the dot at the beginning of a line
140                 if (state == LAST_WAS_NL_STATE) {
141                     output.write('.');
142                 }
143                 //$FALL-THROUGH$
144             default:
145                 state = NOTHING_SPECIAL_STATE;
146                 output.write(ch);
147             }
148         }
149     }
150 
151     /**
152      * Writes a String to the output.
153      *
154      * @param string The String to write.
155      * @throws IOException If an error occurs while writing to the underlying output.
156      */
157     @Override
158     public void write(final String string) throws IOException {
159         write(string.toCharArray());
160     }
161 
162     /**
163      * Writes part of a String to the output starting from a given offset.
164      *
165      * @param string The String to write.
166      * @param offset The offset into the String at which to start copying data.
167      * @param length The number of characters to write.
168      * @throws IOException If an error occurs while writing to the underlying output.
169      */
170     @Override
171     public void write(final String string, final int offset, final int length) throws IOException {
172         write(string.toCharArray(), offset, length);
173     }
174 
175 }