import java.util.List; import java.util.ArrayList; public class RoedyAccumulator implements StringAccumulator { private final List filledBuffers; private final int bufSize; private StringBuilder buf; // lemma: this will only be empty when the accumulator is empty public RoedyAccumulator(int bufSize) { filledBuffers = new ArrayList(); this.bufSize = bufSize; buf = new StringBuilder(bufSize); } public RoedyAccumulator() { this(8096); } public void accumulate(Object obj) { accumulate(obj.toString()); } public void accumulate(String str) { if (str.length() == 0) return; // not for performance, just makes things easier to think about! pushBufferIfNecessary(); if (str.length() <= available()) { // deal with the simple case simply append(str, 0, str.length()); } else { // deal with the general case generally int off = available(); append(str, 0, off); pushBufferIfNecessary(); // get all chars written so far into the list assert buf.length() == 0: "all characters should have been pushed to the list"; int remaining; while ((remaining = str.length() - off) >= bufSize()) { // avoid copying - put substrings directly in the list filledBuffers.add(str.substring(off, (off + bufSize))); off += bufSize(); } append(str, off, (off + remaining)); } } /** Appends some string to the buffer. Never more than what will fit in the current buffer (or a fresh one). */ private void append(String s, int off, int end) { int len = end - off; if (len == 0) return; pushBufferIfNecessary(); if (len > available()) throw new IllegalStateException(); buf.append(s.substring(off, end)); } private void pushBufferIfNecessary() { if (available() == 0) pushBuffer(); } private void pushBuffer() { assert available() == 0: "pushBuffer must only be called when the buffer is full"; filledBuffers.add(buf); buf = new StringBuilder(bufSize()); } /** @return the number of chars which can be appended to the current buffer before it fills */ private int available() { assert buf.length() <= bufSize: "buffer length should always be less than the buffer size: " + buf.length(); return buf.capacity() - buf.length(); } private int bufSize() { assert buf.length() <= bufSize: "buffer length should always be less than the buffer size: " + buf.length(); return bufSize; } public String toString() { int size = (filledBuffers.size() * bufSize()) + buf.length(); StringBuilder finalBuf = new StringBuilder(size); // note that StringBuilder optimises append(CharSequence) when it's actually a String or StringBuilder, so this is nice and quick for (CharSequence filledBuf: filledBuffers) finalBuf.append(filledBuf); finalBuf.append(buf); return finalBuf.toString(); } }