/* Copyright 1997, John Webster Small, All rights reserved */ package extensions.util; import java.util.*; /** * A general purpose container for objects of any class * or polymorphic cluster of classes. The container * emulates the various collection classes, e.g. * stack, queue, list, bag, etc. * * @version 1.0.2 * @author John Webster Small, email: jsmall@laser.net * @see extensions.util.Comparable */ public class ContainerLite implements Cloneable { /** Maximum nodes ever allowed in one container. * Notes: Value must be <= Integer.MAX_VALUE / 2 * to prevent overflow on (first+n)%_limit * expressions! */ public static final int MAXNODES = Integer.MAX_VALUE/2; /** Indicates that the requested node was not found. */ public static final int NOTFOUND = MAXNODES; /** Default size of initial physical array. */ public static final int LIMIT = 32; /** Default expansion/contraction increment. */ public static final int DELTA = 16; /** Restricts containment to instances of * baseElementClass and its descendants. */ public static final boolean DESCENDANTS = true; /** Restricts containment to instances of * baseElementClass. */ public static final boolean ONLY_CLASS = false; private Class _rootElementClass; private boolean _descendants; /** * The following relationships are maintained * during operation of a Container: * 1 <= _delta <= lowLimit <= _limit <= _maxNodes * <= MAXNODES and * lowThreshold = lowLimit - _delta; */ private int lowLimit, lowThreshold, first; private Object linkS[]; private int _limit, _delta, _nodes, _maxNodes; /** Is container still sorted? */ private boolean _sorted; /** Node comparator */ private Comparable cmP; /** Override to notify incoming element that it * is about to be attached to the container. The * element has the opportunity to reject binding * by having this function return false. */ protected boolean attachD(Object D) { return true; } /** Override to notify element that it has been * detached from the container. */ protected void detachD(Object D) { return; } /** * Constructs an empty container, restricting * containment to the rootElementClass and perhaps * its descendants. * @param rootElementClass restrict object membership * @param descendants allow binding of descendants */ public ContainerLite (Class rootElementClass, boolean descendants) { if (rootElementClass == null) try { _rootElementClass = Class.forName("java.lang.Object"); } catch (ClassNotFoundException e) {} else _rootElementClass = rootElementClass; _descendants = descendants; _sorted = true; lowLimit = _limit; lowThreshold = lowLimit - _delta; first = 0; linkS = null; _limit = LIMIT; _delta = DELTA; _nodes = 0; _maxNodes = MAXNODES; } /** Constructs an empty container for instances * of any derivative of java.lang.Object. */ public ContainerLite() { this(null,DESCENDANTS); } /** Explodes an enumeration into a container * of its elements. * @param e elements to be added initially */ public ContainerLite(Enumeration e) { this(null,DESCENDANTS); while (e.hasMoreElements()) insQ(e.nextElement()); } /** Explodes an array into a container * of its elements. * @param array array of elements to be added initially */ public ContainerLite(Object[] array) { this(null,DESCENDANTS); for (int i = 0; i < array.length; i++) insQ(array[i]); } /** Explodes a vector into a container * of its elements. * @param v vector of elements to be added initially */ public ContainerLite(Vector v) { this(null,DESCENDANTS); for (int i = 0; i < v.size(); i++) insQ(v.elementAt(i)); } /** Clone this container via a shallow copy! */ public synchronized Object clone() { ContainerLite gc = new ContainerLite(_rootElementClass,_descendants); gc.setLimit(_limit); int i = first; int j = _nodes; while (j > 0 && i < _limit) { gc.atIns(0,linkS[i++]); j--; } i = 0; while (j > 0) { gc.atIns(0,linkS[i++]); j--; } return gc; } /** Convert container to string for debug, etc. */ public synchronized String toString() { StringBuffer buf = new StringBuffer(); Enumeration e = elements(); buf.append(getClass().getName()); buf.append("["); while (e.hasMoreElements()) { buf.append(e.nextElement().toString()); if (e.hasMoreElements()) buf.append(", "); } buf.append("]"); return buf.toString(); } /** Compare two containers for equality. */ public synchronized boolean equals(Object anObject) { if (anObject instanceof ContainerLite) { ContainerLite b = (ContainerLite) anObject; if (_nodes != b.nodes()) return false; for (int i = 0; i < nodes(); i++) if (!atGet(i).equals(b.atGet(i))) return false; return true; } return false; } /** Maximum number of nodes that can be * accomodated without expansion. */ public final int limit() { return _limit; } /** * Expands the current limit * @param newLimit the newly requested limit */ public final synchronized void setLimit(int newLimit) { if (newLimit < _nodes) newLimit = _nodes; else if (newLimit > _maxNodes) newLimit = _maxNodes; if (newLimit < _delta) newLimit = _delta; if (newLimit <= 0 || newLimit == _limit) return; if (linkS == null) { _limit = newLimit; return; } Object[] newLinkS = new Object[newLimit]; int headNodes = _limit - first; if (headNodes > _nodes) headNodes = _nodes; if (headNodes > 0) // move leading nodes System.arraycopy (linkS,first,newLinkS,0,headNodes); // copy wrap around trailing nodes if (headNodes < _nodes) System.arraycopy (linkS,0,newLinkS,headNodes,_nodes-headNodes); if (newLimit > _limit) if (newLimit - _delta > _limit) lowLimit = newLimit - _delta; else lowLimit = _limit; else if (newLimit - _delta > _delta) lowLimit = newLimit - _delta; else lowLimit = _delta; lowThreshold = lowLimit - _delta; linkS = newLinkS; _limit = newLimit; first = 0; } /** Trim container's internal physical array size * to its logical size. */ public final void pack() { setLimit(_nodes); } /** * Increment of expansion * @return increment */ public final int delta() { return _delta; } /** * Sets increment of expansion. * @param newDelta the newly requested increment */ public final synchronized void setDelta(int newDelta) { if (newDelta > 0 && newDelta <= lowLimit) _delta = newDelta; } /** Returns count of elements in container. */ public final int nodes() { return _nodes; } /** Returns true if there are no elements in container. */ public final boolean empty() { return _nodes == 0; } /** Returns maximum number of elements allowed in container. */ public final int maxNodes() { return _maxNodes; } /** * Sets the maximum number of nodes allowed in container. * @param newMaxNodes the new upper bound on container size */ public final synchronized void setMaxNodes(int newMaxNodes) { if (newMaxNodes < _limit) if (newMaxNodes > _nodes) setLimit(newMaxNodes); if (newMaxNodes >= _limit) if (newMaxNodes > MAXNODES) _maxNodes = MAXNODES; else _maxNodes = newMaxNodes; } /** The maximum number of additional nodes allowed. */ public final boolean vacancy() { return _nodes < _maxNodes; } /** The maximum number of additional nodes that can * be accomodated without expansion of internal, * physical array. */ public final boolean vacancyNonElastic() { return _nodes < _limit; } private boolean implementsInterface(Class[] i) { for (int j = 0; j < i.length; j++) if (i[j] == _rootElementClass) return true; else if (_descendants) if (implementsInterface(i[j].getInterfaces())) return true; return false; } /** Is this type of instance allowed in this container? */ public final boolean typeAllowed(Object D) { if (D == null) return false; if (_rootElementClass.isInterface()) { return implementsInterface(D.getClass().getInterfaces()); } else if (_descendants) { try { if (_rootElementClass == Class.forName("java.lang.Object")) return true; } catch (ClassNotFoundException e) {} for (Class T = D.getClass(); T != null; T = T.getSuperclass()) if (T == _rootElementClass) return true; return false; } else return D.getClass() == _rootElementClass; } private final void validateType(Object D) { if (!typeAllowed(D)) throw new ArrayStoreException ("wrong type for ContainerLite"); } private final void validateIndex(int n) { if (linkS == null || n >= _nodes || n < 0) throw new ArrayIndexOutOfBoundsException(); } /** The basic type allowed in this container. */ public final Class rootType() { return _rootElementClass; } /** Are descendants allowed? */ public final boolean polyCluster() { return _descendants; } /** * Insert a new cell into the "elastic array" * assigning the incoming element to this cell. * @return indicate success of insertion * @param n index of insertion * @param D element to insert */ public final synchronized boolean atIns(int n, Object D) { Object[] newLinkS; int newLimit, i; if (D == null) return false; validateType(D); if (n < 0) throw new ArrayIndexOutOfBoundsException(); if (_nodes == _limit) { // no vacancy - expand if (_limit == _maxNodes) return false; if (_maxNodes - _delta > _limit) newLimit = _limit + _delta; else newLimit = _maxNodes; newLinkS = new Object[newLimit]; if (!attachD(D)) // data refuses binding return false; if (n > _nodes) n = _nodes; newLinkS[n] = D; // calc headNodes i = _limit - first; if (n <= i) { // insert in head section if (n > 0) { // postfix or split head System.arraycopy (linkS,first,newLinkS,0,n); if (n < i) // it's a split System.arraycopy (linkS,first+n,newLinkS,n+1,i-n); } else // prefix head System.arraycopy (linkS,first,newLinkS,1,i); if (i < _nodes) // copy tail System.arraycopy (linkS,0,newLinkS,1+i,_nodes-i); } else { // insert in tail section // copy head first System.arraycopy (linkS,first,newLinkS,0,i); if (i < _nodes) { // copy tail System.arraycopy (linkS,0,newLinkS,i,n-i); if (n < _nodes) // it's a tail split System.arraycopy (linkS,n-i,newLinkS, n+1,_nodes-n); } } /* Compute next smaller linkS size and threshold for shrinking. */ lowLimit = _limit; lowThreshold = lowLimit - _delta; // swap new for old linkS = newLinkS; _limit = newLimit; first = 0; } else { // vacancy if (linkS == null) linkS = new Object[_limit]; if (!attachD(D)) { // data refuses binding if (_nodes == 0) linkS = null; return false; } if (n == 0) { // push if (first > 0) first--; else first = _limit - 1; linkS[first] = D; } else if (n >= _nodes) { // insQ n = _nodes; linkS[(first+n) % _limit] = D; } else { // insert interior i = (first + n) % _limit; if (i < first || first == 0) // move rear rightward System.arraycopy(linkS,i,linkS, i+1,_nodes-n); else { // move front leftward System.arraycopy(linkS,first, linkS,first-1,n); first--; i--; } linkS[i] = D; } } _nodes++; _sorted = false; return true; } /** * Insert remaining elements of the enumeration * sequentially into the "elastic array". * @return number of elements inserted * @param n index of insertion * @param D element to insert */ public final synchronized int atIns(int n, Enumeration e) { int i = n; for (/* no init */; e.hasMoreElements(); i++) if (!atIns(i,e.nextElement())) break; return i - n; } /** * Insert element into the queue. * @return indicate success of insertion * @param D element to be queued */ public final boolean insQ(Object D) { return atIns(_nodes,D); } /** * Exchange the element with the one stored * in a container at the specified index. * @return element orignally at index n * @param n index of cell to be exchanged * @param D new element to store at index n */ public final synchronized Object atXchg(int n, Object D) { if (D == null) return null; validateType(D); validateIndex(n); int i = (first + n) % _limit; if (D != linkS[i]) if (attachD(D)) { Object oldD = linkS[i]; detachD(oldD); linkS[i] = D; _sorted = false; return oldD; } return null; } /** * Fetch element at index n * @return element at index n * @param n index */ public final synchronized Object atGet(int n) { validateIndex(n); return linkS[(first + n) % _limit]; } /** * Remove cell from the elastic array returning * its element. * @return element at index n * @param n index of extraction */ public final synchronized Object atRmv(int n) { Object[] newLinkS; int newLimit, i; Object D; validateIndex(n); i = (first + n) % _limit; D = linkS[i]; newLinkS = null; newLimit = lowLimit; if (_nodes <= lowThreshold) { // get ready to contract try { newLinkS = new Object[newLimit]; } catch(Throwable t) { newLinkS = null; } /* Compute next smaller linkS size and threshold for shrinking. */ if (newLinkS != null) { if (_nodes < _delta) lowThreshold = 0; else lowThreshold -= _delta; lowLimit = lowThreshold + _delta; } } if (newLinkS != null) { // remove and contract // calc head nodes i = _limit - first; if (i > _nodes) i = _nodes; if (n < i) { // rmv from head section if (n > 0) { // rmv from head System.arraycopy(linkS,first, newLinkS,0,n); if (n < i - 1) System.arraycopy(linkS,first+n+1, newLinkS,n,i-n-1); } else if (i > 1) // pop head System.arraycopy(linkS,first+1, newLinkS,0,i-1); if (i < _nodes) // copy tail System.arraycopy(linkS,0, newLinkS,i-1,_nodes-i); } else { // i <= n < nodes // rmv from tail section copy head first System.arraycopy(linkS,first, newLinkS,0,i); if (i == n) { // pop tail if (n < _nodes - 1) // copy trailing tail System.arraycopy(linkS,1, newLinkS,i,_nodes-n-1); } else { // copy up to n in tail System.arraycopy(linkS,0, newLinkS,i,n-i); // skip n'th node if (n < _nodes - 1) // copy anything // trailing n'th node System.arraycopy(linkS,n-i+1, newLinkS,n,_nodes-n-1); } } // swap new for old linkS = newLinkS; _limit = newLimit; first = 0; } else { // simply remove if (n == 0) { // pop linkS[first++] = null; if (first >= _limit) first = 0; } else if (n != _nodes-1) // del interior if (i < first) { // move tail leftward System.arraycopy(linkS,i+1, linkS,i,_nodes-n-1); linkS[(first+_nodes-1)% _limit] = null; } else { // move head rightward System.arraycopy(linkS,first, linkS,first+1,n); linkS[first++] = null; } else // unQ linkS[(first+_nodes-1)% _limit] = null; } if (--_nodes == 0) { linkS = null; _sorted = true; } detachD(D); return D; } /** * Remove all occurrences of the indicated element and * their cells from the elastic array. * @return number of occurrences removed * @param D object to be removed */ public final synchronized int rmv(Object D) { int i = 0; int found = 0; while ((i = indexOf(D,i)) != NOTFOUND) { found++; atRmv(i); } return found; } /** Remove all elements from container. */ public final synchronized void allRmv() { int i, j; if (linkS != null) { i = first; j = _nodes; while (j > 0 && i < _limit) { detachD(linkS[i++]); j--; } i = 0; while (j > 0) { detachD(linkS[i++]); j--; } _sorted = true; first = 0; _nodes = 0; linkS = null; _limit = _delta; lowLimit = _limit; lowThreshold = lowLimit - _delta; } } /** Remove all instances of all remaining * elements of the enumeration from the container. */ public final synchronized void allRmv(Enumeration e) { while (e.hasMoreElements()) rmv(e.nextElement()); } /** * Look for element in container using a linear search. * @return index of element if found, otherwise NOTFOUND * @param D element sought * @param startIdx index to start search from */ public final synchronized int indexOf(Object D, int startIdx) { if (linkS != null) { int i = (first + startIdx) % _limit; int j = _nodes - startIdx; while (j > 0 && i < _limit) { if (linkS[i] == D) return i; i++; j--; } i = 0; while (j > 0) { if (linkS[i] == D) return i; i++; j--; } } return NOTFOUND; } /** * Look for element in container using a linear search. * @return index of element if found, otherwise NOTFOUND * @param D element sought */ public final int indexOf(Object D) { return indexOf(D,0); } /** Release all elements in the container. */ public void finalize() { allRmv(); } /** Return enumeration of elements in the container. */ public final synchronized Enumeration elements() { return new ContainerLiteEnumerator(this); } /** Return array of elements in the container. */ public final synchronized Object[] toArray(int offset, int count) { if (_nodes <= 0 || offset < 0 || offset >= _nodes || count <= 0) return null; if (offset + count > _nodes) count = _nodes - offset; Object[] array = new Object[_nodes]; int i = first, j = count, k = 0; while (j > 0 && i < _limit) { array[k++] = linkS[i++]; j--; } i = 0; while (j-- > 0) array[k++] = linkS[i++]; return array; } /** Return array of elements in the container. */ public final synchronized Object[] toArray() { return toArray(0,_nodes); } /** Is the container still sorted? */ public final boolean sorted() { return _sorted; } /** Force container to think it is unsorted. */ public final synchronized void unsort() { _sorted = false; } /** * Set the comparator object * @see Comparable */ public final synchronized void setCmP(Comparable cmP) { if ((this.cmP != cmP) && (_nodes > 0)) _sorted = false; this.cmP = cmP; } /** * Insert element into priority queue. * The comparator object must have been previously set. * @see #setCmP * @return indicate success of insertion * @param D element to be inserted */ public final synchronized boolean insSort(Object D) { int low, mid, high; if (cmP == null || D == null) return false; validateType(D); if (!_sorted) { sort(); if (!_sorted) return false; } low = 0; high = _nodes; // linkS == 0 -> nodes == 0! while (low < high) { mid = (int)((((long)high) + ((long)low)) >> 1); if (cmP.compare(D,linkS[(first+mid) % _limit]) <= 0) high = mid; else low = mid + 1; } boolean ok = atIns(high,D); _sorted = true; return ok; } /** * Insert elements into priority queue. * The comparator object must have been previously set. * @see #setCmP * @return number of elements inserted * @param e elements to be inserted */ public final synchronized int insSort(Enumeration e) { int count = 0; while (e.hasMoreElements()) { if (!insSort(e.nextElement())) break; count++; } return count; } /** * Sort the container. * The comparator object must have been previously set. * @see #setCmP */ public final synchronized void sort() { int low, mid, high, d, nidx; Object D_; if (cmP == null) return; _sorted = true; if (_nodes == 0) return; if (first > 0) { // form contiguous block at front d = (first + _nodes) % _limit; if (d > first) { // no wrap System.arraycopy (linkS,first,linkS,0,_nodes); nidx = first + _nodes; while (--nidx >= _nodes) linkS[nidx] = null; } else if (d < first) { // join head to rear of tail System.arraycopy(linkS,first, linkS,d,_limit-first); nidx = d + _limit - first; while (nidx < _limit) linkS[nidx++] = null; } // else array is full/contiguous first = 0; } high = 1; d = 1; while (d < _nodes) { low = 0; D_ = linkS[d]; while (low < high) { mid = (int) ((((long)high) + ((long)low)) >> 1); if (cmP.compare(D_,linkS[mid]) <= 0) high = mid; else low = mid + 1; } if (high < d) { System.arraycopy(linkS,high, linkS,high+1,d-high); linkS[high] = D_; } high = ++d; } } /** * Look for first matching element using binary * search if the container is sorted and honorSorted * flag is set, otherwise a linear search is used. * @return index of first matching element or NOTFOUND * @param key element key * @param cmpKey comparator object * @param honorSorted if true then use a binary search * if the container is sorted, in all other cases use * a linear search. * @param offset search at offset * @param count range of search is from offset to * offset + count - 1. */ public final synchronized int find(Object key, Comparable cmpKey, boolean honorSorted, int offset, int count) { int low, high, idx; if (key == null || cmpKey == null || offset < 0 || offset >= _nodes || count <= 0) return NOTFOUND; if (offset + count > _nodes) count = _nodes - offset; if (honorSorted && _sorted) { low = offset; high = offset + count - 1; // binary search for first match while (low < high) { idx = (int)((((long)high) + ((long)low)) >> 1); if (cmpKey.compare(key,linkS[(first+idx) % _limit]) <= 0) high = idx; else low = idx + 1; } idx = high; if (cmpKey.compare(key,linkS[(first+high) % _limit]) != 0) idx = _nodes; } else { // linear search! idx = offset; do { if (cmpKey.compare(key,linkS[(first+idx) % _limit]) == 0) break; idx++; } while (count-- > 0); } return ((idx < _nodes)? idx: NOTFOUND); } /** * Look for first matching element using binary * search if the container is sorted and honorSorted * flag is set, otherwise a linear search is used. * @return index of first matching element or NOTFOUND * @param key element key * @param cmpKey comparator object * @param honorSorted if true then use a binary search * if the container is sorted, in all other cases use * a linear search. */ public final int findFirst (Object key, Comparable cmpKey, boolean honorSorted) { return find(key,cmpKey,honorSorted,0,_nodes); } /** * Look for all matching elements using binary * search if the container is sorted and honorSorted * flag is set, otherwise a linear search is used. * @return enumeration of matching elements * @param key element key * @param cmpKey comparator object * @param honorSorted if true then use a binary search * if the container is sorted, in all other cases use * a linear search. */ public final synchronized Enumeration findAll (Object key, Comparable cmpKey, boolean honorSorted) { ContainerLite gc = new ContainerLite(); for (int i = find(key,cmpKey,honorSorted,0,_nodes); i != NOTFOUND; i = find(key,cmpKey,honorSorted,i+1,_nodes-i)) gc.insQ(atGet(i)); return gc.elements(); } /** * Look for first matching element using binary search if the * container is sorted, otherwise a linear search is used. * The comparator object must have been previously set. * @see #setCmP * @return index of first matching element or NOTFOUND * @param D element key */ public final int firstMatching(Object D) { return find(D,cmP,true,0,_nodes); } /** * Look for all matching elements using binary * search if the container is sorted and honorSorted * flag is set, otherwise a linear search is used. * @return enumeration of matching elements * @param D element key */ public final synchronized Enumeration allMatching(Object D) { ContainerLite gc = new ContainerLite(); for (int i = find(D,cmP,true,0,_nodes); i != NOTFOUND; i = find(D,cmP,true,i+1,_nodes-i)) gc.insQ(atGet(i)); return gc.elements(); } } final class ContainerLiteEnumerator implements Enumeration { private ContainerLite gc; private int idx; public ContainerLiteEnumerator(ContainerLite gc) { this.gc = gc; idx = 0; } public boolean hasMoreElements() { return idx < gc.nodes(); } public Object nextElement() { try { return gc.atGet(idx++); } catch (Exception e) { throw new NoSuchElementException ("ContainerLiteEnumerator"); } } }