/* * Copyright (c) 2008-2011 by Bjoern Kolbeck, Jan Stender, * Zuse Institute Berlin * * Licensed under the BSD License, see LICENSE file for details. * */ package de.mxro.thrd.xstreemfs.foundation.buffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.concurrent.atomic.AtomicInteger; import de.mxro.thrd.xstreemfs.foundation.logging.Logging; import de.mxro.thrd.xstreemfs.foundation.logging.Logging.Category; /** * * @author bjko */ public final class ReusableBuffer { private static final Charset ENC_UTF8 = Charset.forName("utf-8"); /** * A view buffer of parentBuffer with the requested size. For non-reusable * buffers this is the buffer itself */ private ByteBuffer buffer; /** * A parent buffer which is returned to the pool */ private final ByteBuffer parentBuffer; /** * True if the buffer can be returned to the pool */ private final boolean reusable; /** * set to true after a buffer was returned to the pool */ protected volatile boolean returned; /** * size (as requested), might be smaller than parentBuffer size but is * always equal to the (view) buffer size. */ private int size; protected ReusableBuffer viewParent; protected String freeStack, allocStack; /** * reference count */ AtomicInteger refCount; /** * Creates a new instance of ReusableBuffer. A view buffer of size is * created. * * @param buffer * the parent buffer * @param size * the requested size */ protected ReusableBuffer(ByteBuffer buffer, int size) { buffer.position(0); buffer.limit(size); this.buffer = buffer.slice(); this.parentBuffer = buffer; this.size = size; this.reusable = true; this.refCount = new AtomicInteger(1); returned = false; viewParent = null; } /** * A wrapper for a non-reusable buffer. The buffer is not used by the pool * when returned. */ public ReusableBuffer(ByteBuffer nonManaged) { this.buffer = nonManaged; this.size = buffer.limit(); this.reusable = false; this.parentBuffer = null; returned = false; this.refCount = new AtomicInteger(1); viewParent = null; } /** * Creates a non-reusable buffer around a byte array. Uses the * ByteBuffer.wrap method. * * @param data * the byte arry containing the data * @return */ public static ReusableBuffer wrap(byte[] data) { return new ReusableBuffer(ByteBuffer.wrap(data)); } public static ReusableBuffer wrap(byte[] data, int offset, int length) { assert (offset >= 0); assert (length >= 0); if (offset + length > data.length) throw new IllegalArgumentException("offset+length > buffer size (" + offset + "+" + length + " > " + data.length); ByteBuffer tmp = ByteBuffer.wrap(data); tmp.position(offset); tmp.limit(offset + length); return new ReusableBuffer(tmp.slice()); } /** * Creates a new view buffer. This view buffer shares the same data (i.e. * backing byte buffer) but has independet position, limit etc. */ public ReusableBuffer createViewBuffer() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; if (this.viewParent == null) { if (parentBuffer == null) { // wraped buffers ReusableBuffer view = new ReusableBuffer(this.buffer, this.size); view.viewParent = this; return view; } else { // regular buffer ReusableBuffer view = new ReusableBuffer(this.parentBuffer, this.size); view.viewParent = this; this.refCount.incrementAndGet(); if (BufferPool.recordStackTraces) { try { throw new Exception("allocate stack trace"); } catch (Exception e) { view.allocStack = "\n"; for (StackTraceElement elem : e.getStackTrace()) view.allocStack += elem.toString() + "\n"; } } return view; } } else { if (parentBuffer == null) { // wraped buffers ReusableBuffer view = new ReusableBuffer(this.buffer, this.size); view.viewParent = this.viewParent; return view; } else { // regular buffer: use the parent to create a view buffer ReusableBuffer view = new ReusableBuffer(this.buffer, this.size); view.viewParent = this.viewParent; this.viewParent.refCount.incrementAndGet(); if (BufferPool.recordStackTraces) { try { throw new Exception("allocate stack trace"); } catch (Exception e) { view.allocStack = "\n"; for (StackTraceElement elem : e.getStackTrace()) view.allocStack += elem.toString() + "\n"; } } return view; } } } /** * @see java.nio.Buffer#capacity */ public int capacity() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return this.size; } /** * @see java.nio.ByteBuffer#hasArray */ public boolean hasArray() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.hasArray(); } /** * Returns the byte array of the buffer, creating a copy if the buffer is * not backed by an array * * @return a byte array with a copy of the data */ public byte[] array() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; byte[] array = null; if (this.hasArray() && (this.viewParent == null)) { array = buffer.array(); } else { array = new byte[this.limit()]; final int oldPos = this.position(); this.position(0); this.get(array); this.position(oldPos); } return array; } /** * @see java.nio.Buffer#flip */ public void flip() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.flip(); } /** * @see java.nio.Buffer#compact */ public void compact() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.compact(); } /** * @see java.nio.Buffer#limit(int) */ public void limit(int l) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.limit(l); } /** * @see java.nio.Buffer#limit() */ public int limit() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.limit(); } /** * @see java.nio.Buffer#position(int) */ public void position(int p) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.position(p); } /** * @see java.nio.Buffer#position() */ public int position() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.position(); } /** * @see java.nio.Buffer#hasRemaining */ public boolean hasRemaining() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.hasRemaining(); } /** * Returns the view buffer encapsulated by this ReusableBuffer. * * @return the view buffer */ public ByteBuffer getBuffer() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return this.buffer; } /** * Returns true, if this buffer is re-usable and can be returned to the * pool. * * @return true, if this buffer is re-usable */ public boolean isReusable() { // assert(!returned) : // "Buffer was already freed and cannot be used anymore"+this.freeStack; return this.reusable; } /** * Returns the parent buffer. * * @return the parent buffer */ protected ByteBuffer getParent() { return this.parentBuffer; } /** * @see java.nio.ByteBuffer#get() */ public byte get() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.get(); } public byte get(int index) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.get(index); } /** * @see java.nio.ByteBuffer#get(byte[]) */ public ReusableBuffer get(byte[] dst) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.get(dst); return this; } /** * @see java.nio.ByteBuffer#get(byte[], int offset, int length) */ public ReusableBuffer get(byte[] dst, int offset, int length) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.get(dst, offset, length); return this; } /** * @see java.nio.ByteBuffer#put(byte) */ public ReusableBuffer put(byte b) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.put(b); return this; } /** * @see java.nio.ByteBuffer#put(byte[]) */ public ReusableBuffer put(byte[] src) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.put(src); return this; } /** * @see java.nio.ByteBuffer#put(byte[],int,int) */ public ReusableBuffer put(byte[] src, int offset, int len) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.put(src, offset, len); return this; } /** * @see java.nio.ByteBuffer#put(ByteBuffer) */ public ReusableBuffer put(ByteBuffer src) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.put(src); return this; } /** * Writes the content of src into this buffer. * * @param src * the buffer to read from * @return this ReusableBuffer after reading * @see java.nio.ByteBuffer#put(ByteBuffer) */ public ReusableBuffer put(ReusableBuffer src) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.put(src.buffer); return this; } /** * @see java.nio.ByteBuffer#getInt */ public int getInt() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.getInt(); } /** * @see java.nio.ByteBuffer#putInt(int) */ public ReusableBuffer putInt(int i) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.putInt(i); return this; } public long getLong() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.getLong(); } public ReusableBuffer putLong(long l) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.putLong(l); return this; } public double getDouble() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.getDouble(); } public ReusableBuffer putDouble(double d) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.putDouble(d); return this; } public String getString() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; int length = buffer.getInt(); if (length > 0) { byte[] bytes = new byte[length]; buffer.get(bytes); return new String(bytes, ENC_UTF8); } else if (length == 0) { return ""; } else { return null; } } public ReusableBuffer putString(String str) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; if (str != null) { byte[] bytes = str.getBytes(ENC_UTF8); buffer.putInt(bytes.length); buffer.put(bytes); } else { buffer.putInt(-1); } return this; } public ReusableBuffer putShortString(String str) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; assert (str.length() <= Short.MAX_VALUE); if (str != null) { byte[] bytes = str.getBytes(ENC_UTF8); buffer.putShort((short) bytes.length); buffer.put(bytes); } else { buffer.putInt(-1); } return this; } public ASCIIString getBufferBackedASCIIString() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return ASCIIString.unmarshall(this); } public ReusableBuffer putBufferBackedASCIIString(ASCIIString str) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; if (str != null) { str.marshall(this); } else { buffer.putInt(-1); } return this; } public ReusableBuffer putShort(short s) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.putShort(s); return this; } public short getShort() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.getShort(); } /** * @see java.nio.ByteBuffer#isDirect */ public boolean isDirect() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.isDirect(); } /** * @see java.nio.Buffer#remaining */ public int remaining() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.remaining(); } /** * @see java.nio.Buffer#clear */ public void clear() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.clear(); } public byte[] getData() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; byte[] array = new byte[this.position()]; this.position(0); this.get(array); return array; } public void shrink(int newSize) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; if (newSize > size) { throw new IllegalArgumentException("new size must not be larger than old size"); } this.size = newSize; int oldPos = buffer.position(); if (oldPos > newSize) oldPos = 0; // save parent position and limit int position = parentBuffer.position(); int limit = parentBuffer.limit(); parentBuffer.position(0); parentBuffer.limit(newSize); this.buffer = parentBuffer.slice(); buffer.position(oldPos); // restore parent position and limit parentBuffer.position(position); parentBuffer.limit(limit); } public boolean enlarge(int newSize) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; if (newSize > this.parentBuffer.capacity()) { return false; } else { this.size = newSize; int oldPos = buffer.position(); if (oldPos > newSize) oldPos = 0; // save parent position and limit int position = parentBuffer.position(); int limit = parentBuffer.limit(); parentBuffer.position(0); parentBuffer.limit(newSize); this.buffer = parentBuffer.slice(); buffer.position(oldPos); // restore parent position and limit parentBuffer.position(position); parentBuffer.limit(limit); return true; } } public void range(int offset, int length) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; // useless call! if ((offset == 0) && (length == this.size)) return; if (offset > size) { throw new IllegalArgumentException("offset must be < size. offset=" + offset + " siz=" + size); } if (offset + length > size) { throw new IllegalArgumentException("offset+length must be <= size. size=" + size + " offset=" + offset + " length=" + length); } this.size = length; // save parent position and limit int position = parentBuffer.position(); int limit = parentBuffer.limit(); // ensure that the subsequent 'position' does not fail if (offset > limit) parentBuffer.limit(offset); parentBuffer.position(offset); parentBuffer.limit(offset + length); this.buffer = parentBuffer.slice(); assert (this.buffer.capacity() == length); // restore parent position and limit parentBuffer.position(position); parentBuffer.limit(limit); } public ReusableBuffer putBoolean(boolean bool) { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; buffer.put(bool ? (byte) 1 : (byte) 0); return this; } public boolean getBoolean() { assert (!returned) : "Buffer was already freed and cannot be used anymore" + this.freeStack; return buffer.get() == 1; } public int getRefCount() { if (this.viewParent == null) { return this.refCount.get(); } else { return this.viewParent.refCount.get(); } } protected void finalize() { if (!returned && reusable) { Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "buffer was finalized but not freed before! buffer = %s", this.toString()); Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "stacktrace: %s", allocStack); if (Logging.isDebug()) { byte[] data = new byte[(this.capacity() > 128) ? 128 : this.capacity()]; this.position(0); this.limit(this.capacity()); this.get(data); String content = new String(data); Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "content: %s", content); Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "stacktrace: %s", allocStack); if (this.viewParent != null) { Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "view parent: %s", this.viewParent.toString()); Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "view parent stacktrace: %s", this.viewParent.allocStack); Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "ref count: %d", this.viewParent.refCount.get()); } else { Logging.logMessage(Logging.LEVEL_WARN, Category.buffer, this, "ref count: %d", this.refCount.get()); } } BufferPool.free(this); } } public String toString() { return "ReusableBuffer( capacity=" + this.capacity() + " limit=" + this.limit() + " position=" + this.position() + ")"; } }