diff --git a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..727c55b86882ebea065c4f68883dccf0bbd6df7f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java @@ -0,0 +1,155 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.util; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A variation of {@link java.io.ByteArrayOutputStream} that: + * + * + * @author Brian Clozel + * @since 4.0 + */ +public class ResizableByteArrayOutputStream extends OutputStream { + + private static final int INITIAL_BUFFER_SIZE = 32; + + protected byte[] buffer; + + protected int count; + + /** + * Create a new ByteArrayOutputStream with the default buffer size of 32 bytes. + */ + public ResizableByteArrayOutputStream() { + this(INITIAL_BUFFER_SIZE); + } + + /** + * Create a new ByteArrayOutputStream with a specified initial buffer size. + * + * @param size The initial buffer size in bytes + */ + public ResizableByteArrayOutputStream(int size) { + buffer = new byte[size]; + count = 0; + } + + /** + * Return the size of the internal buffer. + */ + public int size() { + return buffer.length; + } + + /** + * Return the number of bytes that have been written to the buffer so far. + */ + public int count() { + return count; + } + + /** + * Discard all bytes written to the internal buffer by setting the count variable to 0. + */ + public void reset() { + count = 0; + } + + /** + * Grow the internal buffer size + * @param add number of bytes to add to the current buffer size + * @see ResizableByteArrayOutputStream#size() + */ + public void grow(int add) { + if (count + add > buffer.length) { + int newlen = Math.max(buffer.length * 2, count + add); + resize(newlen); + } + } + + /** + * Resize the internal buffer size to a specified value + * @param size the size of the buffer + * @throws java.lang.IllegalArgumentException if the given size is + * smaller than the actual size of the content stored in the buffer + * @see ResizableByteArrayOutputStream#size() + */ + public void resize(int size) { + Assert.isTrue(size >= count); + + byte[] newbuf = new byte[size]; + System.arraycopy(buffer, 0, newbuf, 0, count); + buffer = newbuf; + } + + /** + * Write the specified byte into the internal buffer, thus incrementing the + * {{@link org.springframework.util.ResizableByteArrayOutputStream#count()}} + * @param oneByte the byte to be written in the buffer + * @see ResizableByteArrayOutputStream#count() + */ + public void write(int oneByte) { + grow(1); + buffer[count++] = (byte) oneByte; + } + + /** + * Write add bytes from the passed in array + * inBuffer starting at index offset into the + * internal buffer. + * + * @param inBuffer The byte array to write data from + * @param offset The index into the buffer to start writing data from + * @param add The number of bytes to write + * @see ResizableByteArrayOutputStream#count() + */ + public void write(byte[] inBuffer, int offset, int add) { + if (add >= 0) { + grow(add); + } + System.arraycopy(inBuffer, offset, buffer, count, add); + count += add; + } + + /** + * Write all bytes that have been written to the specified OutputStream. + * + * @param out The OutputStream to write to + * @exception IOException If an error occurs + */ + public void writeTo(OutputStream out) throws IOException { + out.write(buffer, 0, count); + } + + /** + * Return a byte array containing the bytes that have been written to this stream so far. + */ + public byte[] toByteArray() { + byte[] ret = new byte[count]; + System.arraycopy(buffer, 0, ret, 0, count); + return ret; + } + +} diff --git a/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java b/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java new file mode 100644 index 0000000000000000000000000000000000000000..e5ec17bb8495b986f58d456c71d63bcfb1e277af --- /dev/null +++ b/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.util; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Brian Clozel + */ +public class ResizableByteArrayOutputStreamTests { + + private ResizableByteArrayOutputStream baos; + + private String helloString; + + private byte[] helloBytes; + + private static final int INITIAL_SIZE = 32; + + @Before + public void setUp() throws Exception { + this.baos = new ResizableByteArrayOutputStream(INITIAL_SIZE); + this.helloString = "Hello World"; + this.helloBytes = helloString.getBytes("UTF-8"); + } + + @Test + public void resize() throws Exception { + assertEquals(INITIAL_SIZE, this.baos.buffer.length); + this.baos.write(helloBytes); + int size = 64; + this.baos.resize(size); + assertEquals(size, this.baos.buffer.length); + assertByteArrayEqualsString(helloString, this.baos); + } + + @Test + public void autoGrow() { + assertEquals(INITIAL_SIZE, this.baos.buffer.length); + for(int i= 0; i < 33; i++) { + this.baos.write(0); + } + assertEquals(64, this.baos.buffer.length); + } + + @Test + public void grow() throws Exception { + assertEquals(INITIAL_SIZE, this.baos.buffer.length); + this.baos.write(helloBytes); + this.baos.grow(100); + assertEquals(this.helloString.length() + 100, this.baos.buffer.length); + assertByteArrayEqualsString(helloString, this.baos); + } + + @Test + public void write() throws Exception{ + this.baos.write(helloBytes); + assertByteArrayEqualsString(helloString, this.baos); + } + + @Test(expected = IllegalArgumentException.class) + public void failResize() throws Exception{ + this.baos.write(helloBytes); + this.baos.resize(5); + } + + private void assertByteArrayEqualsString(String expected, ResizableByteArrayOutputStream actual) { + String actualString = new String(actual.buffer, 0, actual.count()); + assertEquals(expected, actualString); + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java index 2414c1989e8502f9150052eb8ccb882ebf595ecd..1179f2a8421d74c2b968492f5c103560633d432d 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java @@ -16,7 +16,6 @@ package org.springframework.web.filter; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; @@ -30,6 +29,7 @@ import javax.servlet.http.HttpServletResponseWrapper; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; +import org.springframework.util.ResizableByteArrayOutputStream; import org.springframework.util.DigestUtils; import org.springframework.util.StreamUtils; import org.springframework.web.util.WebUtils; @@ -175,7 +175,7 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { */ private static class ShallowEtagResponseWrapper extends HttpServletResponseWrapper { - private final ByteArrayOutputStream content = new ByteArrayOutputStream(); + private final ResizableByteArrayOutputStream content = new ResizableByteArrayOutputStream(); private final ServletOutputStream outputStream = new ResponseServletOutputStream(); @@ -214,6 +214,7 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { @Override public void setContentLength(int len) { + this.content.resize(len); } @Override