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