/******************************************************************************* * Copyright 2013 Geoscience Australia * * 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 au.gov.ga.earthsci.common.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; /** * {@link ByteArrayOutputStream} that only keeps the tail of bytes written, * defined by a customizable limit. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class TailByteArrayOutputStream extends ByteArrayOutputStream { protected int limit = 0; protected float loadFactor = 0.5f; protected int start = 0; public TailByteArrayOutputStream() { super(); } public TailByteArrayOutputStream(int size) { super(size); } /** * @return Tail limit of bytes to store */ public int getLimit() { return limit; } /** * Set the limit of the number of tail bytes to store. Passing 0 or negative * means unlimited. * * @param limit */ public void setLimit(int limit) { this.limit = limit; } /** * @return Percentage of the limit that the internal buffer is allowed to * increase its length to. */ public float getLoadFactor() { return loadFactor; } /** * Set the percentage of the <code>limit</code> that the internal buffer is * allowed to increase its length to. A value of 1.0 means that the internal * buffer is allowed to be 100% larger (double) than the limit. A value of * 0.0 means that the internal buffer will never be larger than the limit. * <p/> * Larger values mean the tail will be moved back to the start of the buffer * less often, at the cost of increased memory usage. * <p/> * Default value is 0.5. * * @param loadFactor */ public void setLoadFactor(float loadFactor) { this.loadFactor = Math.max(0, loadFactor); } @Override public synchronized void write(int b) { int movement = moveTail(1); super.write(b); tailUpdated(movement, start, count); } @Override public synchronized void write(byte[] b, int off, int len) { //if limited, and the length to be written is greater than the limit, //reduce the length to be written to the limit if (limit > 0 && len > limit) { off += len - limit; len = limit; } int movement = moveTail(len); super.write(b, off, len); tailUpdated(movement, start, count); } /** * Moves the tail of bytes to the start of the buffer if <code>len</code> * will increase the size of the buffer beyond the allowed capacity. * * @param len * @return Distance the tail moved towards the start of the array */ protected int moveTail(int len) { if (limit > 0) { int newcount = count + len; if (newcount > limit * (1 + loadFactor)) { newcount = Math.max(0, limit - len); int pos = count - newcount; System.arraycopy(buf, pos, buf, 0, newcount); count = newcount; setStart(0); return pos; } else { setStart(Math.max(0, newcount - limit)); } } return 0; } @Override public synchronized void reset() { super.reset(); setStart(0); tailUpdated(0, 0, 0); } /** * Set the position of the first byte in the tail. If the <code>count</code> * is greater than the <code>limit</code>, then * <code>start = count - limit</code>. * * @param start */ protected void setStart(int start) { this.start = start; } /** * Called when the tail is updated/written to. * <p/> * Subclasses can override this to react to changes. * * @param movement * Amount the tail was moved backward in the array (non-zero if * the tail was moved due to the length of the new data written * being greater than the allowed capacity) * @param start * New start of the tail * @param count * New length of the tail */ protected void tailUpdated(int movement, int start, int count) { } @Override public synchronized byte[] toByteArray() { byte[] copy = new byte[count - start]; System.arraycopy(buf, start, copy, 0, copy.length); return copy; } @Override public synchronized String toString() { return new String(buf, start, count); } @Deprecated @Override public synchronized String toString(int hibyte) { return new String(buf, hibyte, start, count); } @Override public synchronized String toString(String charsetName) throws UnsupportedEncodingException { return new String(buf, start, count, charsetName); } @Override public synchronized void writeTo(OutputStream out) throws IOException { out.write(buf, start, count); } }