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 */
017package org.apache.commons.io.output;
018
019import java.io.FilterOutputStream;
020import java.io.IOException;
021import java.io.OutputStream;
022
023import org.apache.commons.io.IOUtils;
024import org.apache.commons.io.build.AbstractStreamBuilder;
025
026/**
027 * OutputStream which breaks larger output blocks into chunks. Native code may need to copy the input array; if the write buffer is very large this can cause
028 * OOME.
029 * <p>
030 * To build an instance, see {@link Builder}
031 * </p>
032 *
033 * @see Builder
034 * @since 2.5
035 */
036public class ChunkedOutputStream extends FilterOutputStream {
037
038    // @formatter:off
039    /**
040     * Builds a new {@link UnsynchronizedByteArrayOutputStream}.
041     *
042     * <p>
043     * Using File IO:
044     * </p>
045     * <pre>{@code
046     * UnsynchronizedByteArrayOutputStream s = UnsynchronizedByteArrayOutputStream.builder()
047     *   .setBufferSize(8192)
048     *   .get();
049     * }
050     * </pre>
051     * <p>
052     * Using NIO Path:
053     * </p>
054     * <pre>{@code
055     * UnsynchronizedByteArrayOutputStream s = UnsynchronizedByteArrayOutputStream.builder()
056     *   .setBufferSize(8192)
057     *   .get();
058     * }
059     * </pre>
060     *
061     * @see #get()
062     * @since 2.13.0
063     */
064    // @formatter:on
065    public static class Builder extends AbstractStreamBuilder<ChunkedOutputStream, Builder> {
066
067        /**
068         * Builds a new {@link UnsynchronizedByteArrayOutputStream}.
069         * <p>
070         * This builder use the following aspects:
071         * </p>
072         * <ul>
073         * <li>{@link #getInputStream()}</li>
074         * <li>{@link #getBufferSize()} (chunk size)</li>
075         * </ul>
076         *
077         * @return a new instance.
078         * @throws IllegalStateException         if the {@code origin} is {@code null}.
079         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
080         * @throws IOException                   if an I/O error occurs.
081         * @see #getOutputStream()
082         * @see #getBufferSize()
083         */
084        @Override
085        public ChunkedOutputStream get() throws IOException {
086            return new ChunkedOutputStream(getOutputStream(), getBufferSize());
087        }
088
089    }
090
091    /**
092     * Constructs a new {@link Builder}.
093     *
094     * @return a new {@link Builder}.
095     * @since 2.13.0
096     */
097    public static Builder builder() {
098        return new Builder();
099    }
100
101    /**
102     * The maximum chunk size to us when writing data arrays
103     */
104    private final int chunkSize;
105
106    /**
107     * Constructs a new stream that uses a chunk size of {@link IOUtils#DEFAULT_BUFFER_SIZE}.
108     *
109     * @param stream the stream to wrap
110     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
111     */
112    @Deprecated
113    public ChunkedOutputStream(final OutputStream stream) {
114        this(stream, IOUtils.DEFAULT_BUFFER_SIZE);
115    }
116
117    /**
118     * Constructs a new stream that uses the specified chunk size.
119     *
120     * @param stream    the stream to wrap
121     * @param chunkSize the chunk size to use; must be a positive number.
122     * @throws IllegalArgumentException if the chunk size is &lt;= 0
123     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
124     */
125    @Deprecated
126    public ChunkedOutputStream(final OutputStream stream, final int chunkSize) {
127        super(stream);
128        if (chunkSize <= 0) {
129            throw new IllegalArgumentException("chunkSize <= 0");
130        }
131        this.chunkSize = chunkSize;
132    }
133
134    int getChunkSize() {
135        return chunkSize;
136    }
137
138    /**
139     * Writes the data buffer in chunks to the underlying stream
140     *
141     * @param data      the data to write
142     * @param srcOffset the offset
143     * @param length    the length of data to write
144     *
145     * @throws IOException if an I/O error occurs.
146     */
147    @Override
148    public void write(final byte[] data, final int srcOffset, final int length) throws IOException {
149        int bytes = length;
150        int dstOffset = srcOffset;
151        while (bytes > 0) {
152            final int chunk = Math.min(bytes, chunkSize);
153            out.write(data, dstOffset, chunk);
154            bytes -= chunk;
155            dstOffset += chunk;
156        }
157    }
158
159}