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 }