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 server that are terminated by a single dot followed by a <CR><LF> sequence and 025 * 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 026 * NNTP and POP3 produce messages of this type. 027 * <p> 028 * This class handles the doubling of line-starting periods, converts single linefeeds to NETASCII newlines, and on closing will send the final message 029 * terminator dot and NETASCII newline sequence. 030 */ 031public final class DotTerminatedMessageWriter extends Writer { 032 private static final int NOTHING_SPECIAL_STATE = 0; 033 private static final int LAST_WAS_CR_STATE = 1; 034 private static final int LAST_WAS_NL_STATE = 2; 035 036 private int state; 037 private Writer output; 038 039 /** 040 * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination. 041 * 042 * @param output The Writer output destination to write the message. 043 */ 044 public DotTerminatedMessageWriter(final Writer output) { 045 super(output); 046 this.output = output; 047 this.state = NOTHING_SPECIAL_STATE; 048 } 049 050 /** 051 * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for 052 * communicating with the server and therefore is not closed. 053 * 054 * @throws IOException If an error occurs while writing to the underlying output or closing the Writer. 055 */ 056 @Override 057 public void close() throws IOException { 058 synchronized (lock) { 059 if (output == null) { 060 return; 061 } 062 063 if (state == LAST_WAS_CR_STATE) { 064 output.write('\n'); 065 } else if (state != LAST_WAS_NL_STATE) { 066 output.write("\r\n"); 067 } 068 069 output.write(".\r\n"); 070 071 output.flush(); 072 output = null; 073 } 074 } 075 076 /** 077 * Flushes the underlying output, writing all buffered output. 078 * 079 * @throws IOException If an error occurs while writing to the underlying output. 080 */ 081 @Override 082 public void flush() throws IOException { 083 synchronized (lock) { 084 output.flush(); 085 } 086 } 087 088 /** 089 * Writes a character array to the output. 090 * 091 * @param buffer The character array to write. 092 * @throws IOException If an error occurs while writing to the underlying output. 093 */ 094 @Override 095 public void write(final char[] buffer) throws IOException { 096 write(buffer, 0, buffer.length); 097 } 098 099 /** 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}