/* * @@COPYRIGHT@@ */ package com.cosylab.acs.maci.manager; import java.io.Serializable; /** * Data structure for maintaining a collection of elements that can be referred to using handles. * * Stores data elements in an array-like structure. Individual elements are * addressed using handles, which can be though of as indices in the * array. It fulfills these requirements: * * <OL> * <LI><B>allocation</B> - a new element can be added and is assigned a unique handle. * Allocation is an O(1) operation.</LI> * <LI><B>deallocation</B> - an element can be removed from the registrar. * The handle is freed, and can be assigned to another element during allocation at * a later time. Deallocation is an O(1) operation.</LI> * <LI><B>retrieval</B> - a reference to the element can be retrieved for reading and writing. * Retrieval is an O(1) operation. * <LI><B>enumeration</B> - elements stored can be traversed from first to last. * Costs of acquiring first, last, next and previous element of the array are O(1). * </OL> * * This ADT is suitable for enumerating resources that are frequently allocated, * retrieved and deallocated without losing large amounts of memory and/or time. * * This is essentially a doubly-linked list of elements, which are placed in an array. * Each element has assigned a handle (the index in the array), and handles of * the elements that come previous and next to it. There are actually two * chains of elements: the free element chain, which contains all elements * that have not yet been allocated, and the allocated element chain. * Free element chain is cyclic (passing the end resumes at the beginning), * and contains the element with the handle 0. * The allocated element chain is not cyclic: it starts with the element * that was first allocated, and ends with the one that was last allocated. * * @author Matej Sekoranja (matej.sekoranja@cosylab.com) * @author Klemen Zagar (klemen.zagar@cosylab.com) * @version @@VERSION@@ */ public class HandleDataStore implements Serializable { /** * Serial version UID. */ private static final long serialVersionUID = -6137572272422678754L; /** * Element of the <code>HandleDataStore</code>. */ private class Element implements Serializable { /** * Serial version UID. */ private static final long serialVersionUID = -6225830724337396211L; /** * Previous element with the same <code>free</code>. */ int previous; /** * Next element with the same <code>free</code>. */ int next; /** * The actual data. */ Object data; /** * <code>true</code> if the element is still unallocated. */ boolean free; }; /** * Capacity of this ADT. */ private int capacity; /** * Default maximum capacity of this ADT. */ public static final int DEFAULT_MAX_CAPACITY = Integer.MAX_VALUE; /** * Maximum capacity of this ADT. */ private int maxCapacity = DEFAULT_MAX_CAPACITY; /** * Number of elements currently in the <code>HandleDataStore</code>. */ private int size; /** * Array of elements */ private Element[] elements; /** * Handle of the first non-free element. */ private int first; /** * Handle of the last non-free element. */ private int last; /** * Amount by which to offset all handles. */ //private int offset; /** * Constructs a <code>HandleDataStore</code> with zero offset and initial capacity of ten. */ public HandleDataStore() { this(10, DEFAULT_MAX_CAPACITY); } /** * Constructs a <code>HandleDataStore</code> with zero offset. * * @param initialCapacity the initial capacity of the list. * @exception IllegalArgumentException if the specified initial capacity is negative */ public HandleDataStore(int initialCapacity) { this(initialCapacity, DEFAULT_MAX_CAPACITY); } /** * Constructs a <code>HandleDataStore</code>. * * Creates a <code>HandleDataStore</code> and allocates enough space to hold <code>initialCapacity</code> elements. * * @param initialCapacity the initial capacity of the list. * @exception IllegalArgumentException if the specified initial capacity is negative */ public HandleDataStore(int initialCapacity, int maxCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal capacity: "+ initialCapacity); if (maxCapacity < initialCapacity) throw new IllegalArgumentException("Illegal maxCapacity (less than capacity): "+ maxCapacity); this.maxCapacity = maxCapacity; setCapacity(initialCapacity); } /** * Returns the number of elements in this ADT. * * @return the number of elements in this ADT. */ public int size() { return size; } /** * Returns the capacity of this ADT. * * Capacity is the maximum number of elements that this ADT can hold before resizing itself. * @return the capacity of this ADT. */ public int capacity() { return capacity - 1; } /** * Tests if this list has no elements. * * @return <code>true</code> if this list has no elements, <code>false</code> otherwise. */ public boolean isEmpty() { return size == 0; } /** * Returns the element with the specified handle. * * NOTE: <code>handle</code> is not checked, if it is valid. * * @param handle handle of the element * @throws IndexOutOfBoundsException if handle is out of bounds. */ public Object get(int handle) { /* if (handle < 0 || handle >= capacity) throw new IndexOutOfBoundsException(String.valueOf(handle) + " < " + offset + " or " + String.valueOf(handle) + " >= " + (offset+capacity)); */ return elements[handle].data; } /** * Sets the element with the specified handle. * * NOTE: <code>handle</code> is not checked, if it is valid. * * @param handle handle of the element * @param data data to be set * @throws IndexOutOfBoundsException if handle is out of bounds. */ public void set(int handle, Object data) { if (data == null) throw new IllegalArgumentException("data == null"); /* if (handle < 0 || handle >= capacity) throw new IndexOutOfBoundsException(String.valueOf(handle) + " < " + offset + " or " + String.valueOf(handle) + " >= " + (offset+capacity)); */ elements[handle].data = data; } /** * Sets the capacity of this <code>HandleDataStore</code> instance to * hold <code>newCapacity</code> elements;. * * @param newCapacity the desired capacity. */ public void setCapacity(int newCapacity) { // account for the extra element with handle 0 newCapacity++; // It is quite difficult to reduce the size of the registrar, because // doing that could invalidate some of the outstanding handles. Instead // of inventing science, we chose to fail if shrinking is requested. if (newCapacity <= capacity || newCapacity > maxCapacity) return; // allocate memory for new elements Element[] newElements = new Element[newCapacity]; // initialize the newly added elements so that they form a doubly linked list for(int i = capacity; i < newCapacity; i++) { newElements[i] = new Element(); newElements[i].next = i+1; newElements[i].previous = i-1; newElements[i].free = true; } // copy existing elements for (int i = 0; i < capacity; i++) newElements[i] = elements[i]; if(capacity != 0) { // join the newly added elements linked-list to the linked list of free elements newElements[newElements[0].previous].next = capacity; newElements[capacity].previous = newElements[0].previous; newElements[newCapacity-1].next = 0; newElements[0].previous = newCapacity-1; } else { newElements[0].previous = newCapacity-1; newElements[newCapacity-1].next = 0; } // save new data elements = newElements; capacity = newCapacity; } /** * Return the handle of the first element in this ADT. * * @return the handle of the element that was the first one to get allocated and * has not yet been deallocated. * Useful for determining the starting point when enumerating the entire ADT. * @see #next * @see #previous * @see #last */ public int first() { return first; } /** * Return the handle of the last element in this ADT. * * @return the handle of the element that was the last one to get allocated and * has not yet been deallocated. * Useful for determining the starting point when enumerating the entire ADT. * @see #next * @see #previous * @see #first */ public int last() { return last; } /** * Return the handle of the next element in this ADT. * * @param handle handle of the element whose sucessor's handle we wish to acquire. * @return the handle of the element that follows the one whose handle is <code>handle</code>. * If <code>handle</code> is the last element, <code>0</code> is returned. */ public int next(int handle) { return elements[handle].next; } /** * Return the handle of the previous element in this ADT. * * @param handle handle of the element whose predecessor's handle we wish to acquire. * @return the handle of the element that precedes the one whose handle is <code>handle</code>. * If <code>handle</code> is the first element, <code>0</code> is returned. */ public int previous(int handle) { return elements[handle].previous; } /** * Determines whether a given handle is allocated. * * @param handle the handle in question. * @return <code>true</code> if an element with handle <code>handle</code> already * exists in this ADT, <code>false</code> otherwise. */ public boolean isAllocated(int handle) { if (handle <= 0 || handle >= capacity) return false; else return !elements[handle].free; } /** * Allocate an element in this <code>HandleDataStore</code>. * * @return newly allocated handle if allocation was successful, otherwise <code>0</code> * @see #deallocate */ public int allocate() { return allocate(elements[0].next, false); } /** * Allocate an element in this <code>HandleDataStore</code>. * * @param handle hanlde be allocated * @return newly allocated handle if allocation was successful, otherwise <code>0</code> * @see #deallocate */ public int allocate(int handle) { return allocate(handle, false); } /** * Preallocate an element in this <code>HandleDataStore</code>. * * @return newly allocated handle if allocation was successful, otherwise <code>0</code> * @see #allocate * @see #deallocate */ public int preallocate() { return allocate(elements[0].next, true); } /** * Allocate an element with given handle in this <code>HandleDataStore</code>. * * Assures that this ADT is capable of holding yet another element * and returns a handle to it. * * @param handle handle to be allocated * @param preallocate if <code>true</code> element is not really allocated (only reserved, * listing through the ADT will skip preallocated elements), to completely allocate * preallocated elements use <code>ackAllocation(handle)</code> or canceled by * <code>deallocate(handle, true)</code> method. * @return newly allocated handle if allocation was successful, otherwise <code>0</code> * @see #deallocate */ public int allocate(int handle, boolean preallocate) { // if the capacity is exceeded, double the capacity of this ADT if(handle == 0) { int newCapacity = Math.min(2*(capacity-1), maxCapacity-1); setCapacity(newCapacity); handle = elements[0].next; } else if (handle >= capacity && handle < maxCapacity) { setCapacity(handle+1); } // if handle isn't free, bail out if (handle == 0 || handle >= capacity || !elements[handle].free) return 0; // remove element from the free element chain elements[elements[handle].previous].next = elements[handle].next; elements[elements[handle].next].previous = elements[handle].previous; if (!preallocate) ackAllocation(handle); // mark it as allocated. elements[handle].free = false; // increase size size++; return handle; } /** * Completes allocation of handle, to be used to completely allocate handle after preallocation. * * @param handle handle to be completely allocated */ public void ackAllocation(int handle) { // if this is the first element to get allocated, remember it if (first == 0) first = handle; // add newly allocated element to the end of the allocated element chain elements[handle].next = 0; elements[handle].previous = last; // remeber last if (last != 0) elements[last].next = handle; last = handle; } /** * Deallocate an element with the given handle. * * The element and its corresponding handle can be reused at a later call to <code>allocate</code>. * * @param handle the handle of the element to deallocate. * @see #allocate */ public void deallocate(int handle) { deallocate(handle, false); } /** * Deallocate an element with the given handle. * * The element and its corresponding handle can be reused at a later call to <code>allocate</code>. * * @param handle the handle of the element to deallocate. * @param depreallocate has to be <code>true</code>, if handle was only preallocated * @see #allocate */ public void deallocate(int handle, boolean depreallocate) { // check if already free if (elements[handle].free) return; if (!depreallocate) { if (elements[handle].previous != 0) elements[elements[handle].previous].next = elements[handle].next; else first = elements[handle].next; if(elements[handle].next != 0) elements[elements[handle].next].previous = elements[handle].previous; else last = elements[handle].previous; } // mark as free elements[handle].free = true; elements[elements[0].previous].next = handle; elements[handle].previous = elements[0].previous; elements[handle].next = 0; elements[0].previous = handle; // free pointer, to that GC can do its work elements[handle].data = null; // decrease size size--; } /** * Returns a single-line rendition of this instance into text. * * @return internal state of this instance */ public String toString() { StringBuffer sbuff = new StringBuffer(); sbuff.append("HandleDataStore = { "); sbuff.append("size = '"); sbuff.append(size); sbuff.append("', capacity = '"); sbuff.append(capacity); sbuff.append("', elements = { "); int h = first(); while (h != 0) { sbuff.append(get(h)); h = next(h); if (h != 0) sbuff.append(", "); } sbuff.append(" }}"); return new String(sbuff); } }