/** * Copyright (c) 2003, Spellcast development team * http://spellcast.dev.java.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "Spellcast development team" nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.java.dev.spellcast.utilities; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import javax.swing.AbstractListModel; import javax.swing.ComboBoxModel; import javax.swing.ListModel; import javax.swing.MutableComboBoxModel; /** * Lockable aspects of this class have been removed due to incompatibilities with Swing; synchronization between two * threads when one is the Swing thread turns out to have a lot of problems. It retains its original name for * convenience purposes only. */ public class LockableListModel<E> extends AbstractListModel implements Cloneable, List<E>, ListModel, ComboBoxModel, MutableComboBoxModel { private static final ListElementFilter NO_FILTER = new ShowEverythingFilter(); private boolean actionListenerFired = false; protected ArrayList<E> actualElements; private ArrayList<E> visibleElements; private ArrayList<WeakReference<LockableListModel<E>>> mirrorList; E selectedValue; protected ListElementFilter currentFilter; /** * Constructs a new <code>LockableListModel</code>. */ public LockableListModel() { this.actualElements = new ArrayList<E>(); this.visibleElements = new ArrayList<E>(); this.mirrorList = new ArrayList<WeakReference<LockableListModel<E>>>(); this.selectedValue = null; this.currentFilter = LockableListModel.NO_FILTER; } public LockableListModel( final Collection<E> c ) { this(); this.addAll( c ); } private LockableListModel( final LockableListModel<E> l ) { this( l, LockableListModel.NO_FILTER ); } private LockableListModel( final LockableListModel<E> l, final ListElementFilter f ) { synchronized ( l.actualElements ) { this.actualElements = l.actualElements; this.visibleElements = new ArrayList<E>(); this.selectedValue = null; this.currentFilter = f == null ? LockableListModel.NO_FILTER : f; this.mirrorList = new ArrayList<WeakReference<LockableListModel<E>>>(); l.mirrorList.add( new WeakReference<LockableListModel<E>>( this ) ); if ( f == LockableListModel.NO_FILTER ) { this.visibleElements.addAll( this.actualElements ); } else if ( f == l.currentFilter ) { this.visibleElements.addAll( l.visibleElements ); } else { this.updateFilter( false ); } } } private LockableListModel<E> getNextMirror( final Iterator<WeakReference<LockableListModel<E>>> it ) { while ( it.hasNext() ) { WeakReference<LockableListModel<E>> ref = it.next(); if ( ref.get() != null ) { return ref.get(); } it.remove(); } return null; } public void sort() { this.sort( null ); } public void sort( final Comparator c ) { synchronized ( this.actualElements ) { Collections.sort( this.actualElements, c ); Collections.sort( this.visibleElements, c ); this.fireContentsChanged( this, 0, this.visibleElements.size() - 1 ); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { break; } Collections.sort( mirror.visibleElements, c ); mirror.fireContentsChanged( this, 0, mirror.visibleElements.size() - 1 ); } } } public void touch() { synchronized ( this.actualElements ) { this.fireContentsChanged( this, 0, this.visibleElements.size() - 1 ); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { break; } mirror.fireContentsChanged( this, 0, mirror.visibleElements.size() - 1 ); } } } @Override public void fireContentsChanged( final Object source, final int index0, final int index1 ) { if ( index0 >= 0 && index1 < 0 ) { return; } if ( this.actionListenerFired || this.listenerList.getListenerCount() == 0 ) { return; } this.actionListenerFired = true; super.fireContentsChanged( source, index0, index1 ); this.actionListenerFired = false; } @Override public void fireIntervalAdded( final Object source, final int index0, final int index1 ) { if ( this.actionListenerFired || this.listenerList.getListenerCount() == 0 ) { return; } this.actionListenerFired = true; super.fireIntervalAdded( source, index0, index1 ); this.actionListenerFired = false; } @Override public void fireIntervalRemoved( final Object source, final int index0, final int index1 ) { if ( this.actionListenerFired || this.listenerList.getListenerCount() == 0 ) { return; } this.actionListenerFired = true; super.fireIntervalRemoved( source, index0, index1 ); this.actionListenerFired = false; } /** * Please refer to {@link java.util.List#add(int,Object)} for more information regarding this function. */ public void add( final int index, final E element ) { if ( element == null ) { return; } synchronized ( this.actualElements ) { if ( this.currentFilter != LockableListModel.NO_FILTER || !this.mirrorList.isEmpty() ) { this.updateFilter( false ); } this.actualElements.add( index, element ); this.addVisibleElement( index, element ); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { return; } mirror.addVisibleElement(index, element ); } } } private void addVisibleElement( final int index, final E element ) { if ( !this.currentFilter.isVisible( element ) ) { return; } int visibleIndex = this.computeVisibleIndex( index ); this.visibleElements.add( visibleIndex, element ); this.fireIntervalAdded( this, visibleIndex, visibleIndex ); } /** * Please refer to {@link java.util.List#add(Object)} for more information regarding this function. */ public boolean add( final E o ) { if ( o == null ) { return false; } synchronized ( this.actualElements ) { int originalSize = this.actualElements.size(); this.add( originalSize, o ); return originalSize != this.actualElements.size(); } } /** * Please refer to {@link java.util.List#addAll(Collection)} for more information regarding this function. */ public boolean addAll( final Collection<? extends E> c ) { synchronized ( this.actualElements ) { return this.addAll( this.actualElements.size(), c ); } } /** * Please refer to {@link java.util.List#addAll(int,Collection)} for more information regarding this function. */ public boolean addAll( final int index, final Collection<? extends E> c ) { synchronized ( this.actualElements ) { boolean result = this.actualElements.addAll( index, c ); this.updateFilter( false ); return result; } } /** * Please refer to {@link java.util.List#clear()} for more information regarding this function. */ public void clear() { synchronized ( this.actualElements ) { this.actualElements.clear(); this.clearVisibleElements(); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { return; } mirror.clearVisibleElements(); } } } private void clearVisibleElements() { int originalSize = this.visibleElements.size(); if ( originalSize == 0 ) { return; } this.visibleElements.clear(); this.fireIntervalRemoved( this, 0, originalSize - 1 ); } /** * Please refer to {@link java.util.List#contains(Object)} for more information regarding this function. */ public boolean contains( final Object o ) { return o == null ? false : this.actualElements.contains( o ); } /** * Please refer to {@link java.util.List#containsAll(Collection)} for more information regarding this function. */ public boolean containsAll( final Collection<?> c ) { return this.actualElements.containsAll( c ); } /** * Please refer to {@link java.util.List#equals(Object)} for more information regarding this function. */ @Override public boolean equals( final Object o ) { return o instanceof LockableListModel ? this == o : this.actualElements.equals( o ); } /** * Please refer to {@link java.util.List#get(int)} for more information regarding this function. */ public E get( final int index ) { if ( index < 0 || index >= this.actualElements.size() ) { return null; } return this.actualElements.get( index ); } /** * Please refer to {@link java.util.List#hashCode()} for more information regarding this function. */ @Override public int hashCode() { return this.actualElements.hashCode(); } /** * Please refer to {@link java.util.List#indexOf(Object)} for more information regarding this function. */ public int indexOf( final Object o ) { return o == null ? -1 : this.actualElements.indexOf( o ); } /** * Please refer to {@link java.util.List#isEmpty()} for more information regarding this function. */ public boolean isEmpty() { return this.actualElements.isEmpty(); } /** * Internal class used to handle iterators. This is done to ensure that all applicable interface structures are * notified whenever changes are made to the list elements. */ private class ListModelIterator implements ListIterator<E> { private int nextIndex, previousIndex; private boolean isIncrementing; public ListModelIterator() { this( 0 ); } public ListModelIterator( final int initialIndex ) { this.nextIndex = 0; this.previousIndex = -1; this.isIncrementing = true; } public boolean hasPrevious() { return this.previousIndex > 0; } public boolean hasNext() { return this.nextIndex < LockableListModel.this.actualElements.size(); } public E next() { this.isIncrementing = true; E nextObject = LockableListModel.this.get( this.nextIndex ); ++this.nextIndex; ++this.previousIndex; return nextObject; } public E previous() { this.isIncrementing = false; E previousObject = LockableListModel.this.get( this.previousIndex ); --this.nextIndex; --this.previousIndex; return previousObject; } public int nextIndex() { return this.nextIndex; } public int previousIndex() { return this.previousIndex; } public void add( final E o ) { LockableListModel.this.add( this.nextIndex, o ); ++this.nextIndex; ++this.previousIndex; } public void remove() { if ( this.isIncrementing ) { --this.nextIndex; --this.previousIndex; LockableListModel.this.remove( this.nextIndex ); } else { ++this.nextIndex; ++this.previousIndex; LockableListModel.this.remove( this.previousIndex ); } } public void set( final E o ) { LockableListModel.this.set( this.isIncrementing ? this.nextIndex - 1 : this.previousIndex + 1, o ); } } /** * Please refer to {@link java.util.List#iterator()} for more information regarding this function. */ public Iterator<E> iterator() { return new ListModelIterator(); } /** * Please refer to {@link java.util.Vector#lastElement()} for more information regarding this function. */ public E lastElement() { return this.actualElements.isEmpty() ? null : this.actualElements.get( this.actualElements.size() - 1 ); } /** * Please refer to {@link java.util.List#lastIndexOf(Object)} for more information regarding this function. */ public int lastIndexOf( final Object o ) { return o == null ? -1 : this.indexOf( o ); } /** * Please refer to {@link java.util.List#listIterator()} for more information regarding this function. */ public ListIterator<E> listIterator() { return new ListModelIterator(); } /** * Please refer to {@link java.util.List#listIterator(int)} for more information regarding this function. */ public ListIterator<E> listIterator( final int index ) { return new ListModelIterator( index ); } /** * Please refer to {@link java.util.List#remove(int)} for more information regarding this function. */ public E remove( final int index ) { synchronized ( this.actualElements ) { if ( index < 0 || index >= this.actualElements.size() ) { return null; } if ( this.currentFilter != LockableListModel.NO_FILTER || !this.mirrorList.isEmpty() ) { this.updateFilter( false ); } E originalValue = this.actualElements.get( index ); this.actualElements.remove( index ); this.removeVisibleElement( index, originalValue ); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { return originalValue; } mirror.removeVisibleElement( index, originalValue ); } return originalValue; } } private void removeVisibleElement( final int index, final E element ) { if ( !this.currentFilter.isVisible( element ) ) { return; } int visibleIndex = this.computeVisibleIndex( index ); this.visibleElements.remove( visibleIndex ); this.fireIntervalRemoved( this, visibleIndex, visibleIndex ); } /** * Please refer to {@link java.util.List#remove(Object)} for more information regarding this function. */ public boolean remove( final Object o ) { synchronized ( this.actualElements ) { return o == null ? false : this.remove( this.indexOf( o ) ) != null; } } /** * Please refer to {@link java.util.List#removeAll(Collection)} for more information regarding this function. */ public boolean removeAll( final Collection<?> c ) { synchronized ( this.actualElements ) { int originalSize = this.actualElements.size(); Iterator it = c.iterator(); while ( it.hasNext() ) { Object current = it.next(); if ( current != null ) { this.remove( current ); } } return originalSize != this.actualElements.size(); } } /** * Please refer to {@link java.util.List#retainAll(Collection)} for more information regarding this function. */ public boolean retainAll( final Collection<?> c ) { synchronized ( this.actualElements ) { int originalSize = this.actualElements.size(); Iterator it = this.iterator(); while ( it.hasNext() ) { if ( !c.contains( it.next() ) ) { it.remove(); } } return originalSize != this.actualElements.size(); } } /** * Please refer to {@link java.util.List#set(int,E)} for more information regarding this function. */ public E set( final int index, final E element ) { synchronized ( this.actualElements ) { if ( element == null ) { return null; } if ( this.currentFilter != LockableListModel.NO_FILTER || !this.mirrorList.isEmpty() ) { this.updateFilter( false ); } E originalValue = this.actualElements.set( index, element ); this.setVisibleElement( index, element, originalValue ); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { return originalValue; } mirror.setVisibleElement( index, element, originalValue ); } return originalValue; } } private void setVisibleElement( final int index, final E element, final E originalValue ) { int visibleIndex = this.computeVisibleIndex( index ); if ( originalValue != null && this.currentFilter.isVisible( originalValue ) ) { if ( !this.currentFilter.isVisible( element ) ) { this.visibleElements.remove( visibleIndex ); this.fireIntervalRemoved( this, visibleIndex, visibleIndex ); } else if ( visibleIndex == this.visibleElements.size() ) { this.visibleElements.add( visibleIndex, element ); this.fireIntervalAdded( this, visibleIndex, visibleIndex ); } else { this.visibleElements.set( visibleIndex, element ); this.fireContentsChanged( this, visibleIndex, visibleIndex ); } } else if ( this.currentFilter.isVisible( element ) ) { this.visibleElements.add( visibleIndex, element ); this.fireIntervalAdded( this, visibleIndex, visibleIndex ); } } /** * Please refer to {@link java.util.List#size()} for more information regarding this function. */ public int size() { return this.actualElements.size(); } /** * Please refer to {@link java.util.List#subList(int,int)} for more information regarding this function. */ public List<E> subList( final int fromIndex, final int toIndex ) { return this.actualElements.subList( fromIndex, toIndex ); } /** * Please refer to {@link java.util.List#toArray()} for more information regarding this function. */ public Object[] toArray() { return this.actualElements.toArray(); } /** * Please refer to {@link java.util.List#toArray(Object[])} for more information regarding this function. */ public <T> T[] toArray( final T[] a ) { return this.actualElements.toArray( a ); } public void updateFilter( final boolean refresh ) { synchronized ( this.actualElements ) { this.updateSingleFilter( refresh ); Iterator<WeakReference<LockableListModel<E>>> it = this.mirrorList.iterator(); while ( it.hasNext() ) { LockableListModel<E> mirror = this.getNextMirror( it ); if ( mirror == null ) { return; } mirror.updateSingleFilter( refresh ); } } } private void updateSingleFilter( final boolean refresh ) { int visibleIndex = 0; int low = -1; int high = -1; boolean adding = true; for ( int i = 0; i < this.actualElements.size(); ++i ) { E element = this.actualElements.get( i ); if ( this.currentFilter.isVisible( element ) ) { if ( visibleIndex == this.visibleElements.size() || this.visibleElements.get( visibleIndex ) != element ) { if ( low == -1 ) { low = high = visibleIndex; adding = true; } else if ( !adding ) { this.fireIntervalRemoved( this, low, high ); low = high = visibleIndex; adding = true; } else { high += 1; } this.visibleElements.add( visibleIndex, element ); } ++visibleIndex; } else { if ( visibleIndex < this.visibleElements.size() && this.visibleElements.get( visibleIndex ) == element ) { if ( low == -1 ) { low = high = visibleIndex; adding = false; } else if ( adding ) { this.fireIntervalAdded( this, low, high ); low = high = visibleIndex; adding = false; } else { high += 1; } this.visibleElements.remove( visibleIndex ); } } } if ( low != -1 ) { if ( adding ) { this.fireIntervalAdded( this, low, high ); } else { this.fireIntervalRemoved( this, low, high ); } } if ( refresh ) { this.fireContentsChanged( this, 0, this.visibleElements.size() - 1 ); } } private int computeVisibleIndex( final int actualIndex ) { if ( currentFilter == NO_FILTER ) { return actualIndex; } int visibleIndex = 0; synchronized ( this.actualElements ) { for ( int i = 0; i < actualIndex && visibleIndex < this.visibleElements.size(); ++i ) { if ( this.actualElements.get( i ) == this.visibleElements.get( visibleIndex ) ) { ++visibleIndex; } } } return visibleIndex; } /** * Filters the current list using the provided filter. */ public void setFilter( final ListElementFilter newFilter ) { this.currentFilter = newFilter == null ? LockableListModel.NO_FILTER : newFilter; } public int getIndexOf( final E o ) { return this.visibleElements.indexOf( o ); } /** * Please refer to {@link javax.swing.ListModel#getElementAt(int)} for more information regarding this function. */ public E getElementAt( final int index ) { return index < 0 || index >= this.visibleElements.size() ? null : this.visibleElements.get( index ); } /** * Please refer to {@link javax.swing.ListModel#getSize()} for more information regarding this function. */ public int getSize() { return this.visibleElements.size(); } /** * Please refer to {@link javax.swing.ComboBoxModel#getSelectedItem()} for more information regarding this function. */ public Object getSelectedItem() { return this.contains( this.selectedValue ) ? this.selectedValue : null; } /** * Returns the index of the currently selected item in this <code>LockableListModel</code>. This is used * primarily in cloning, to ensure that the same indices are being selected; however it may also be used to report * the index of the currently selected item in testing a new object which uses a list model. * * @return the index of the currently selected item */ public int getSelectedIndex() { return this.visibleElements.indexOf( this.selectedValue ); } /** * Please refer to {@link javax.swing.ComboBoxModel#setSelectedItem(Object)} for more information regarding this * function. */ public void setSelectedItem( final Object o ) { this.selectedValue = (E)o; this.fireContentsChanged( this, -1, -1 ); } /** * Sets the given index in this <code>LockableListModel</code> as the currently selected item. This is meant to be * a complement to setSelectedItem(), and also functions to help in the cloning process. */ public void setSelectedIndex( final int index ) { this.setSelectedItem( this.getElementAt( index ) ); } /** * Please refer to {@link javax.swing.MutableComboBoxModel#addElement(Object)} for more information regarding this * function. */ public void addElement( final Object element ) { this.add( (E)element ); } /** * Please refer to {@link javax.swing.MutableComboBoxModel#insertElementAt(Object,int)} for more information * regarding this function. */ public void insertElementAt( final Object element, final int index ) { this.add( (E)element ); } /** * Please refer to {@link javax.swing.MutableComboBoxModel#removeElement(Object)} for more information regarding * this function. */ public void removeElement( final Object element ) { this.remove( element ); } /** * Please refer to {@link javax.swing.MutableComboBoxModel#removeElementAt(int)} for more information regarding this * function. */ public void removeElementAt( final int index ) { this.remove( this.visibleElements.get( index ) ); } /** * Returns a deep copy of the data associated with this <code>LockableListModel</code>. Note that any subclasses * must override this method in order to ensure that the object can be cast appropriately; note also that the * listeners are not inherited by the clone copy, since this violates the principle of independence. If they are * required, then the class using this model should add them using the functions provided in the * <code>ListModel</code> interface. Note also that if an element added to the list does not implement the * <code>Cloneable</code> interface, or implements it by causing it to fail by default, this method will not fail; * it will add a reference to the object, in effect creating a shallow copy of it. Thus, retrieving an object using * get() and modifying a field will result in both <code>LockableListModel</code> objects changing, in the same * way retrieving an element from a cloned <code>ArrayList</code> will. * * @return a deep copy (exempting listeners) of this <code>LockableListModel</code>. */ @Override public Object clone() { LockableListModel cloneCopy; try { cloneCopy = (LockableListModel) super.clone(); } catch ( CloneNotSupportedException e ) { // Because none of the super classes support clone(), this means // that this method is overriding the one found in Object. Thus, // this exception should never be thrown, unless one of the super // classes is re-written to throw the exception by default. throw new RuntimeException( "AbstractListModel or one of its superclasses was rewritten to throw CloneNotSupportedException by default, call to clone() was unsuccessful" ); } cloneCopy.listenerList = new javax.swing.event.EventListenerList(); cloneCopy.actualElements = new ArrayList(); cloneCopy.actualElements.addAll( this.actualElements ); cloneCopy.visibleElements = new ArrayList(); cloneCopy.visibleElements.addAll( this.visibleElements ); cloneCopy.mirrorList = new ArrayList<WeakReference<LockableListModel<E>>>(); cloneCopy.currentFilter = this.currentFilter; cloneCopy.selectedValue = null; return cloneCopy; } /** * Special class which allows you to filter elements inside of this list model. */ public static interface ListElementFilter { public boolean isVisible( Object element ); } private static class ShowEverythingFilter implements ListElementFilter { public boolean isVisible( final Object element ) { return true; } } /** * Returns a mirror image of this <code>LockableListModel</code>. In essence, the object returned will be a clone * of the original object. However, it has the additional feature of listening for changes to the * <em>underlying data</em> of this <code>LockableListModel</code>. Note that this means any changes in * selected indices will not be mirrored in the mirror image. Note that because this function modifies the listeners * for this class, an asynchronous version is not available. * * @return a mirror image of this <code>LockableListModel</code> */ public LockableListModel<E> getMirrorImage() { return new LockableListModel<E>( this ); } /** * Returns a mirror image of this <code>LockableListModel</code>. In essence, the object returned will be a clone * of the original object. However, it has the additional feature of listening for changes to the * <em>underlying data</em> of this <code>LockableListModel</code>. Note that this means any changes in * selected indices will not be mirrored in the mirror image. Note that because this function modifies the listeners * for this class, an asynchronous version is not available. * * @return a mirror image of this <code>LockableListModel</code> */ public LockableListModel<E> getMirrorImage( final ListElementFilter filter ) { return new LockableListModel<E>( this, filter ); } }