001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.FilterInputStream; 022import java.io.FilterOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.InputStreamReader; 026import java.io.OutputStream; 027import java.io.OutputStreamWriter; 028import java.io.UnsupportedEncodingException; 029import java.io.Writer; 030import java.nio.charset.Charset; 031 032import org.springframework.lang.Nullable; 033 034/** 035 * Simple utility methods for dealing with streams. The copy methods of this class are 036 * similar to those defined in {@link FileCopyUtils} except that all affected streams are 037 * left open when done. All copy methods use a block size of 4096 bytes. 038 * 039 * <p>Mainly for use within the framework, but also useful for application code. 040 * 041 * @author Juergen Hoeller 042 * @author Phillip Webb 043 * @author Brian Clozel 044 * @since 3.2.2 045 * @see FileCopyUtils 046 */ 047public abstract class StreamUtils { 048 049 /** 050 * The default buffer size used when copying bytes. 051 */ 052 public static final int BUFFER_SIZE = 4096; 053 054 private static final byte[] EMPTY_CONTENT = new byte[0]; 055 056 057 /** 058 * Copy the contents of the given InputStream into a new byte array. 059 * <p>Leaves the stream open when done. 060 * @param in the stream to copy from (may be {@code null} or empty) 061 * @return the new byte array that has been copied to (possibly empty) 062 * @throws IOException in case of I/O errors 063 */ 064 public static byte[] copyToByteArray(@Nullable InputStream in) throws IOException { 065 if (in == null) { 066 return new byte[0]; 067 } 068 069 ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); 070 copy(in, out); 071 return out.toByteArray(); 072 } 073 074 /** 075 * Copy the contents of the given InputStream into a String. 076 * <p>Leaves the stream open when done. 077 * @param in the InputStream to copy from (may be {@code null} or empty) 078 * @param charset the {@link Charset} to use to decode the bytes 079 * @return the String that has been copied to (possibly empty) 080 * @throws IOException in case of I/O errors 081 */ 082 public static String copyToString(@Nullable InputStream in, Charset charset) throws IOException { 083 if (in == null) { 084 return ""; 085 } 086 087 StringBuilder out = new StringBuilder(BUFFER_SIZE); 088 InputStreamReader reader = new InputStreamReader(in, charset); 089 char[] buffer = new char[BUFFER_SIZE]; 090 int charsRead; 091 while ((charsRead = reader.read(buffer)) != -1) { 092 out.append(buffer, 0, charsRead); 093 } 094 return out.toString(); 095 } 096 097 /** 098 * Copy the contents of the given {@link ByteArrayOutputStream} into a {@link String}. 099 * <p>This is a more effective equivalent of {@code new String(baos.toByteArray(), charset)}. 100 * @param baos the {@code ByteArrayOutputStream} to be copied into a String 101 * @param charset the {@link Charset} to use to decode the bytes 102 * @return the String that has been copied to (possibly empty) 103 * @since 5.2.6 104 */ 105 public static String copyToString(ByteArrayOutputStream baos, Charset charset) { 106 Assert.notNull(baos, "No ByteArrayOutputStream specified"); 107 Assert.notNull(charset, "No Charset specified"); 108 try { 109 // Can be replaced with toString(Charset) call in Java 10+ 110 return baos.toString(charset.name()); 111 } 112 catch (UnsupportedEncodingException ex) { 113 // Should never happen 114 throw new IllegalArgumentException("Invalid charset name: " + charset, ex); 115 } 116 } 117 118 /** 119 * Copy the contents of the given byte array to the given OutputStream. 120 * <p>Leaves the stream open when done. 121 * @param in the byte array to copy from 122 * @param out the OutputStream to copy to 123 * @throws IOException in case of I/O errors 124 */ 125 public static void copy(byte[] in, OutputStream out) throws IOException { 126 Assert.notNull(in, "No input byte array specified"); 127 Assert.notNull(out, "No OutputStream specified"); 128 129 out.write(in); 130 out.flush(); 131 } 132 133 /** 134 * Copy the contents of the given String to the given OutputStream. 135 * <p>Leaves the stream open when done. 136 * @param in the String to copy from 137 * @param charset the Charset 138 * @param out the OutputStream to copy to 139 * @throws IOException in case of I/O errors 140 */ 141 public static void copy(String in, Charset charset, OutputStream out) throws IOException { 142 Assert.notNull(in, "No input String specified"); 143 Assert.notNull(charset, "No Charset specified"); 144 Assert.notNull(out, "No OutputStream specified"); 145 146 Writer writer = new OutputStreamWriter(out, charset); 147 writer.write(in); 148 writer.flush(); 149 } 150 151 /** 152 * Copy the contents of the given InputStream to the given OutputStream. 153 * <p>Leaves both streams open when done. 154 * @param in the InputStream to copy from 155 * @param out the OutputStream to copy to 156 * @return the number of bytes copied 157 * @throws IOException in case of I/O errors 158 */ 159 public static int copy(InputStream in, OutputStream out) throws IOException { 160 Assert.notNull(in, "No InputStream specified"); 161 Assert.notNull(out, "No OutputStream specified"); 162 163 int byteCount = 0; 164 byte[] buffer = new byte[BUFFER_SIZE]; 165 int bytesRead; 166 while ((bytesRead = in.read(buffer)) != -1) { 167 out.write(buffer, 0, bytesRead); 168 byteCount += bytesRead; 169 } 170 out.flush(); 171 return byteCount; 172 } 173 174 /** 175 * Copy a range of content of the given InputStream to the given OutputStream. 176 * <p>If the specified range exceeds the length of the InputStream, this copies 177 * up to the end of the stream and returns the actual number of copied bytes. 178 * <p>Leaves both streams open when done. 179 * @param in the InputStream to copy from 180 * @param out the OutputStream to copy to 181 * @param start the position to start copying from 182 * @param end the position to end copying 183 * @return the number of bytes copied 184 * @throws IOException in case of I/O errors 185 * @since 4.3 186 */ 187 public static long copyRange(InputStream in, OutputStream out, long start, long end) throws IOException { 188 Assert.notNull(in, "No InputStream specified"); 189 Assert.notNull(out, "No OutputStream specified"); 190 191 long skipped = in.skip(start); 192 if (skipped < start) { 193 throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required"); 194 } 195 196 long bytesToCopy = end - start + 1; 197 byte[] buffer = new byte[(int) Math.min(StreamUtils.BUFFER_SIZE, bytesToCopy)]; 198 while (bytesToCopy > 0) { 199 int bytesRead = in.read(buffer); 200 if (bytesRead == -1) { 201 break; 202 } 203 else if (bytesRead <= bytesToCopy) { 204 out.write(buffer, 0, bytesRead); 205 bytesToCopy -= bytesRead; 206 } 207 else { 208 out.write(buffer, 0, (int) bytesToCopy); 209 bytesToCopy = 0; 210 } 211 } 212 return (end - start + 1 - bytesToCopy); 213 } 214 215 /** 216 * Drain the remaining content of the given InputStream. 217 * <p>Leaves the InputStream open when done. 218 * @param in the InputStream to drain 219 * @return the number of bytes read 220 * @throws IOException in case of I/O errors 221 * @since 4.3 222 */ 223 public static int drain(InputStream in) throws IOException { 224 Assert.notNull(in, "No InputStream specified"); 225 byte[] buffer = new byte[BUFFER_SIZE]; 226 int bytesRead = -1; 227 int byteCount = 0; 228 while ((bytesRead = in.read(buffer)) != -1) { 229 byteCount += bytesRead; 230 } 231 return byteCount; 232 } 233 234 /** 235 * Return an efficient empty {@link InputStream}. 236 * @return a {@link ByteArrayInputStream} based on an empty byte array 237 * @since 4.2.2 238 */ 239 public static InputStream emptyInput() { 240 return new ByteArrayInputStream(EMPTY_CONTENT); 241 } 242 243 /** 244 * Return a variant of the given {@link InputStream} where calling 245 * {@link InputStream#close() close()} has no effect. 246 * @param in the InputStream to decorate 247 * @return a version of the InputStream that ignores calls to close 248 */ 249 public static InputStream nonClosing(InputStream in) { 250 Assert.notNull(in, "No InputStream specified"); 251 return new NonClosingInputStream(in); 252 } 253 254 /** 255 * Return a variant of the given {@link OutputStream} where calling 256 * {@link OutputStream#close() close()} has no effect. 257 * @param out the OutputStream to decorate 258 * @return a version of the OutputStream that ignores calls to close 259 */ 260 public static OutputStream nonClosing(OutputStream out) { 261 Assert.notNull(out, "No OutputStream specified"); 262 return new NonClosingOutputStream(out); 263 } 264 265 266 private static class NonClosingInputStream extends FilterInputStream { 267 268 public NonClosingInputStream(InputStream in) { 269 super(in); 270 } 271 272 @Override 273 public void close() throws IOException { 274 } 275 } 276 277 278 private static class NonClosingOutputStream extends FilterOutputStream { 279 280 public NonClosingOutputStream(OutputStream out) { 281 super(out); 282 } 283 284 @Override 285 public void write(byte[] b, int off, int let) throws IOException { 286 // It is critical that we override this method for performance 287 this.out.write(b, off, let); 288 } 289 290 @Override 291 public void close() throws IOException { 292 } 293 } 294 295}