    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.Closeable;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.OutputStream;
    024import java.io.Reader;
    025import java.io.Writer;
    026import java.net.Socket;
    027
    028import org.apache.commons.net.util.NetConstants;
    029
    030/**
    031 * The Util class cannot be instantiated and stores short static convenience methods that are often quite useful.
    032 *
    033 *
    034 * @see CopyStreamException
    035 * @see CopyStreamListener
    036 * @see CopyStreamAdapter
    037 */
    038
    039public final class Util {
    040    /**
    041     * The default buffer size ({@value}) used by {@link #copyStream copyStream } and {@link #copyReader copyReader} and by the copyReader/copyStream methods if
    042     * a zero or negative buffer size is supplied.
    043     */
    044    public static final int DEFAULT_COPY_BUFFER_SIZE = 1024;
    045
    046    /**
    047     * Closes the object quietly, catching rather than throwing IOException. Intended for use from finally blocks.
    048     *
    049     * @param closeable the object to close, may be {@code null}
    050     * @since 3.0
    051     */
    052    public static void closeQuietly(final Closeable closeable) {
    053        if (closeable != null) {
    054            try {
    055                closeable.close();
    056            } catch (final IOException e) {
    057                // Ignored
    058            }
    059        }
    060    }
    061
    062    /**
    063     * Closes the socket quietly, catching rather than throwing IOException. Intended for use from finally blocks.
    064     *
    065     * @param socket the socket to close, may be {@code null}
    066     * @since 3.0
    067     */
    068    public static void closeQuietly(final Socket socket) {
    069        if (socket != null) {
    070            try {
    071                socket.close();
    072            } catch (final IOException e) {
    073                // Ignored
    074            }
    075        }
    076    }
    077
    078    /**
    079     * Same as <code> copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code>
    080     *
    081     * @param source where to copy from
    082     * @param dest   where to copy to
    083     * @return number of bytes copied
    084     * @throws CopyStreamException on error
    085     */
    086    public static long copyReader(final Reader source, final Writer dest) throws CopyStreamException {
    087        return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE);
    088    }
    089
    090    /**
    091     * Copies the contents of a Reader to a Writer using a copy buffer of a given size. The contents of the Reader are read until its end is reached, but
    092     * neither the source nor the destination are closed. You must do this yourself outside the method call. The number of characters read/written is
    093     * returned.
    094     *
    095     * @param source     The source Reader.
    096     * @param dest       The destination writer.
    097     * @param bufferSize The number of characters to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
    098     * @return The number of characters read/written in the copy operation.
    099     * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the
    100     *                             number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException
    101     *                             that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and
    102     *                             getIOException() methods.
    103     */
    104    public static long copyReader(final Reader source, final Writer dest, final int bufferSize) throws CopyStreamException {
    105        return copyReader(source, dest, bufferSize, CopyStreamEvent.UNKNOWN_STREAM_SIZE, null);
    106    }
    107
    108    /**
    109     * Copies the contents of a Reader to a Writer using a copy buffer of a given size and notifies the provided CopyStreamListener of the progress of the copy
    110     * operation by calling its bytesTransferred(long, int) method after each write to the destination. If you wish to notify more than one listener you should
    111     * use a CopyStreamAdapter as the listener and register the additional listeners with the CopyStreamAdapter.
    112     * <p>
    113     * The contents of the Reader are read until its end is reached, but neither the source nor the destination are closed. You must do this yourself outside
    114     * the method call. The number of characters read/written is returned.
    115     *
    116     * @param source     The source Reader.
    117     * @param dest       The destination writer.
    118     * @param bufferSize The number of characters to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
    119     * @param streamSize The number of characters in the stream being copied. Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. Not currently
    120     *                   used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
    121     * @param listener   The CopyStreamListener to notify of progress. If this parameter is null, notification is not attempted.
    122     * @return The number of characters read/written in the copy operation.
    123     * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the
    124     *                             number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException
    125     *                             that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and
    126     *                             getIOException() methods.
    127     */
    128    public static long copyReader(final Reader source, final Writer dest, final int bufferSize, final long streamSize, final CopyStreamListener listener)
    129            throws CopyStreamException {
    130        int numChars;
    131        long total = 0;
    132        final char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];
    133
    134        try {
    135            while ((numChars = source.read(buffer)) != NetConstants.EOS) {
    136                // Technically, some read(char[]) methods may return 0, and we cannot
    137                // accept that as an indication of EOF.
    138                if (numChars == 0) {
    139                    final int singleChar = source.read();
    140                    if (singleChar < 0) {
    141                        break;
    142                    }
    143                    dest.write(singleChar);
    144                    dest.flush();
    145                    ++total;
    146                    if (listener != null) {
    147                        listener.bytesTransferred(total, 1, streamSize);
    148                    }
    149                    continue;
    150                }
    151
    152                dest.write(buffer, 0, numChars);
    153                dest.flush();
    154                total += numChars;
    155                if (listener != null) {
    156                    listener.bytesTransferred(total, numChars, streamSize);
    157                }
    158            }
    159        } catch (final IOException e) {
    160            throw new CopyStreamException("IOException caught while copying.", total, e);
    161        }
    162
    163        return total;
    164    }
    165
    166    /**
    167     * Same as <code> copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code>
    168     *
    169     * @param source where to copy from
    170     * @param dest   where to copy to
    171     * @return number of bytes copied
    172     * @throws CopyStreamException on error
    173     */
    174    public static long copyStream(final InputStream source, final OutputStream dest) throws CopyStreamException {
    175        return copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE);
    176    }
    177
    178    /**
    179     * Copies the contents of an InputStream to an OutputStream using a copy buffer of a given size. The contents of the InputStream are read until the end of
    180     * the stream is reached, but neither the source nor the destination are closed. You must do this yourself outside the method call. The number of bytes
    181     * read/written is returned.
    182     *
    183     * @param source     The source InputStream.
    184     * @param dest       The destination OutputStream.
    185     * @param bufferSize The number of bytes to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
    186     * @return The number of bytes read/written in the copy operation.
    187     * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the
    188     *                             number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException
    189     *                             that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and
    190     *                             getIOException() methods.
    191     */
    192    public static long copyStream(final InputStream source, final OutputStream dest, final int bufferSize) throws CopyStreamException {
    193        return copyStream(source, dest, bufferSize, CopyStreamEvent.UNKNOWN_STREAM_SIZE, null);
    194    }
    195
    196    /**
    197     * Copies the contents of an InputStream to an OutputStream using a copy buffer of a given size and notifies the provided CopyStreamListener of the progress
    198     * of the copy operation by calling its bytesTransferred(long, int) method after each write to the destination. If you wish to notify more than one listener
    199     * you should use a CopyStreamAdapter as the listener and register the additional listeners with the CopyStreamAdapter.
    200     * <p>
    201     * The contents of the InputStream are read until the end of the stream is reached, but neither the source nor the destination are closed. You must do this
    202     * yourself outside the method call. The number of bytes read/written is returned.
    203     *
    204     * @param source     The source InputStream.
    205     * @param dest       The destination OutputStream.
    206     * @param bufferSize The number of bytes to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
    207     * @param streamSize The number of bytes in the stream being copied. Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. Not currently used
    208     *                   (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
    209     * @param listener   The CopyStreamListener to notify of progress. If this parameter is null, notification is not attempted.
    210     * @return number of bytes read/written
    211     * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the
    212     *                             number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException
    213     *                             that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and
    214     *                             getIOException() methods.
    215     */
    216    public static long copyStream(final InputStream source, final OutputStream dest, final int bufferSize, final long streamSize,
    217            final CopyStreamListener listener) throws CopyStreamException {
    218        return copyStream(source, dest, bufferSize, streamSize, listener, true);
    219    }
    220
    221    /**
    222     * Copies the contents of an InputStream to an OutputStream using a copy buffer of a given size and notifies the provided CopyStreamListener of the progress
    223     * of the copy operation by calling its bytesTransferred(long, int) method after each write to the destination. If you wish to notify more than one listener
    224     * you should use a CopyStreamAdapter as the listener and register the additional listeners with the CopyStreamAdapter.
    225     * <p>
    226     * The contents of the InputStream are read until the end of the stream is reached, but neither the source nor the destination are closed. You must do this
    227     * yourself outside the method call. The number of bytes read/written is returned.
    228     *
    229     * @param source     The source InputStream.
    230     * @param dest       The destination OutputStream.
    231     * @param bufferSize The number of bytes to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
    232     * @param streamSize The number of bytes in the stream being copied. Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. Not currently used
    233     *                   (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
    234     * @param listener   The CopyStreamListener to notify of progress. If this parameter is null, notification is not attempted.
    235     * @param flush      Whether to flush the output stream after every write. This is necessary for interactive sessions that rely on buffered streams. If you
    236     *                   don't flush, the data will stay in the stream buffer.
    237     * @return number of bytes read/written
    238     * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the
    239     *                             number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException
    240     *                             that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and
    241     *                             getIOException() methods.
    242     */
    243    public static long copyStream(final InputStream source, final OutputStream dest, final int bufferSize, final long streamSize,
    244            final CopyStreamListener listener, final boolean flush) throws CopyStreamException {
    245        int numBytes;
    246        long total = 0;
    247        final byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];
    248
    249        try {
    250            while ((numBytes = source.read(buffer)) != NetConstants.EOS) {
    251                // Technically, some read(byte[]) methods may return 0, and we cannot
    252                // accept that as an indication of EOF.
    253
    254                if (numBytes == 0) {
    255                    final int singleByte = source.read();
    256                    if (singleByte < 0) {
    257                        break;
    258                    }
    259                    dest.write(singleByte);
    260                    if (flush) {
    261                        dest.flush();
    262                    }
    263                    ++total;
    264                    if (listener != null) {
    265                        listener.bytesTransferred(total, 1, streamSize);
    266                    }
    267                    continue;
    268                }
    269
    270                dest.write(buffer, 0, numBytes);
    271                if (flush) {
    272                    dest.flush();
    273                }
    274                total += numBytes;
    275                if (listener != null) {
    276                    listener.bytesTransferred(total, numBytes, streamSize);
    277                }
    278            }
    279        } catch (final IOException e) {
    280            throw new CopyStreamException("IOException caught while copying.", total, e);
    281        }
    282
    283        return total;
    284    }
    285
    286    /** Cannot be instantiated. */
    287    private Util() {
    288    }
    289}