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