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.Writer; 029import java.nio.charset.Charset; 030 031/** 032 * Simple utility methods for dealing with streams. The copy methods of this class are 033 * similar to those defined in {@link FileCopyUtils} except that all affected streams are 034 * left open when done. All copy methods use a block size of 4096 bytes. 035 * 036 * <p>Mainly for use within the framework, but also useful for application code. 037 * 038 * @author Juergen Hoeller 039 * @author Phillip Webb 040 * @author Brian Clozel 041 * @since 3.2.2 042 * @see FileCopyUtils 043 */ 044public abstract class StreamUtils { 045 046 /** 047 * The default buffer size used when copying bytes. 048 */ 049 public static final int BUFFER_SIZE = 4096; 050 051 private static final byte[] EMPTY_CONTENT = new byte[0]; 052 053 054 /** 055 * Copy the contents of the given InputStream into a new byte array. 056 * <p>Leaves the stream open when done. 057 * @param in the stream to copy from (may be {@code null} or empty) 058 * @return the new byte array that has been copied to (possibly empty) 059 * @throws IOException in case of I/O errors 060 */ 061 public static byte[] copyToByteArray(InputStream in) throws IOException { 062 if (in == null) { 063 return new byte[0]; 064 } 065 066 ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); 067 copy(in, out); 068 return out.toByteArray(); 069 } 070 071 /** 072 * Copy the contents of the given InputStream into a String. 073 * <p>Leaves the stream open when done. 074 * @param in the InputStream to copy from (may be {@code null} or empty) 075 * @param charset the {@link Charset} to use to decode the bytes 076 * @return the String that has been copied to (possibly empty) 077 * @throws IOException in case of I/O errors 078 */ 079 public static String copyToString(InputStream in, Charset charset) throws IOException { 080 if (in == null) { 081 return ""; 082 } 083 084 StringBuilder out = new StringBuilder(BUFFER_SIZE); 085 InputStreamReader reader = new InputStreamReader(in, charset); 086 char[] buffer = new char[BUFFER_SIZE]; 087 int charsRead; 088 while ((charsRead = reader.read(buffer)) != -1) { 089 out.append(buffer, 0, charsRead); 090 } 091 return out.toString(); 092 } 093 094 /** 095 * Copy the contents of the given byte array to the given OutputStream. 096 * <p>Leaves the stream open when done. 097 * @param in the byte array to copy from 098 * @param out the OutputStream to copy to 099 * @throws IOException in case of I/O errors 100 */ 101 public static void copy(byte[] in, OutputStream out) throws IOException { 102 Assert.notNull(in, "No input byte array specified"); 103 Assert.notNull(out, "No OutputStream specified"); 104 105 out.write(in); 106 out.flush(); 107 } 108 109 /** 110 * Copy the contents of the given String to the given OutputStream. 111 * <p>Leaves the stream open when done. 112 * @param in the String to copy from 113 * @param charset the Charset 114 * @param out the OutputStream to copy to 115 * @throws IOException in case of I/O errors 116 */ 117 public static void copy(String in, Charset charset, OutputStream out) throws IOException { 118 Assert.notNull(in, "No input String specified"); 119 Assert.notNull(charset, "No Charset specified"); 120 Assert.notNull(out, "No OutputStream specified"); 121 122 Writer writer = new OutputStreamWriter(out, charset); 123 writer.write(in); 124 writer.flush(); 125 } 126 127 /** 128 * Copy the contents of the given InputStream to the given OutputStream. 129 * <p>Leaves both streams open when done. 130 * @param in the InputStream to copy from 131 * @param out the OutputStream to copy to 132 * @return the number of bytes copied 133 * @throws IOException in case of I/O errors 134 */ 135 public static int copy(InputStream in, OutputStream out) throws IOException { 136 Assert.notNull(in, "No InputStream specified"); 137 Assert.notNull(out, "No OutputStream specified"); 138 139 int byteCount = 0; 140 byte[] buffer = new byte[BUFFER_SIZE]; 141 int bytesRead; 142 while ((bytesRead = in.read(buffer)) != -1) { 143 out.write(buffer, 0, bytesRead); 144 byteCount += bytesRead; 145 } 146 out.flush(); 147 return byteCount; 148 } 149 150 /** 151 * Copy a range of content of the given InputStream to the given OutputStream. 152 * <p>If the specified range exceeds the length of the InputStream, this copies 153 * up to the end of the stream and returns the actual number of copied bytes. 154 * <p>Leaves both streams open when done. 155 * @param in the InputStream to copy from 156 * @param out the OutputStream to copy to 157 * @param start the position to start copying from 158 * @param end the position to end copying 159 * @return the number of bytes copied 160 * @throws IOException in case of I/O errors 161 * @since 4.3 162 */ 163 public static long copyRange(InputStream in, OutputStream out, long start, long end) throws IOException { 164 Assert.notNull(in, "No InputStream specified"); 165 Assert.notNull(out, "No OutputStream specified"); 166 167 long skipped = in.skip(start); 168 if (skipped < start) { 169 throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required"); 170 } 171 172 long bytesToCopy = end - start + 1; 173 byte[] buffer = new byte[(int) Math.min(StreamUtils.BUFFER_SIZE, bytesToCopy)]; 174 while (bytesToCopy > 0) { 175 int bytesRead = in.read(buffer); 176 if (bytesRead == -1) { 177 break; 178 } 179 else if (bytesRead <= bytesToCopy) { 180 out.write(buffer, 0, bytesRead); 181 bytesToCopy -= bytesRead; 182 } 183 else { 184 out.write(buffer, 0, (int) bytesToCopy); 185 bytesToCopy = 0; 186 } 187 } 188 return (end - start + 1 - bytesToCopy); 189 } 190 191 /** 192 * Drain the remaining content of the given InputStream. 193 * <p>Leaves the InputStream open when done. 194 * @param in the InputStream to drain 195 * @return the number of bytes read 196 * @throws IOException in case of I/O errors 197 * @since 4.3 198 */ 199 public static int drain(InputStream in) throws IOException { 200 Assert.notNull(in, "No InputStream specified"); 201 byte[] buffer = new byte[BUFFER_SIZE]; 202 int bytesRead = -1; 203 int byteCount = 0; 204 while ((bytesRead = in.read(buffer)) != -1) { 205 byteCount += bytesRead; 206 } 207 return byteCount; 208 } 209 210 /** 211 * Return an efficient empty {@link InputStream}. 212 * @return a {@link ByteArrayInputStream} based on an empty byte array 213 * @since 4.2.2 214 */ 215 public static InputStream emptyInput() { 216 return new ByteArrayInputStream(EMPTY_CONTENT); 217 } 218 219 /** 220 * Return a variant of the given {@link InputStream} where calling 221 * {@link InputStream#close() close()} has no effect. 222 * @param in the InputStream to decorate 223 * @return a version of the InputStream that ignores calls to close 224 */ 225 public static InputStream nonClosing(InputStream in) { 226 Assert.notNull(in, "No InputStream specified"); 227 return new NonClosingInputStream(in); 228 } 229 230 /** 231 * Return a variant of the given {@link OutputStream} where calling 232 * {@link OutputStream#close() close()} has no effect. 233 * @param out the OutputStream to decorate 234 * @return a version of the OutputStream that ignores calls to close 235 */ 236 public static OutputStream nonClosing(OutputStream out) { 237 Assert.notNull(out, "No OutputStream specified"); 238 return new NonClosingOutputStream(out); 239 } 240 241 242 private static class NonClosingInputStream extends FilterInputStream { 243 244 public NonClosingInputStream(InputStream in) { 245 super(in); 246 } 247 248 @Override 249 public void close() throws IOException { 250 } 251 } 252 253 254 private static class NonClosingOutputStream extends FilterOutputStream { 255 256 public NonClosingOutputStream(OutputStream out) { 257 super(out); 258 } 259 260 @Override 261 public void write(byte[] b, int off, int let) throws IOException { 262 // It is critical that we override this method for performance 263 this.out.write(b, off, let); 264 } 265 266 @Override 267 public void close() throws IOException { 268 } 269 } 270 271}