/** * 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.util.Collection; /** * An extension of the {@link net.java.dev.spellcast.utilities.LockableListModel} which maintains elements in ascending * order, where elements can only be added and replaced if they do not disturb the sorted property of the * <code>List</code>. */ public class SortedListModel<E> extends LockableListModel<E> { /** * Constructs a new <code>SortedListModel</code>. In essence, all this class does is call the constructor for the * <code>LockableListModel</code> with the class object associated with <code>java.lang.Comparable</code>. */ public SortedListModel() { } /** * Please refer to {@link java.util.List#add(int,Object)} for more information regarding this function. Note that if * the position is invalid (ie: it does not result in a sorted property), the element will be successfully added, * but to a different position. */ @Override public void add( final int index, final E element ) { if ( element == null ) { return; } this.add( element ); } /** * Please refer to {@link java.util.List#add(Object)} for more information regarding this function. */ @Override public boolean add( final E o ) { if ( o == null ) { return false; } try { super.add( this.insertionIndexOf( 0, this.size() - 1, (Comparable) o ), o ); return true; } catch ( IllegalArgumentException e1 ) { return false; } catch ( ClassCastException e2 ) { return false; } } /** * Please refer to {@link java.util.List#addAll(int,Collection)} for more information regarding this function. */ @Override public boolean addAll( final int index, final Collection<? extends E> c ) { synchronized (this.actualElements ) { if ( !super.addAll( index, c ) ) { return false; } this.sort(); } return true; } /** * Please refer to {@link java.util.List#indexOf(Object)} for more information regarding this function. */ @Override public int indexOf( final Object o ) { return o == null ? -1 : this.normalIndexOf( 0, this.size() - 1, (Comparable) o ); } /** * Please refer to {@link java.util.List#contains(Object)} for more information regarding this function. */ @Override public boolean contains( final Object o ) { return this.indexOf( o ) != -1; } /** * A helper function which calculates the index of an element using binary search. In most cases, the difference is * minimal, since most <code>ListModel</code> objects are fairly small. However, in the event that there are * multiple <code>SortedListModel</code> objects of respectable size, having good performance is ideal. */ private int normalIndexOf( int beginIndex, int endIndex, final Comparable element ) { int compareResult; while ( true ) { if ( beginIndex == endIndex ) { compareResult = this.compare( element, (Comparable) this.get( beginIndex ) ); return compareResult == 0 ? beginIndex : -1; } if ( beginIndex > endIndex ) { return -1; } // calculate the halfway point and compare the element with the // element located at the halfway point - note that in locating // the last index of, the value is rounded up to avoid an infinite // recursive loop int halfwayIndex = beginIndex + endIndex >> 1; compareResult = this.compare( (Comparable) this.get( halfwayIndex ), element ); // if the element in the middle is larger than the element being checked, // then it is known that the element is smaller than the middle element, // so it must preceed the middle element if ( compareResult > 0 ) { endIndex = halfwayIndex - 1; continue; } // if the element in the middle is smaller than the element being checked, // then it is known that the element is larger than the middle element, so // it must succeed the middle element if ( compareResult < 0 ) { beginIndex = halfwayIndex + 1; continue; } // if the element in the middle is equal to the element being checked, // then it is known that you have located at least one occurrence of the // object; because duplicates are not allowed, return the halfway point return halfwayIndex; } } private int insertionIndexOf( int beginIndex, int endIndex, final Comparable element ) { int compareResult; while ( true ) { if ( beginIndex == endIndex ) { compareResult = this.compare( element, (Comparable) this.get( beginIndex ) ); return compareResult < 0 ? beginIndex : beginIndex + 1; } if ( beginIndex > endIndex ) { return beginIndex; } // calculate the halfway point and compare the element with the // element located at the halfway point - note that in locating // the last index of, the value is rounded up to avoid an infinite // recursive loop int halfwayIndex = beginIndex + endIndex >> 1; compareResult = this.compare( (Comparable) this.get( halfwayIndex ), element ); // if the element in the middle is larger than the element being checked, // then it is known that the element is smaller than the middle element, // so it must preceed the middle element if ( compareResult > 0 ) { endIndex = halfwayIndex - 1; continue; } // if the element in the middle is smaller than the element being checked, // then it is known that the element is larger than the middle element, so // it must succeed the middle element if ( compareResult < 0 ) { beginIndex = halfwayIndex + 1; continue; } // if the element in the middle is equal to the element being checked, // then it is known that you have located at least one occurrence of the // object; because duplicates are not allowed, return the halfway point return halfwayIndex + 1; } } private int compare( final Comparable left, final Comparable right ) { return left instanceof String || right instanceof String ? left.toString().compareToIgnoreCase( right.toString() ) : left.compareTo( right ); } }