DotTerminatedMessageWriter.java

  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. package org.apache.commons.net.io;

  18. import java.io.IOException;
  19. import java.io.Writer;

  20. /**
  21.  * 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
  22.  * 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
  23.  * NNTP and POP3 produce messages of this type.
  24.  * <p>
  25.  * This class handles the doubling of line-starting periods, converts single linefeeds to NETASCII newlines, and on closing will send the final message
  26.  * terminator dot and NETASCII newline sequence.
  27.  */
  28. public final class DotTerminatedMessageWriter extends Writer {
  29.     private static final int NOTHING_SPECIAL_STATE = 0;
  30.     private static final int LAST_WAS_CR_STATE = 1;
  31.     private static final int LAST_WAS_NL_STATE = 2;

  32.     private int state;
  33.     private Writer output;

  34.     /**
  35.      * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination.
  36.      *
  37.      * @param output The Writer output destination to write the message.
  38.      */
  39.     public DotTerminatedMessageWriter(final Writer output) {
  40.         super(output);
  41.         this.output = output;
  42.         this.state = NOTHING_SPECIAL_STATE;
  43.     }

  44.     /**
  45.      * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for
  46.      * communicating with the server and therefore is not closed.
  47.      *
  48.      * @throws IOException If an error occurs while writing to the underlying output or closing the Writer.
  49.      */
  50.     @Override
  51.     public void close() throws IOException {
  52.         synchronized (lock) {
  53.             if (output == null) {
  54.                 return;
  55.             }

  56.             if (state == LAST_WAS_CR_STATE) {
  57.                 output.write('\n');
  58.             } else if (state != LAST_WAS_NL_STATE) {
  59.                 output.write("\r\n");
  60.             }

  61.             output.write(".\r\n");

  62.             output.flush();
  63.             output = null;
  64.         }
  65.     }

  66.     /**
  67.      * Flushes the underlying output, writing all buffered output.
  68.      *
  69.      * @throws IOException If an error occurs while writing to the underlying output.
  70.      */
  71.     @Override
  72.     public void flush() throws IOException {
  73.         synchronized (lock) {
  74.             output.flush();
  75.         }
  76.     }

  77.     /**
  78.      * Writes a character array to the output.
  79.      *
  80.      * @param buffer The character array to write.
  81.      * @throws IOException If an error occurs while writing to the underlying output.
  82.      */
  83.     @Override
  84.     public void write(final char[] buffer) throws IOException {
  85.         write(buffer, 0, buffer.length);
  86.     }

  87.     /**
  88.      * Writes a number of characters from a character array to the output starting from a given offset.
  89.      *
  90.      * @param buffer The character array to write.
  91.      * @param offset The offset into the array at which to start copying data.
  92.      * @param length The number of characters to write.
  93.      * @throws IOException If an error occurs while writing to the underlying output.
  94.      */
  95.     @Override
  96.     public void write(final char[] buffer, int offset, int length) throws IOException {
  97.         synchronized (lock) {
  98.             while (length-- > 0) {
  99.                 write(buffer[offset++]);
  100.             }
  101.         }
  102.     }

  103.     /**
  104.      * 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
  105.      * linefeeds to NETASCII line separators and to double line-leading periods. This is transparent to the programmer and is only mentioned for completeness.
  106.      *
  107.      * @param ch The character to write.
  108.      * @throws IOException If an error occurs while writing to the underlying output.
  109.      */
  110.     @Override
  111.     public void write(final int ch) throws IOException {
  112.         synchronized (lock) {
  113.             switch (ch) {
  114.             case '\r':
  115.                 state = LAST_WAS_CR_STATE;
  116.                 output.write('\r');
  117.                 return;
  118.             case '\n':
  119.                 if (state != LAST_WAS_CR_STATE) {
  120.                     output.write('\r');
  121.                 }
  122.                 output.write('\n');
  123.                 state = LAST_WAS_NL_STATE;
  124.                 return;
  125.             case '.':
  126.                 // Double the dot at the beginning of a line
  127.                 if (state == LAST_WAS_NL_STATE) {
  128.                     output.write('.');
  129.                 }
  130.                 //$FALL-THROUGH$
  131.             default:
  132.                 state = NOTHING_SPECIAL_STATE;
  133.                 output.write(ch);
  134.             }
  135.         }
  136.     }

  137.     /**
  138.      * Writes a String to the output.
  139.      *
  140.      * @param string The String to write.
  141.      * @throws IOException If an error occurs while writing to the underlying output.
  142.      */
  143.     @Override
  144.     public void write(final String string) throws IOException {
  145.         write(string.toCharArray());
  146.     }

  147.     /**
  148.      * Writes part of a String to the output starting from a given offset.
  149.      *
  150.      * @param string The String to write.
  151.      * @param offset The offset into the String at which to start copying data.
  152.      * @param length The number of characters to write.
  153.      * @throws IOException If an error occurs while writing to the underlying output.
  154.      */
  155.     @Override
  156.     public void write(final String string, final int offset, final int length) throws IOException {
  157.         write(string.toCharArray(), offset, length);
  158.     }

  159. }