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