/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.collections;
import org.helios.apmrouter.unsafe.UnsafeAdapter;
import sun.misc.Unsafe;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicLong;
/**
* <p>Title: UnsafeArray</p>
* <p>Description: Base class for unsafe array implementations</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.collections.UnsafeArray</code></p>
*/
public abstract class UnsafeArray {
/** The default number of slots that will be allocated when the array needs to be extended */
public static final int DEFAULT_ALLOC_INCR = 128;
/** A marker placed in the {@link UnsafeArray#toFullString()} indicating where actual size elements terminate */
public static final String SIZE_MARKER = ">><<,";
/** The byte array offset */
public static final int BYTE_ARRAY_OFFSET;
/** The int array offset */
public static final int INT_ARRAY_OFFSET;
/** The default capacity of an empty created UnsafeArray */
public static int DEFAULT_CAPACITY = 128;
/** A gauge of the number of un-managed memory allocations */
private static final AtomicLong UNMANAGED_MEM_ALLOCATIONS = new AtomicLong(0L);
/** The unsafe instance */
protected static final Unsafe unsafe;
/** Indicates the array will be maintained in sorted order */
protected final boolean sorted;
/** Indicates the capacity of the array will be fixed */
protected final boolean fixed;
/** The maximum capacity of the array */
protected final int maxCapacity;
/** The minimum capacity of the array */
protected final int minCapacity;
/** The number of slots that will be allocated when the array needs to be extended */
protected final int allocationIncrement;
/** The number of excess slots that are emptied by rollLefts before the array capacity is shrunk */
protected final int clearedSlotsFree;
/** The size of one slot */
protected final int slotSize;
/** The native memory address of the array */
protected long address;
/** The current capacity of the array */
protected int capacity;
/** The current size of the array */
protected int size;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe)field.get(null);
BYTE_ARRAY_OFFSET = unsafe.arrayBaseOffset(byte[].class);
INT_ARRAY_OFFSET = unsafe.arrayBaseOffset(int[].class);
} catch (Exception e) {
throw new RuntimeException("Failed to get the Unsafe instance", e);
}
}
/**
* Returns the bitshift factor for calculating the total number of bytes for this array's type.
* i.e. where <b><code>1 << slotsize == number of bytes per slot</code></b>.
* @return the bitshift factor
*/
protected abstract int getSlotSize();
/**
* Appends the specified item to the passed StringBuilder
* @param index The index of the item to append
* @param b the stringbuilder to append to
* @return the passed StringBuilder
*/
protected abstract StringBuilder append(final StringBuilder b, int index);
/**
* Allocates the sized memory block, incrementing the unmanaged counter
* @param size The size of the memory to allocate in bytes
* @return a pointer to the memory block allocated
*/
protected static long allocateMemory(long size) {
long address = unsafe.allocateMemory(size);
UNMANAGED_MEM_ALLOCATIONS.incrementAndGet();
return address;
}
/**
* Frees the memory block pointed to by the passed address, decrementing the unmanaged counter
* @param address The pointer to the memory block to free
* @return the number of unmanaged allocations remaining
*/
protected static long freeMemory(long address) {
unsafe.freeMemory(address);
return UNMANAGED_MEM_ALLOCATIONS.decrementAndGet();
}
/**
* Returns the number of existing unmanaged memory allocations
* @return the number of existing unmanaged memory allocations
*/
public static long getPointerCount() {
return UNMANAGED_MEM_ALLOCATIONS.get();
}
/**
* Creates a new UnsafeArray
* @param initialCapacity The initial allocated capacity
* @param sorted Indicates the array will be maintained in sorted order
* @param fixed Indicates the capacity of the array will be fixed
* @param maxCapacity The maximum capacity of the array
* @param minCapacity The minimum capacity of the array
* @param allocationIncrement The number of slots that will be allocated when the array needs to be extended
* @param clearedSlotsFree The number of excess slots that are emptied by rollLefts before the array capacity is shrunk
*/
protected UnsafeArray(int initialCapacity, boolean sorted, boolean fixed, int maxCapacity, int minCapacity, int allocationIncrement, int clearedSlotsFree) {
this.sorted = sorted;
this.fixed = fixed;
this.maxCapacity = maxCapacity;
this.minCapacity = minCapacity;
this.allocationIncrement = allocationIncrement;
this.clearedSlotsFree = clearedSlotsFree;
slotSize = getSlotSize();
capacity = initialCapacity;
address = allocateMemory(capacity << slotSize);
UnsafeAdapter.setMemory(null, address, capacity << slotSize, (byte)0);
size = 0;
}
/**
* Creates a new UnsafeArray. Used for cloning.
* @param size The size of the clone
* @param capacity The capacity of the clone
* @param address The memory address of the array to be cloned
* @param sorted Indicates the array will be maintained in sorted order
* @param fixed Indicates the capacity of the array will be fixed
* @param maxCapacity The maximum capacity of the array
* @param minCapacity The minimum capacity of the array
* @param allocationIncrement The number of slots that will be allocated when the array needs to be extended
* @param clearedSlotsFree The number of excess slots that are emptied by rollLefts before the array capacity is shrunk
*/
protected UnsafeArray(int size, int capacity, long address, boolean sorted, boolean fixed, int maxCapacity, int minCapacity, int allocationIncrement, int clearedSlotsFree) {
this.sorted = sorted;
this.fixed = fixed;
this.maxCapacity = maxCapacity;
this.minCapacity = minCapacity;
this.allocationIncrement = allocationIncrement;
this.clearedSlotsFree = clearedSlotsFree;
slotSize = getSlotSize();
this.size = size;
this.capacity = capacity;
this.address = allocateMemory(capacity << slotSize);
unsafe.copyMemory(address, this.address, size << slotSize);
}
/**
* Initializes this array from one of the following supported types:<ol>
* <li>Primitive Arrays</li>
* </ol>
* @param data An instance of one of the above supported classes
*/
protected void load(Object data) {
if(data.getClass().isArray()) {
loadArray(data);
}
}
/**
* Initializes this array from an array
* @param data The array to load
*/
protected void loadArray(Object data) {
Class<?> clazz = data.getClass().getComponentType();
if(!clazz.isPrimitive()) throw new RuntimeException("An array of [" + clazz.getName() + "]s cannot be loaded. Only primitives are currently supported", new Throwable());
long offset = unsafe.arrayBaseOffset(data.getClass());
int length = Array.getLength(data);
if(length>maxCapacity) {
throw new ArrayOverflowException("Passed array of length [" + length + "] is too large for this UnsafeArray with a max capacity of [" + maxCapacity + "]", new Throwable());
}
UnsafeAdapter.copyMemory(data, offset, null, address, length << slotSize);
}
/**
* If the passed object is assignable to an {@link UnsafeArray}, indicates if the two instances point to the same array (i.e. memory address).
* @param obj The object to compare to
* @return true if the passed object is an {@link UnsafeLongArray} share the same array (memory address).
*/
public boolean isSameInstance(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!getClass().isAssignableFrom(obj.getClass())) {
return false;
}
UnsafeArray other = (UnsafeArray) obj;
if (address != other.address) {
return false;
}
return true;
}
/**
* Checks to make sure this UnsafeLongArray has not been deallocated
* @return true if this UnsafeLongArray is still allocated, false otherwise
*/
public boolean check() {
return this.address !=0;
}
/**
* Checks that the array has not been deallocated, throwing a {@link IllegalStateException} if it has.
*/
protected void _check() {
if(!check()) throw new IllegalStateException("This UnsafeLongArray has been deallocated", new Throwable());
}
/**
* Deallocates this UnsafeLongArrray
*/
public void destroy() {
if(this.address!=0) {
try { freeMemory(address); } catch (Throwable t) {}
this.address=0;
}
}
/**
* Checks that an index is within the populated slots of this array.
* If the check fails, throws a {@link IllegalArgumentException}
* @param index The index to check
*/
protected void _check(int index) {
if(index<0 || index > (size-1)) {
throw new IllegalArgumentException("The passed index was invalid [" + index + "]. Valid ranges are 0 - " + (size-1), new Throwable());
}
}
protected void _checkc(int index) {
if(index<0 || index > (capacity-1)) {
throw new IllegalArgumentException("The passed index was invalid [" + index + "]. Valid ranges are 0 - " + (capacity-1), new Throwable());
}
}
/**
* Extends the allocated memory for the passed number of items in increments of the configured allocation increment size.
* If this will result in a projected capacity that is larger than {@link Integer#MAX_VALUE},
* will use the highest possible increment where <b><code>increment < Integer.MAX_VALUE && capacity+increment <= max-capacity</code></b>
* @param allowTruncate If true, an allocation of less than what was asked for is ok, otherwise, throw an ArrayOverflowException
* @param items The number of items we're extending for
* @return the number of items additional capacity was allocated for
*/
protected int extend(boolean allowTruncate, int items) {
assert items > 0;
if(fixed && capacity==maxCapacity) throw new ArrayOverflowException("Capacity cannot be extended:" + (fixed ? "Array is fixed capacity" : "Array is at maximum capacity"), new Throwable());
long targetCap = (long)capacity + (long)items;
if(targetCap > Integer.MAX_VALUE) throw new ArrayOverflowException("Capacity cannot be extended by [" + items + "] as it would overflow Integer.MAX_ITEMS", new Throwable());
int targetCapacity = size + items;
if(fixed && targetCapacity > maxCapacity) {
if(!allowTruncate) throw new ArrayOverflowException("Capacity cannot be extended to [" + targetCapacity + "] as it would overflow the defined maximum capacity of [" + maxCapacity + "]", new Throwable());
items = targetCapacity - maxCapacity;
targetCapacity = maxCapacity;
}
int newAlloc = 0;
while(capacity<targetCapacity) {
long cap = capacity;
long alloc = allocationIncrement;
if(cap+alloc > maxCapacity) {
newAlloc = maxCapacity-capacity;
} else if(cap+alloc > Integer.MAX_VALUE) {
newAlloc = Integer.MAX_VALUE-capacity;
} else {
newAlloc = allocationIncrement;
}
capacity += newAlloc;
}
address = unsafe.reallocateMemory(address, (capacity << slotSize) + (newAlloc << slotSize));
return items;
}
/**
* <p>Rolls all the entries in the array one slot to the right after the referenced index,
* optionally extending the array capacity if it is full when this method is called.
* Logically, this opens a new slot at the referenced index, and the new slot is set to the passed new value.
* Once this method completes, the size of the array will have been incremented by 1, unless <b><code>this.fixed==true</code></b>
* in which case both the size and the capacity will be unchanged.</p>
* If this array is fixed capacity when <b><code>size==capacity</code></b>,
* the right-most value of the array will be dropped, effectively creating a sliding-window when used with <b><code>index==0</code></b>.
* <p><b>Note:</b> The rolling of the array values is performed by {@link sun.misc.Unsafe#copyMemory(long, long, long)}</p>
* <p><b>Example</b> of calling <b><code>rollRight(1, 77, bool)</code></b> on an array of size 6 and capacity of 8</p>
* <b>Before Operation</b>
* <pre>
--> --> --> --> -->
+--+ +--+ +--+ +--+ +--+ +--+ Size: 6 Index: 1
|23| |47| |19| |67| |42| |89| Capacity: 8 Value: 77
+--+ +--+ +--+ +--+ +--+ +--+ +--+ +--+
/^\
|
Index
</pre><b>After Operation</b><pre>
+--+ +--+ +--+ +--+ +--+ +--+ +--+ Size: 7
|23| |77| |47| |19| |67| |42| |89| Capacity: 8
+--+ +--+ +--+ +--+ +--+ +--+ +--+ +--+
* </pre>
* @param index The index after which the remaining values are rolled to the right
* @return false if the rightmost item was dropped to make room for the new value, true otherwise
*/
protected boolean rollRight(int index) {
_check(); _checkc(index);
final int numberOfSlotsToMove;
final boolean incrSize;
if(size==capacity) {
if(fixed) {
numberOfSlotsToMove = size-index-1;
incrSize=false;
} else {
extend(false, 1);
numberOfSlotsToMove = size-index;
incrSize=true;
}
} else {
numberOfSlotsToMove = size-index;
incrSize=true;
}
long srcOffset = (index << slotSize);
long destOffset = ((index+1) << slotSize);
long bytes = numberOfSlotsToMove << slotSize;
unsafe.copyMemory(
(address + srcOffset), // src: the address of the first index we want to roll
(address + destOffset), // dest: the address of the slot after the one we want to roll
bytes // bytes: the number of bytes in the entries that need to be rolled
);
if(incrSize) size++;
return incrSize;
}
/**
* Rolls all the entries in the array one slot to the left after the referenced index
* Logically, this removes a new slot at the referenced index.
* Once this method completes, the size of the array will have been decremented by 1.
* @param shrink If true, a shrink check will be made after this op concludes.
* @param index The index after which the remaining values are rolled to the left
* <p><b>Before</b><pre>
<-- <-- <-- <--
+--+ +--+ +--+ +--+ +--+ +--+ Size: 6 Index: 1
|23| |47| |19| |67| |42| |89| Capacity: 6
+--+ +--+ +--+ +--+ +--+ +--+
^
|
Delete
</pre><b>After</b><pre>
+--+ +--+ +--+ +--+ +--+ Size: 5
|23| |19| |67| |42| |89| Capacity: 6
+--+ +--+ +--+ +--+ +--+ +--+
</pre>
*/
public void rollLeft(boolean shrink, int index) {
_check(); _check(index);
int newInd = index+1;
int numberOfSlotsToMove = size-newInd;
long srcOffset = (newInd << slotSize);
long destOffset = (index << slotSize);
long bytes = numberOfSlotsToMove << slotSize;
unsafe.copyMemory(
(address + srcOffset), // src: the address of the first index we want to roll
(address + destOffset), // dest: the address of the slot after the one we want to roll
bytes // bytes: the number of bytes in the entries that need to be rolled
);
size--;
if(shrink) shrink();
}
/**
* <p>Rolls all the entries in the array one slot to the left after the referenced index, then checks for shrink.
* Logically, this removes a new slot at the referenced index.
* Once this method completes, the size of the array will have been decremented by 1.</p>
* @see {@link #rollLeft(boolean, int)}
*
* @param index The index after which the remaining values are rolled to the left
*/
public void rollLeft(int index) {
rollLeft(true, index);
}
/**
* Checks the capacity to see if any empty slots can be released
* @return the number of slots freed
*/
public int shrink() {
int freeSlots = capacity-size; // the number of empty slots
// if true, there are enough empty slots to trigger a shrink,
// and still leave the min capacity available
if(freeSlots >= clearedSlotsFree && freeSlots-minCapacity>0) {
int slotsToFree = freeSlots-minCapacity; // the number of slots to free and leave the min
_shrink(slotsToFree);
return slotsToFree;
}
return 0;
}
/**
* Unchecked version of {@link UnsafeArray#shrink(int)}.
* @param clear The number of free slots to shrink out of the array
*/
private void _shrink(int clear) {
capacity -= clear;
address = unsafe.reallocateMemory(address, capacity << slotSize);
}
/**
* {@inheritDoc}
* @see java.lang.Object#finalize()
*/
@Override
public void finalize() throws Throwable {
if(this.address!=0) try { freeMemory(address); } catch (Throwable t) {}
super.finalize();
}
/**
* Returns the current capacity of the array, i.e. the number of used slots
* @return the current capacity of the array
*/
public int capacity() {
return capacity;
}
/**
* Removes all values and if applicable, shrinks the capacity.
*/
public void clear() {
size = 0;
shrink();
}
/**
* Returns the current size of the array, i.e. the number of used slots
* @return the current size of the array
*/
public int size() {
return size;
}
/**
* Indicates if this array is maintained in a sorted state
* @return true if this array is maintained in a sorted state, false otherwise
*/
public boolean sorted() {
return sorted;
}
/**
* Indicates if this array has a fixed capacity
* @return true if this array has a fixed capacity, false otherwise
*/
public boolean fixed() {
return fixed;
}
/**
* Returns the maximum capacity of this array
* @return the maximum capacity of this array
*/
public int maxCapacity() {
return maxCapacity;
}
/**
* Returns the minimum capacity of this array, i.e. the capacity will not be shrunk below this size
* @return the minimum capacity of this array
*/
public int minCapacity() {
return minCapacity;
}
/**
* Returns the number of slots that will be added when the array is extended, returning 0 if the capacity is fixed.
* @return the number of slots that will be added when the array is extended
*/
public int allocationIncrement() {
return allocationIncrement;
}
/**
* Returns the number of unallocated slots that will trigger a shrink of the capacity when a slot is logically deleted.
* @return of unallocated slots that will trigger a shrink of the capacity
*/
public int clearedSlotsFree() {
return clearedSlotsFree;
}
/**
* Out log
* @param msg The message to log
*/
public static void log(Object msg) {
System.out.println(msg);
}
/**
* Err log
* @param msg The message to log
*/
public static void loge(Object msg) {
if(msg!=null && msg instanceof Throwable) {
((Throwable)msg).printStackTrace(System.err);
}
System.err.println(msg);
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
if(size==0) return "[]";
StringBuilder b = new StringBuilder("[");
for(int i = 0; i < size; i++) {
append(b, i).append(",");
}
b.deleteCharAt(b.length()-1);
b.append("]");
return b.toString();
}
/**
* Renders in the same format as {@link #toString()} except it includes the entire capacity of the array.
* <p><b>Note:</b>The empty slots may contain complete garbage.
* @return a {@link #toString()} of the full array
*/
public String toFullString() {
StringBuilder b = new StringBuilder("fc:[");
for(int i = 0; i < capacity; i++) {
if(i==size-1) {
append(b, i).append(SIZE_MARKER);
} else {
append(b, i).append(",");
}
}
b.deleteCharAt(b.length()-1);
b.append("]");
return b.toString();
}
public static int toInt(byte[] arr) {
int[] iarr = new int[1];
UnsafeAdapter.copyMemory(arr, INT_ARRAY_OFFSET, iarr, INT_ARRAY_OFFSET, 4);
return iarr[0];
}
}