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 begining of lines which do not signal end of message yet start with a dot. Various Internet protocols such as NNTP and POP3 026 * 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 * 031 * 032 */ 033 034public final class DotTerminatedMessageWriter extends Writer { 035 private static final int NOTHING_SPECIAL_STATE = 0; 036 private static final int LAST_WAS_CR_STATE = 1; 037 private static final int LAST_WAS_NL_STATE = 2; 038 039 private int state; 040 private Writer output; 041 042 /** 043 * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination. 044 * 045 * @param output The Writer output destination to write the message. 046 */ 047 public DotTerminatedMessageWriter(final Writer output) { 048 super(output); 049 this.output = output; 050 this.state = NOTHING_SPECIAL_STATE; 051 } 052 053 /** 054 * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for 055 * communicating with the server and therefore is not closed. 056 * 057 * @throws IOException If an error occurs while writing to the underlying output or closing the Writer. 058 */ 059 @Override 060 public void close() throws IOException { 061 synchronized (lock) { 062 if (output == null) { 063 return; 064 } 065 066 if (state == LAST_WAS_CR_STATE) { 067 output.write('\n'); 068 } else if (state != LAST_WAS_NL_STATE) { 069 output.write("\r\n"); 070 } 071 072 output.write(".\r\n"); 073 074 output.flush(); 075 output = null; 076 } 077 } 078 079 /** 080 * Flushes the underlying output, writing all buffered output. 081 * 082 * @throws IOException If an error occurs while writing to the underlying output. 083 */ 084 @Override 085 public void flush() throws IOException { 086 synchronized (lock) { 087 output.flush(); 088 } 089 } 090 091 /** 092 * Writes a character array to the output. 093 * 094 * @param buffer The character array to write. 095 * @throws IOException If an error occurs while writing to the underlying output. 096 */ 097 @Override 098 public void write(final char[] buffer) throws IOException { 099 write(buffer, 0, buffer.length); 100 } 101 102 /** 103 * Writes a number of characters from a character array to the output starting from a given offset. 104 * 105 * @param buffer The character array to write. 106 * @param offset The offset into the array at which to start copying data. 107 * @param length The number of characters to write. 108 * @throws IOException If an error occurs while writing to the underlying output. 109 */ 110 @Override 111 public void write(final char[] buffer, int offset, int length) throws IOException { 112 synchronized (lock) { 113 while (length-- > 0) { 114 write(buffer[offset++]); 115 } 116 } 117 } 118 119 /** 120 * 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 121 * linefeeds to NETASCII line separators and to double line-leading periods. This is transparent to the programmer and is only mentioned for completeness. 122 * 123 * @param ch The character to write. 124 * @throws IOException If an error occurs while writing to the underlying output. 125 */ 126 @Override 127 public void write(final int ch) throws IOException { 128 synchronized (lock) { 129 switch (ch) { 130 case '\r': 131 state = LAST_WAS_CR_STATE; 132 output.write('\r'); 133 return; 134 case '\n': 135 if (state != LAST_WAS_CR_STATE) { 136 output.write('\r'); 137 } 138 output.write('\n'); 139 state = LAST_WAS_NL_STATE; 140 return; 141 case '.': 142 // Double the dot at the beginning of a line 143 if (state == LAST_WAS_NL_STATE) { 144 output.write('.'); 145 } 146 //$FALL-THROUGH$ 147 default: 148 state = NOTHING_SPECIAL_STATE; 149 output.write(ch); 150 } 151 } 152 } 153 154 /** 155 * Writes a String to the output. 156 * 157 * @param string The String to write. 158 * @throws IOException If an error occurs while writing to the underlying output. 159 */ 160 @Override 161 public void write(final String string) throws IOException { 162 write(string.toCharArray()); 163 } 164 165 /** 166 * Writes part of a String to the output starting from a given offset. 167 * 168 * @param string The String to write. 169 * @param offset The offset into the String at which to start copying data. 170 * @param length The number of characters to write. 171 * @throws IOException If an error occurs while writing to the underlying output. 172 */ 173 @Override 174 public void write(final String string, final int offset, final int length) throws IOException { 175 write(string.toCharArray(), offset, length); 176 } 177 178}