/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.mvstore; import java.nio.ByteBuffer; /** * An auto-resize buffer to write data into a ByteBuffer. */ public class WriteBuffer { /** * The maximum size of the buffer in order to be re-used after a clear * operation. */ private static final int MAX_REUSE_CAPACITY = 4 * 1024 * 1024; /** * The minimum number of bytes to grow a buffer at a time. */ private static final int MIN_GROW = 1024 * 1024; /** * The buffer that is used after a clear operation. */ private ByteBuffer reuse; /** * The current buffer (may be replaced if it is too small). */ private ByteBuffer buff; public WriteBuffer(int initialSize) { reuse = ByteBuffer.allocate(initialSize); buff = reuse; } public WriteBuffer() { this(MIN_GROW); } /** * Write a variable size integer. * * @param x the value * @return this */ public WriteBuffer putVarInt(int x) { DataUtils.writeVarInt(ensureCapacity(5), x); return this; } /** * Write a variable size long. * * @param x the value * @return this */ public WriteBuffer putVarLong(long x) { DataUtils.writeVarLong(ensureCapacity(10), x); return this; } /** * Write the characters of a string in a format similar to UTF-8. * * @param s the string * @param len the number of characters to write * @return this */ public WriteBuffer putStringData(String s, int len) { ByteBuffer b = ensureCapacity(3 * len); DataUtils.writeStringData(b, s, len); return this; } /** * Put a byte. * * @param x the value * @return this */ public WriteBuffer put(byte x) { ensureCapacity(1).put(x); return this; } /** * Put a character. * * @param x the value * @return this */ public WriteBuffer putChar(char x) { ensureCapacity(2).putChar(x); return this; } /** * Put a short. * * @param x the value * @return this */ public WriteBuffer putShort(short x) { ensureCapacity(2).putShort(x); return this; } /** * Put an integer. * * @param x the value * @return this */ public WriteBuffer putInt(int x) { ensureCapacity(4).putInt(x); return this; } /** * Put a long. * * @param x the value * @return this */ public WriteBuffer putLong(long x) { ensureCapacity(8).putLong(x); return this; } /** * Put a float. * * @param x the value * @return this */ public WriteBuffer putFloat(float x) { ensureCapacity(4).putFloat(x); return this; } /** * Put a double. * * @param x the value * @return this */ public WriteBuffer putDouble(double x) { ensureCapacity(8).putDouble(x); return this; } /** * Put a byte array. * * @param bytes the value * @return this */ public WriteBuffer put(byte[] bytes) { ensureCapacity(bytes.length).put(bytes); return this; } /** * Put a byte array. * * @param bytes the value * @param offset the source offset * @param length the number of bytes * @return this */ public WriteBuffer put(byte[] bytes, int offset, int length) { ensureCapacity(length).put(bytes, offset, length); return this; } /** * Put the contents of a byte buffer. * * @param src the source buffer * @return this */ public WriteBuffer put(ByteBuffer src) { ensureCapacity(buff.remaining()).put(src); return this; } /** * Set the limit, possibly growing the buffer. * * @param newLimit the new limit * @return this */ public WriteBuffer limit(int newLimit) { ensureCapacity(newLimit - buff.position()).limit(newLimit); return this; } /** * Get the capacity. * * @return the capacity */ public int capacity() { return buff.capacity(); } /** * Set the position. * * @param newPosition the new position * @return the new position */ public WriteBuffer position(int newPosition) { buff.position(newPosition); return this; } /** * Get the limit. * * @return the limit */ public int limit() { return buff.limit(); } /** * Get the current position. * * @return the position */ public int position() { return buff.position(); } /** * Copy the data into the destination array. * * @param dst the destination array * @return this */ public WriteBuffer get(byte[] dst) { buff.get(dst); return this; } /** * Update an integer at the given index. * * @param index the index * @param value the value * @return this */ public WriteBuffer putInt(int index, int value) { buff.putInt(index, value); return this; } /** * Update a short at the given index. * * @param index the index * @param value the value * @return this */ public WriteBuffer putShort(int index, short value) { buff.putShort(index, value); return this; } /** * Clear the buffer after use. * * @return this */ public WriteBuffer clear() { if (buff.limit() > MAX_REUSE_CAPACITY) { buff = reuse; } else if (buff != reuse) { reuse = buff; } buff.clear(); return this; } /** * Get the byte buffer. * * @return the byte buffer */ public ByteBuffer getBuffer() { return buff; } private ByteBuffer ensureCapacity(int len) { if (buff.remaining() < len) { grow(len); } return buff; } private void grow(int additional) { ByteBuffer temp = buff; int needed = additional - temp.remaining(); // grow at least MIN_GROW long grow = Math.max(needed, MIN_GROW); // grow at least 50% of the current size grow = Math.max(temp.capacity() / 2, grow); // the new capacity is at most Integer.MAX_VALUE int newCapacity = (int) Math.min(Integer.MAX_VALUE, temp.capacity() + grow); if (newCapacity < needed) { throw new OutOfMemoryError("Capacity: " + newCapacity + " needed: " + needed); } try { buff = ByteBuffer.allocate(newCapacity); } catch (OutOfMemoryError e) { throw new OutOfMemoryError("Capacity: " + newCapacity); } temp.flip(); buff.put(temp); if (newCapacity <= MAX_REUSE_CAPACITY) { reuse = buff; } } }