package jadex.commons.collection; import jadex.commons.SUtil; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; /** * A list with weak entries. */ public class WeakList implements List, Serializable { //-------- attributes -------- /** The list of elements. */ protected transient Reference[] array; /** The number of elements. */ protected int size; /** The state (to check for modifications). */ protected int state; /** Reference queue for garbage-collected elements. */ protected transient ReferenceQueue queue; //-------- constructors -------- /** * Create a new list. */ public WeakList() { this.array = new Reference[10]; this.size = 0; this.state = 0; this.queue = new ReferenceQueue(); } //-------- methods -------- /** * Returns the number of elements in this list. If this list contains * more than <tt>Integer.MAX_VALUE</tt> elements, returns * <tt>Integer.MAX_VALUE</tt>. * @return the number of elements in this list. */ public int size() { expungeStaleEntries(); return size; } /** * Returns <tt>true</tt> if this list contains no elements. * @return <tt>true</tt> if this list contains no elements. */ public boolean isEmpty() { expungeStaleEntries(); return size==0; } /** * Returns <tt>true</tt> if this list contains the specified element. * More formally, returns <tt>true</tt> if and only if this list contains * at least one element <tt>e</tt> such that * <tt>(o==null ? e==null : o.equals(e))</tt>. * @param o element whose presence in this list is to be tested. * @return <tt>true</tt> if this list contains the specified element. * @throws ClassCastException if the type of the specified element * is incompatible with this list (optional). * @throws NullPointerException if the specified element is null. */ public boolean contains(Object o) { expungeStaleEntries(); for(int i=0; i<size; i++) if(o.equals(array[i].get())) return true; return false; } /** * Returns an iterator over the elements in this list in proper sequence. * Can handle garbage collection of elements, but fails fast in the presence * of concurrent modifications. * @return an iterator over the elements in this list in proper sequence. */ public Iterator iterator() { expungeStaleEntries(); return new Iterator() { int i = 0; int removeindex = -1; int startstate = state; Object next = null; public boolean hasNext() { if(startstate!=state) throw new ConcurrentModificationException("List must not be modified while iterating."); // Find next element. if(next==null && i<size) { next=array[i].get(); while(next==null && i<size) { i++; next=array[i].get(); } } return next!=null; } public Object next() { // Find next element. Object ret; if(hasNext()) ret = next; else throw new NoSuchElementException("No more elements in iterator."); // Move cursor and reset next element. removeindex = i; i++; next = null; return ret; } public void remove() { if(removeindex==-1) throw new IllegalStateException("Remove can only be called once after a call to next() method."); if(startstate!=state) throw new ConcurrentModificationException("List must not be modified while iterating."); WeakList.this.remove(removeindex); removeindex = -1; startstate = state; // Modification through iterator is allowed. } }; } /** * Returns an array containing all of the elements in this list in proper * sequence. Obeys the general contract of the * <tt>Collection.toArray</tt> method. * @return an array containing all of the elements in this list in proper * sequence. * @see java.util.Arrays#asList(Object[]) */ public Object[] toArray() { expungeStaleEntries(); Object[] ret = new Object[size]; int index = 0; for(int i=0; i<size; i++) { Object o = array[i].get(); if(o!=null) ret[index++] = o; } // Shrink array, if necessary. if(index<size) { Object[] ret2 = new Object[index]; System.arraycopy(ret, 0, ret2, 0, index); ret = ret2; } return ret; } /** * Returns an array containing all of the elements in this list in proper * sequence; the runtime type of the returned array is that of the * specified array. Obeys the general contract of the * <tt>Collection.toArray(Object[])</tt> method. * @param ret the array into which the elements of this list are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose. * @return an array containing the elements of this list. * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this list. * @throws NullPointerException if the specified array is <tt>null</tt>. */ public Object[] toArray(Object[] ret) { expungeStaleEntries(); if(ret.length<size) ret = (Object[])Array.newInstance(ret.getClass().getComponentType(), size); int index = 0; for(int i=0; i<size; i++) { Object o = array[i].get(); if(o!=null) ret[index++] = o; } // Shrink array, if necessary. if(index<size) { Object[] ret2 = (Object[])Array.newInstance(ret.getClass().getComponentType(), index); System.arraycopy(ret, 0, ret2, 0, index); ret = ret2; } return ret; } /** * Appends the specified element to the end of this list (optional * operation). <p> * * Lists that support this operation may place limitations on what * elements may be added to this list. In particular, some * lists will refuse to add null elements, and others will impose * restrictions on the type of elements that may be added. List * classes should clearly specify in their documentation any restrictions * on what elements may be added. * @param o element to be appended to this list. * @return <tt>true</tt> (as per the general contract of the * <tt>Collection.add</tt> method). * @throws UnsupportedOperationException if the <tt>add</tt> method is not * supported by this list. * @throws ClassCastException if the class of the specified element * prevents it from being added to this list. * @throws NullPointerException if the specified element is nul. * @throws IllegalArgumentException if some aspect of this element * prevents it from being added to this list. */ public boolean add(Object o) { expungeStaleEntries(); if(o==null) throw new NullPointerException("Null elements not supported."); if(array.length==size) { Reference[] array2 = new Reference[array.length*2]; System.arraycopy(array, 0, array2, 0, size); array = array2; } array[size++] = new WeakReference(o, queue); state++; return true; } /** * Removes the first occurrence in this list of the specified element * (optional operation). If this list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index i * such that <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> (if * such an element exists). * @param o element to be removed from this list, if present. * @return <tt>true</tt> if this list contained the specified element. * @throws ClassCastException if the type of the specified element * is incompatible with this list (optional). * @throws NullPointerException if the specified element is null. * @throws UnsupportedOperationException if the <tt>remove</tt> method is * not supported by this list. */ public boolean remove(Object o) { expungeStaleEntries(); for(int i=0; i<size; i++) { if(o.equals(array[i].get())) { size--; if(i<size) System.arraycopy(array, i+1, array, i, size-i); array[size] = null; state++; return true; } } return false; } /** * Returns <tt>true</tt> if this list contains all of the elements of the * specified collection. * @param c collection to be checked for containment in this list. * @return <tt>true</tt> if this list contains all of the elements of the * specified collection. * @throws ClassCastException if the types of one or more elements * in the specified collection are incompatible with this * list (optional). * @throws NullPointerException if the specified collection contains one * or more null elements and this list does not support null * elements (optional). * @throws NullPointerException if the specified collection is * <tt>null</tt>. * @see #contains(Object) */ public boolean containsAll(Collection c) { throw new UnsupportedOperationException(); } /** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the specified * collection's iterator (optional operation). The behavior of this * operation is unspecified if the specified collection is modified while * the operation is in progress. (Note that this will occur if the * specified collection is this list, and it's nonempty.) * @param collection collection whose elements are to be added to this list. * @return <tt>true</tt> if this list changed as a result of the call. * @throws UnsupportedOperationException if the <tt>addAll</tt> method is * not supported by this list. * @throws ClassCastException if the class of an element in the specified * collection prevents it from being added to this list. * @throws NullPointerException if the specified collection contains one * or more null elements and this list does not support null * elements, or if the specified collection is <tt>null</tt>. * @throws IllegalArgumentException if some aspect of an element in the * specified collection prevents it from being added to this * list. * @see #add(Object) */ public boolean addAll(Collection collection) { throw new UnsupportedOperationException(); } /** * Inserts all of the elements in the specified collection into this * list at the specified position (optional operation). Shifts the * element currently at that position (if any) and any subsequent * elements to the right (increases their indices). The new elements * will appear in this list in the order that they are returned by the * specified collection's iterator. The behavior of this operation is * unspecified if the specified collection is modified while the * operation is in progress. (Note that this will occur if the specified * collection is this list, and it's nonempty.) * @param index index at which to insert first element from the specified * collection. * @param collection elements to be inserted into this list. * @return <tt>true</tt> if this list changed as a result of the call. * @throws UnsupportedOperationException if the <tt>addAll</tt> method is * not supported by this list. * @throws ClassCastException if the class of one of elements of the * specified collection prevents it from being added to this * list. * @throws NullPointerException if the specified collection contains one * or more null elements and this list does not support null * elements, or if the specified collection is <tt>null</tt>. * @throws IllegalArgumentException if some aspect of one of elements of * the specified collection prevents it from being added to * this list. * @throws IndexOutOfBoundsException if the index is out of range (index * < 0 || index > size()). */ public boolean addAll(int index, Collection collection) { throw new UnsupportedOperationException(); } /** * Removes from this list all the elements that are contained in the * specified collection (optional operation). * @param c collection that defines which elements will be removed from * this list. * @return <tt>true</tt> if this list changed as a result of the call. * @throws UnsupportedOperationException if the <tt>removeAll</tt> method * is not supported by this list. * @throws ClassCastException if the types of one or more elements * in this list are incompatible with the specified * collection (optional). * @throws NullPointerException if this list contains one or more * null elements and the specified collection does not support * null elements (optional). * @throws NullPointerException if the specified collection is * <tt>null</tt>. * @see #remove(Object) * @see #contains(Object) */ public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } /** * Retains only the elements in this list that are contained in the * specified collection (optional operation). In other words, removes * from this list all the elements that are not contained in the specified * collection. * @param c collection that defines which elements this set will retain. * @return <tt>true</tt> if this list changed as a result of the call. * @throws UnsupportedOperationException if the <tt>retainAll</tt> method * is not supported by this list. * @throws ClassCastException if the types of one or more elements * in this list are incompatible with the specified * collection (optional). * @throws NullPointerException if this list contains one or more * null elements and the specified collection does not support * null elements (optional). * @throws NullPointerException if the specified collection is * <tt>null</tt>. * @see #remove(Object) * @see #contains(Object) */ public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } /** * Removes all of the elements from this list (optional operation). This * list will be empty after this call returns (unless it throws an * exception). */ public void clear() { expungeStaleEntries(); while(queue.poll()!=null); for(int i=0; i<size; i++) array[i] = null; size = 0; state++; } /** * Returns the element at the specified position in this list. * @param index index of element to return. * @return the element at the specified position in this list. * @throws IndexOutOfBoundsException if the index is out of range (index * < 0 || index >= size()). */ public Object get(int index) { expungeStaleEntries(); Object ret; // Hack !!! Throws array index out of bounds with different index than in parameter!? while((ret=array[index++].get())==null); return ret; } /** * Replaces the element at the specified position in this list with the * specified element (optional operation). * @param index index of element to replace. * @param o element to be stored at the specified position. * @return the element previously at the specified position. * @throws UnsupportedOperationException if the <tt>set</tt> method is not * supported by this list. * @throws ClassCastException if the class of the specified element * prevents it from being added to this list. * @throws NullPointerException if the specified element is null and * this list does not support null elements. * @throws IllegalArgumentException if some aspect of the specified * element prevents it from being added to this list. * @throws IndexOutOfBoundsException if the index is out of range * (index < 0 || index >= size()). */ public Object set(int index, Object o) { expungeStaleEntries(); if(o==null) throw new NullPointerException("Null elements not supported."); Object ret; // Hack !!! Throws array index out of bounds with different index than in parameter!? while((ret=array[index].get())==null) index++; array[index] = new WeakReference(o, queue); state++; return ret; } /** * Inserts the specified element at the specified position in this list * (optional operation). Shifts the element currently at that position * (if any) and any subsequent elements to the right (adds one to their * indices). * @param index index at which the specified element is to be inserted. * @param o element to be inserted. * @throws UnsupportedOperationException if the <tt>add</tt> method is not * supported by this list. * @throws ClassCastException if the class of the specified element * prevents it from being added to this list. * @throws NullPointerException if the specified element is null and * this list does not support null elements. * @throws IllegalArgumentException if some aspect of the specified * element prevents it from being added to this list. * @throws IndexOutOfBoundsException if the index is out of range * (index < 0 || index > size()). */ public void add(int index, Object o) { expungeStaleEntries(); if(o==null) throw new NullPointerException("Null elements not supported."); if(index<0 || index>size) throw new IndexOutOfBoundsException("size="+size+", index="+index); // Insert in new array. if(array.length==size) { Reference[] array2 = new Reference[array.length*2]; if(index>0) System.arraycopy(array, 0, array2, 0, index); if(index<size-1) System.arraycopy(array, index, array2, index+1, size-index); array = array2; } // Insert in current array. else { System.arraycopy(array, index, array, index+1, size-index); } array[index] = new WeakReference(o, queue); size++; state++; } /** * Removes the element at the specified position in this list (optional * operation). Shifts any subsequent elements to the left (subtracts one * from their indices). Returns the element that was removed from the * list. * @param index the index of the element to removed. * @return the element previously at the specified position. * @throws UnsupportedOperationException if the <tt>remove</tt> method is * not supported by this list. * @throws IndexOutOfBoundsException if the index is out of range (index * < 0 || index >= size()). */ public Object remove(int index) { expungeStaleEntries(); Object ret; // Hack !!! Throws array index out of bounds with different index than in parameter!? while((ret=array[index].get())==null) index++; size--; if(index<size) System.arraycopy(array, index+1, array, index, size-index); array[size] = null; state++; return ret; } /** * Returns the index in this list of the first occurrence of the specified * element, or -1 if this list does not contain this element. * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. * @param o element to search for. * @return the index in this list of the first occurrence of the specified * element, or -1 if this list does not contain this element. * @throws ClassCastException if the type of the specified element * is incompatible with this list (optional). * @throws NullPointerException if the specified element is null. */ public int indexOf(Object o) { expungeStaleEntries(); for(int i=0; i<size; i++) if(o.equals(array[i].get())) return i; return -1; } /** * Returns the index in this list of the last occurrence of the specified * element, or -1 if this list does not contain this element. * More formally, returns the highest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. * @param o element to search for. * @return the index in this list of the last occurrence of the specified * element, or -1 if this list does not contain this element. * @throws ClassCastException if the type of the specified element * is incompatible with this list (optional). * @throws NullPointerException if the specified element is null. */ public int lastIndexOf(Object o) { expungeStaleEntries(); for(int i=size-1; i>=0; i--) if(o.equals(array[i].get())) return i; return -1; } /** * Returns a list iterator of the elements in this list (in proper * sequence). * @return a list iterator of the elements in this list (in proper * sequence). */ public ListIterator listIterator() { throw new UnsupportedOperationException(); } /** * Returns a list iterator of the elements in this list (in proper * sequence), starting at the specified position in this list. The * specified index indicates the first element that would be returned by * an initial call to the <tt>next</tt> method. An initial call to * the <tt>previous</tt> method would return the element with the * specified index minus one. * @param index index of first element to be returned from the * list iterator (by a call to the <tt>next</tt> method). * @return a list iterator of the elements in this list (in proper * sequence), starting at the specified position in this list. * @throws IndexOutOfBoundsException if the index is out of range (index * < 0 || index > size()). */ public ListIterator listIterator(int index) { throw new UnsupportedOperationException(); } /** * Returns a view of the portion of this list between the specified * <tt>fromIndex</tt>, inclusive, and <tt>toIndex</tt>, exclusive. (If * <tt>fromIndex</tt> and <tt>toIndex</tt> are equal, the returned list is * empty.) The returned list is backed by this list, so non-structural * changes in the returned list are reflected in this list, and vice-versa. * The returned list supports all of the optional list operations supported * by this list.<p> * * This method eliminates the need for explicit range operations (of * the sort that commonly exist for arrays). Any operation that expects * a list can be used as a range operation by passing a subList view * instead of a whole list. For example, the following idiom * removes a range of elements from a list: * <pre> * list.subList(from, to).clear(); * </pre> * Similar idioms may be constructed for <tt>indexOf</tt> and * <tt>lastIndexOf</tt>, and all of the algorithms in the * <tt>Collections</tt> class can be applied to a subList.<p> * * The semantics of the list returned by this method become undefined if * the backing list (i.e., this list) is <i>structurally modified</i> in * any way other than via the returned list. (Structural modifications are * those that change the size of this list, or otherwise perturb it in such * a fashion that max_iter in progress may yield incorrect results.) * @param fromIndex low endpoint (inclusive) of the subList. * @param toIndex high endpoint (exclusive) of the subList. * @return a view of the specified range within this list. * @throws IndexOutOfBoundsException for an illegal endpoint index value * (fromIndex < 0 || toIndex > size || fromIndex > toIndex). */ public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } //-------- helpers -------- /** * Expunge stale entries from the list. */ private void expungeStaleEntries() { Reference ref; while((ref=queue.poll())!=null) { for(int i=0; i<size; i++) { if(ref==array[i]) { size--; if(i<size) System.arraycopy(array, i+1, array, i, size-i); array[size] = null; state++; } } } } /** * Get the string representation. */ public String toString() { expungeStaleEntries(); return SUtil.arrayToString(array); // Use toArray(). } //-------- serialization handling -------- protected List serialized_list; /** * Perform special handling on serialization. */ protected Object writeReplace() throws ObjectStreamException { expungeStaleEntries(); // Extract weak references as they are not serializable. this.serialized_list = SCollection.createLinkedList(); for(int i=0; i<size; i++) { Object next = array[i].get(); if(next!=null) serialized_list.add(next); } return this; } /** * Perform special handling on deserialization. */ protected Object readResolve() throws ObjectStreamException { // Restore weak references as they are not serialized. // Use min size 10 of buffer to allow expanding with length*2. this.array = new Reference[Math.max(serialized_list.size(), 10)]; this.queue = new ReferenceQueue(); Iterator it=serialized_list.iterator(); for(int i=0; it.hasNext(); i++) { array[i] = new WeakReference(it.next(), queue); } this.size = serialized_list.size(); this.serialized_list = null; return this; } //-------- main for testing --------- /** * Main method for testing. */ public static void main(String[] args) { int testsize = 10001; List list = new WeakList(); String teststring = "testvalue "; String[] values = createTestvalues(teststring, testsize); System.out.println("Adding odd values (at end of list)."); for(int i=1; i<values.length; i+=2) { list.add(values[i]); } System.out.println("Inserting even values (at correct position of list)."); for(int i=0; i<values.length; i+=2) { list.add(i, values[i]); } System.out.println("Checking positions using toArray()."); Object[] listvalues = list.toArray(); for(int i=0; i<values.length; i++) { if(!values[i].equals(listvalues[i])) throw new RuntimeException("Test failed."); } listvalues = null; System.out.println("Checking positions using toArray(array)."); listvalues = list.toArray(new Object[0]); for(int i=0; i<values.length; i++) { if(!values[i].equals(listvalues[i])) throw new RuntimeException("Test failed."); } listvalues = null; System.out.println("Checking positions using iterator."); Iterator it = list.iterator(); for(int i=0; i<values.length; i++) { if(!values[i].equals(it.next())) throw new RuntimeException("Test failed."); } it = null; System.out.println("Making some values (x%3==0) available to garbage collection."); for(int i=0; i<values.length; i+=3) { values[i] = null; } System.gc(); try { Thread.sleep(200); } catch(InterruptedException e){} System.out.println("Some elements should have been garbage collected."); if(!(list.size()<values.length)) throw new RuntimeException("Test failed."); checkForEmptySlots(list); System.out.println("Checking positions using iterator."); it = list.iterator(); String value = null; for(int i=0; i<values.length && it.hasNext(); i++) { if(values[i]==null) { // Skip removed array items. // Test if value not yet garbage collected (then skip item also). value = (String)it.next(); int index = Integer.parseInt(value.substring(value.indexOf(" ")+1)); if(index==i) { value = null; } continue; } if(value==null) value = (String)it.next(); if(!values[i].equals(value)) throw new RuntimeException("Test failed: "+i+", item="+value+", test="+values[i]); value = null; } it = null; value = null; System.out.println("Removing some values (x%5==0) using iterator."); it = list.iterator(); while(it.hasNext()) { value = (String)it.next(); int index = Integer.parseInt(value.substring(value.indexOf(" ")+1)); if(index%5==0) { it.remove(); } } it = null; value = null; checkForEmptySlots(list); System.out.println("Checking positions using iterator."); it = list.iterator(); value = null; for(int i=0; i<values.length && it.hasNext(); i++) { // Skip removed list items. if(i%5!=0) { if(values[i]==null) { // Skip removed array items. // Test if value not yet garbage collected (then skip item also). value = (String)it.next(); int index = Integer.parseInt(value.substring(value.indexOf(" ")+1)); if(index==i) { value = null; } continue; } if(value==null) value = (String)it.next(); if(!values[i].equals(value)) throw new RuntimeException("Test failed: "+i+", item="+value+", test="+values[i]); value = null; } } it = null; value = null; System.out.println("Removing even values (including garbage collected ones)."); for(int i=0; i<values.length; i+=2) { list.remove(teststring+i); } checkForEmptySlots(list); System.out.println("Checking positions using iterator."); it = list.iterator(); value = null; for(int i=0; i<values.length && it.hasNext(); i++) { // Skip removed list items. if(i%5!=0 && i%2!=0) { if(values[i]==null) { // Skip removed array items. // Test if value not yet garbage collected (then skip item also). value = (String)it.next(); int index = Integer.parseInt(value.substring(value.indexOf(" ")+1)); if(index==i) { value = null; } continue; } if(value==null) value = (String)it.next(); if(!values[i].equals(value)) throw new RuntimeException("Test failed: "+i+", item="+value+", test="+values[i]); value = null; } } it = null; value = null; System.out.println("Removing odd values (including garbage collected ones)."); for(int i=1; i<values.length; i+=2) { list.remove(teststring+i); } checkForEmptySlots(list); System.out.println("Checking if the list is empty now."); if(list.size()>0) throw new RuntimeException("Test failed."); System.out.println("Doing some more tests for garbage collection."); list = new WeakList(); String[] ins = new String[testsize]; for(int i=0; i<ins.length; i++) ins[i] = "ins_"+Math.random(); for(int i=0; i<ins.length; i++) { list.add(ins[i]); list.add("notrem_"+Math.random()); } System.out.println("Checking if some elements have been garbage collected."); if(!(list.size()<ins.length*2)) throw new RuntimeException("Test failed."); checkForEmptySlots(list); System.out.println("Removing remebered entries."); for(int i=0; i<ins.length; i++) list.remove(ins[i]); checkForEmptySlots(list); System.out.println("Test successful."); } /** * Create some string values for testing. */ protected static String[] createTestvalues(String teststring, int size) { String[] values = new String[size]; for(int i=0; i<values.length; i++) { values[i] = teststring+i; } return values; } /** * Check for empty slots in list. */ protected static void checkForEmptySlots(List list) { Object[] listvalues; System.out.println("Checking for empty slots in toArray()."); listvalues = list.toArray(); for(int i=0; i<listvalues.length; i++) { if(listvalues[i]==null) throw new RuntimeException("Test failed."); } System.out.println("Checking for empty slots in toArray(array)."); listvalues = list.toArray(new Object[0]); for(int i=0; i<listvalues.length; i++) { if(listvalues[i]==null) throw new RuntimeException("Test failed."); } } }