package ChunkedString import Table /* A chunked string splits up big strings into chunks, which is used to circumvent the maximum string length limit and to prepare data for chunked storage, e.g. for FileIO. Make sure to flush the chunked string when done writing to it, so the unfilled buffer gets written as well. Write Data: > let cstring = new ChunkedString() > cstring.append("someString") > cstring.append("someOtherString") > cstring.flush() Read Data Sequentially: > while cstring.hasChunk() > let chunk = cstring.readChunk() > Log.info("read chunk: " + chunk) Read Data iteratively: > for i = 0 to cstring.getChunkCount() > let chunk = cstring.getChunk(i) > Log.info("read chunk: " + chunk) */ /** Maximum length of one chunk */ @configurable public constant DEFAULT_CHUNK_SIZE = 200 public class ChunkedString private Table table = new Table() private var chunkSize = DEFAULT_CHUNK_SIZE protected var chunkCount = 0 private var readIndex = -1 protected var buffer = "" construct() construct(int chunkSize) this.chunkSize = chunkSize function append(string pdata) var data = pdata while true var remain = chunkSize - buffer.length() if ENABLE_MULTIBYTE_SUPPORT and remain > 0 and data.length() >= remain and data.substring(remain - 1, remain).getHash() == 1843378377 remain++ buffer += data.substring(0, remain) if buffer.length() >= chunkSize table.saveString(chunkCount, buffer) buffer = "" chunkCount++ data = data.substring(min(remain, data.length())) if data.length() == 0 break function hasChunk() returns boolean return readIndex < chunkCount function readChunk() returns string readIndex++ return getChunk(readIndex) function resetRead() readIndex = -1 function getChunk(int index) returns string if index == chunkCount return buffer return table.loadString(index) /** Returns the number of chunks in the string. The buffer counts as one chunk */ function getChunkCount() returns int return chunkCount + 1 /** Returns the length of every chunk in the string. The last chunk's size may be smaller than this number. */ function getChunkSize() returns int return chunkSize /** Calculates the length of the whole string. */ function length() returns int if ENABLE_MULTIBYTE_SUPPORT var len = 0 for i = 0 to getChunkCount() - 1 len += getChunk(i).length() return len else return chunkCount * chunkSize + buffer.length() /** Concatenates all chunks into one string. This is unsafe because the chunked length might be above the maximum allowed string length. */ function getUnsafeString() returns string var out = "" for i = 0 to getChunkCount() out += getChunk(i) return out /** Concatenates all chunks required to return the requested substring. This is unsafe because the chunked length might be above the maximum allowed string length. */ function getUnsafeSubString(int startIndex, int endIndex) returns string var out = "" if ENABLE_MULTIBYTE_SUPPORT var pointer = 0 for i = 0 to getChunkCount() - 1 let chunk = getChunk(i) let len = chunk.length() if pointer+len <= startIndex // Chunk can be skipped pointer += len else var chunkIdx = 0 if pointer < startIndex chunkIdx = startIndex - pointer pointer = startIndex let leftToRead = endIndex - pointer let leftInChunk = len - chunkIdx if leftToRead <= leftInChunk // Chunk is partially contained in substring out += chunk.substring(chunkIdx, chunkIdx + leftToRead) break else out += chunk.substring(chunkIdx, len) pointer += leftInChunk else let startChunk = startIndex div chunkSize let lastChunk = endIndex div chunkSize for i = startChunk to lastChunk if i == startChunk out += getChunk(i).substring(startIndex - startChunk * chunkSize, min(endIndex - startChunk * chunkSize, chunkSize)) else if i == lastChunk out += getChunk(i).substring(0, endIndex - lastChunk * chunkSize) else out += getChunk(i) return out ondestroy destroy table @Test function chunkedStringTest() let cstring = new ChunkedString(5) cstring.append("abc") cstring.append("def") cstring.chunkCount.assertEquals(1) cstring.buffer.assertEquals("f") cstring.append("ghijklmnopq") cstring.chunkCount.assertEquals(3) cstring.buffer.assertEquals("pq") cstring.getUnsafeString().assertEquals("abcdefghijklmnopq") cstring.getUnsafeSubString(4, 12).assertEquals("efghijkl") cstring.getUnsafeSubString(0, 3).assertEquals("abc") cstring.getUnsafeSubString(15, 17).assertEquals("pq") @Test function chunkedStringIteration() let cstring = new ChunkedString(5) cstring.append("abc") cstring.append("def") var result = "" for i = 0 to cstring.getChunkCount() result += cstring.getChunk(i) result.assertEquals("abcdef") result = "" while cstring.hasChunk() result += cstring.readChunk() result.assertEquals("abcdef")