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}