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 018package org.apache.commons.net.io; 019 020import java.io.IOException; 021import 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 * 036 * 037 */ 038 039public 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 * 053 * @param output The Writer output destination to write the message. 054 */ 055 public DotTerminatedMessageWriter(final Writer output) 056 { 057 super(output); 058 this.output = output; 059 this.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 * 070 * @param ch The character to write. 071 * @throws IOException If an error occurs while writing to the 072 * underlying output. 073 */ 074 @Override 075 public void write(final 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 } 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 * 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 * @throws IOException If an error occurs while writing to the underlying 114 * output. 115 */ 116 @Override 117 public void write(final 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 /** 129 * Writes a character array to the output. 130 * 131 * @param buffer The character array to write. 132 * @throws IOException If an error occurs while writing to the underlying 133 * output. 134 */ 135 @Override 136 public void write(final char[] buffer) throws IOException 137 { 138 write(buffer, 0, buffer.length); 139 } 140 141 142 /** 143 * Writes a String to the output. 144 * 145 * @param string The String to write. 146 * @throws IOException If an error occurs while writing to the underlying 147 * output. 148 */ 149 @Override 150 public void write(final String string) throws IOException 151 { 152 write(string.toCharArray()); 153 } 154 155 156 /** 157 * Writes part of a String to the output starting from a given offset. 158 * 159 * @param string The String to write. 160 * @param offset The offset into the String at which to start copying data. 161 * @param length The number of characters to write. 162 * @throws IOException If an error occurs while writing to the underlying 163 * output. 164 */ 165 @Override 166 public void write(final String string, final int offset, final int length) throws IOException 167 { 168 write(string.toCharArray(), offset, length); 169 } 170 171 172 /** 173 * Flushes the underlying output, writing all buffered output. 174 * 175 * @throws IOException If an error occurs while writing to the underlying 176 * output. 177 */ 178 @Override 179 public void flush() throws IOException 180 { 181 synchronized (lock) 182 { 183 output.flush(); 184 } 185 } 186 187 188 /** 189 * Flushes the underlying output, writing all buffered output, but doesn't 190 * actually close the underlying stream. The underlying stream may still 191 * be used for communicating with the server and therefore is not closed. 192 * 193 * @throws IOException If an error occurs while writing to the underlying 194 * output or closing the Writer. 195 */ 196 @Override 197 public void close() throws IOException 198 { 199 synchronized (lock) 200 { 201 if (output == null) { 202 return ; 203 } 204 205 if (state == LAST_WAS_CR_STATE) { 206 output.write('\n'); 207 } else if (state != LAST_WAS_NL_STATE) { 208 output.write("\r\n"); 209 } 210 211 output.write(".\r\n"); 212 213 output.flush(); 214 output = null; 215 } 216 } 217 218}