Mega Code Archive

 
Categories / Java / Development Class
 

An extension of ArrayList that provides some handy utilities for working with JavaBeans

/*  * BeanArrayList.java  *  * Created on May 21, 2004, 7:23 PM  *  * Copyright (C) 2004, 2005  Robert Cooper, Temple of the Screaming Penguin  *  * This library is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public  * License as published by the Free Software Foundation; either  * version 2.1 of the License, or (at your option) any later version.  *  * This library is distributed in the hope that it will be useful,  * but WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  * Lesser General Public License for more details.  *  * You should have received a copy of the GNU Lesser General Public  * License along with this library; if not, write to the Free Software  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */ import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyDescriptor; import java.lang.Comparable; import java.lang.reflect.*; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.StringTokenizer; import java.util.ArrayList; /** This is an extension of ArrayList that provides some handy utilities for working with JavaBeans.  * Includes basic statistical information, page chunking and storing and can also be used as a manager for  * indexed properies and supports PropertyChangeEvents.  *  * @version $Rev: 79 $  * @author  <a href="cooper@screaming-penguin.com">Robert Cooper</a>  */ public class BeanArrayList<T> extends ArrayList<T> {     /**      * DOCUMENT ME!      */     Collection source;     /**      * DOCUMENT ME!      */     private PropertyChangeSupport changes;     /**      * DOCUMENT ME!      */     private String indexPropertyName;     /**      * DOCUMENT ME!      */     private int nextChunk = -1;     /**      * DOCUMENT ME!      */     private int numberOfChunks = 1;     /**      * DOCUMENT ME!      */     private int previousChunk = -1;     /** No Args Contstructor */     public BeanArrayList() {         super();     }     /**      * This contructs a new BeanArrayList with PropertyChangeEvent support.      * @param indexPropertyName the property name of the parent object to fire events for      * @param changeOwner the owner object that change events should "come from"      */     public BeanArrayList(String indexPropertyName,Object changeOwner) {         this();         this.indexPropertyName = indexPropertyName;         this.changes = new PropertyChangeSupport(changeOwner);     }     /**      * Creates a new instance of BeanArrayList prepopulating off a      * collection, limited to only the desired chunk.      * @param source Collection to read from      * @param chunkSize int number of object to return.      * @param currentChunk int value of the chunk to get (zero index)      */     public BeanArrayList(int chunkSize,int currentChunk,Collection<T> source) {         super();         this.source = source;         Iterator<T> itr = source.iterator();         if(source.size() > 0) {             this.numberOfChunks = source.size() / chunkSize;             if((source.size() % chunkSize) > 0) {                 this.numberOfChunks++;             }             //spin             for(int i = 0; i < (chunkSize * currentChunk); i++) {                 if(!itr.hasNext()) {                     continue;                 }                 itr.next();             }             for(int i = 0; i < chunkSize; i++) {                 if(!itr.hasNext()) {                     continue;                 }                 this.add(itr.next());             }             if(currentChunk != 0) {                 previousChunk = currentChunk - 1;             }             if(source.size() > ((currentChunk + 1) * chunkSize)) {                 nextChunk = currentChunk + 1;             }         }     }     /**      * Creates a new instance of BeanArrayList prepopulating off a      * collection, limited to only the desired chunk, and includes      * PropertyChangeEvent support.      * @param indexPropertyName the property name of the parent object to fire events for      * @param changeOwner the object change events should come from      * @param chunkSize number of objects to return      * @param currentChunk int value of the chunk to get (zero index)      * @param source source Collection to read from.      */     public BeanArrayList(String indexPropertyName,Object changeOwner,int chunkSize,int currentChunk,Collection<T> source) {         this(chunkSize,currentChunk,source);         this.indexPropertyName = indexPropertyName;         this.changes = new PropertyChangeSupport(changeOwner);     }     /** Creates a new instance of BeanArrayList      * @param source Collection containing the initial values.      */     public BeanArrayList(Collection<T> source) {         this(source.size(),0,source);     }     /**      * This contructs a new BeanArrayList with PropertyChangeEvent support.      * @param indexPropertyName the property name of the parent object to fire events for      * @param changeOwner the object change events should come from      * @param source Collection to prepopulate from.      */     public BeanArrayList(String indexPropertyName,Object changeOwner,Collection<T> source) {         this(source);         this.indexPropertyName = indexPropertyName;         this.changes = new PropertyChangeSupport(changeOwner);     }     /**      * Gets a chunk of this ArrayList.      * @param chunkSize int number of object to return.      * @param currentChunk int value of the chunk to get (zero index)      * @return new BeanArrayList representing the chunk requested.      */     public BeanArrayList getChunk(int chunkSize,int currentChunk) {         return new BeanArrayList(chunkSize,currentChunk,this);     }     /**      * Overrides the parent to support PropertyChangeEvents      * @param obj new object value      * @param index index position to place the object      */     public void setElementAt(T obj,int index) {         T old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.get(index);         }         super.set(index, obj);         if(old != null) {             changes.fireIndexedPropertyChange(this.indexPropertyName,index,old,obj);         }     }     /**      * Filters a property using the Comparable.compareTo() on the porperty to      * test for a range      * @param propertyName property to filter on      * @param inclusive include the values of the range limiters      * @param fromValue low range value      * @param toValue high range value      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      * @return new BeanArrayList filtered on the range      */     public BeanArrayList<T> getFiltered(String propertyName,boolean inclusive,Comparable fromValue,Comparable toValue) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         HashMap cache = new HashMap();         String currentClass = "";         PropertyDescriptor pd = null;         BeanArrayList<T> results = new BeanArrayList<T>();         for(int i = 0; i < this.size(); i++) {             T o = this.get(i);             if(!currentClass.equals(o.getClass().getName())) {                 pd = (PropertyDescriptor)cache.get(o.getClass().getName());                 if(pd == null) {                     PropertyDescriptor[] pds = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();                     boolean foundProperty = false;                     for(int pdi = 0; (pdi < pds.length)&&!foundProperty;                             pdi++) {                         if(pds[pdi].getName().equals(propertyName)) {                             pd = pds[pdi];                             cache.put(o.getClass().getName(),pd);                             foundProperty = true;                         }                     }                 }             }             Comparable value = (Comparable)pd.getReadMethod().invoke(o);             if((value.compareTo(fromValue) > 0)&&(value.compareTo(toValue) < 0)) {                 results.add(o);             } else if(inclusive&&((value.compareTo(fromValue) == 0)||(value.compareTo(toValue) == 0))) {                 results.add(o);             }         }         return results;     }     /**      * This method does a string match on values of a property.      * @param propertyName String value containing the name of the property to match.      * @param match Value to search for. This is a case-insensitive value that takes % as a multiple character wildcard value.      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      * @return a new BeanArrayList filtered on the specified property      */     public BeanArrayList<T> getFilteredStringMatch(String propertyName,String match) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         HashMap cache = new HashMap();         String currentClass = "";         PropertyDescriptor pd = null;         BeanArrayList<T> results = new BeanArrayList<T>();         for(int i = 0; i < this.size(); i++) {             T o = this.get(i);             if(!currentClass.equals(o.getClass().getName())) {                 pd = (PropertyDescriptor)cache.get(o.getClass().getName());                 if(pd == null) {                     PropertyDescriptor[] pds = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();                     boolean foundProperty = false;                     for(int pdi = 0; (pdi < pds.length)&&!foundProperty;                             pdi++) {                         if(pds[pdi].getName().equals(propertyName)) {                             pd = pds[pdi];                             cache.put(o.getClass().getName(),pd);                             foundProperty = true;                         }                     }                 }             }             String value = pd.getReadMethod().invoke(o).toString().toLowerCase();             StringTokenizer st = new StringTokenizer(match.toLowerCase(),"%");             boolean isMatch = true;             int matchIndex = 0;             while(st.hasMoreTokens()&&isMatch) {                 String tk = st.nextToken();                 if(value.indexOf(tk,matchIndex) == -1) {                     isMatch = false;                 } else {                     matchIndex = value.indexOf(tk,matchIndex) + tk.length();                 }             }             if(isMatch) {                 results.add(o);             }         }         return results;     }     /**      * This method returns the average value of a numerical property.      * @param propertyName String value of the property name to calculate.      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      * @return Average of property values.      */     public Number getMeanOfProperty(String propertyName) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         double mean = this.getSumOfProperty(propertyName).doubleValue() / this.size();         return new Double(mean);     }     /**      * This method returns the index of the bean with the median value      * on the specified property.      *      * <p>If there is an odd number of items in the dataset, the one below      * the 50% mark will be returned. The true mathmatical mean, therefore      * would be:      * <code>      * (beanArrayList.get(x).getProperty() + beanArrayList.get(x+1).getProperty() )/2      * </code></p>      * @param propertyName String value of the property name to calculate.      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      * @return int value of the median index of the ArrayList      */     public int getMedianIndex(String propertyName) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         BeanArrayList bv = new BeanArrayList(this.size(),0,this);         bv.sortOnProperty(propertyName);         int orderedIndex = bv.size() / 2;         Object o = bv.get(orderedIndex);         return this.indexOf(o);     }     /**      * This method returns the index of an object representing the      * mode value of a property name.      * @param propertyName String value of the property name to calculate.      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      * @return int value of the mode index      */     public int getModeIndex(String propertyName) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         int index = -1;         int max = 0;         int count = 0;         Object o = null;         Object hold = null;         HashMap cache = new HashMap();         String currentClass = "";         PropertyDescriptor pd = null;         BeanArrayList bv = new BeanArrayList(this.size(),0,this);         bv.sortOnProperty(propertyName);         for(int i = 0; i < bv.size(); i++) {             if(!currentClass.equals(bv.get(i).getClass().getName())) {                 pd = (PropertyDescriptor)cache.get(bv.get(i).getClass().getName());                 if(pd == null) {                     PropertyDescriptor[] pds = Introspector.getBeanInfo(bv.get(i).getClass()).getPropertyDescriptors();                     boolean foundProperty = false;                     for(int pdi = 0; (pdi < pds.length)&&!foundProperty;                             pdi++) {                         if(pds[pdi].getName().equals(propertyName)) {                             pd = pds[pdi];                             cache.put(bv.get(i).getClass().getName(),pd);                             foundProperty = true;                         }                     }                 }             }             if(hold == null) {                 hold = pd.getReadMethod().invoke(bv.get(i));             } else {                 o = pd.getReadMethod().invoke(bv.get(i));                 if((o != null)&&o.equals(hold)) {                     count++;                     if(count > max) {                         max = count;                         index = this.indexOf(bv.get(i));                     }                 } else {                     count = 1;                 }                 hold = o;             }         }         return index;     }     /** Returns -1 or the the index of the next chunk after the current      * ArrayList.      * @return -1 or the the index of the next chunk after the current ArrayList      */     public int getNextChunk() {         return this.nextChunk;     }     /**      * returns the number of chunks in the ArrayList      * @return int value number of chunks      */     public int getNumberOfChunks() {         return this.numberOfChunks;     }     /** Returns -1 or the the index of the previous chunk before the      * current ArrayList.      * @return -1 or the the index of the previous chunk before the      * current ArrayList      */     public int getPreviousChunk() {         return this.previousChunk;     }     /**      * This method returns the sum of all values of a numerical      * property.      * @param propertyName String value of the property name to calculate.      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      * @return sum of a numerical property      */     public Number getSumOfProperty(String propertyName) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         double d = 0.0;         String currentClass = "";         PropertyDescriptor pd = null;         for(int i = 0; i < this.size(); i++) {             T o = this.get(i);             if(!currentClass.equals(o.getClass().getName())) {                 PropertyDescriptor[] pds = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();                 boolean foundProperty = false;                 for(int pdi = 0; (pdi < pds.length)&&!foundProperty; pdi++) {                     if(pds[pdi].getName().equals(propertyName)) {                         pd = pds[pdi];                         foundProperty = true;                     }                 }             }             if(o != null) {                 Number n = (Number)pd.getReadMethod().invoke(o);                 d += n.doubleValue();             }         }         return new Double(d);     }     /**      * Inserts the specified element at the specified position in this ArrayList.      * Shifts the element currently at that position (if any) and any      * subsequent elements to the right (adds one to their indices).      *      * @since 1.2      * @param index index at which the specified element is to be inserted.      * @param element element to be inserted.      */     public void add(int index,T element) {         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         super.add(index,element);         if(old != null) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }     }     /**      * Appends the specified element to the end of this ArrayList.      *      * @param o element to be appended to this ArrayList.      * @return true (as per the general contract of Collection.add).      * @since 1.2      */     public boolean add(T o) {         boolean retValue;         retValue = super.add(o);         if((this.indexPropertyName != null)&&(this.changes != null)) {             changes.fireIndexedPropertyChange(this.indexPropertyName,this.size() - 1,null,o);         }         return retValue;     }     /**      * Appends all of the elements in the specified Collection to the end of      * this ArrayList, in the order that they are returned by the specified      * Collection's Iterator.  The behavior of this operation is undefined if      * the specified Collection is modified while the operation is in progress.      * (This implies that the behavior of this call is undefined if the      * specified Collection is this ArrayList, and this ArrayList is nonempty.)      *      * @return <tt>true</tt> if this ArrayList changed as a result of the call.      * @since 1.2      * @param c elements to be inserted into this ArrayList.      */     public boolean addAll(Collection<? extends T> c) {         boolean retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.addAll(c);         if(retValue&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }     /**      * Inserts all of the elements in the specified Collection into this      * ArrayList at the specified position.  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 the ArrayList      * in the order that they are returned by the specified Collection's      * iterator.      *      * @return <tt>true</tt> if this ArrayList changed as a result of the call.      * @since 1.2      * @param index index at which to insert first element      *                     from the specified collection.      * @param c elements to be inserted into this ArrayList.      */     public boolean addAll(int index,Collection<? extends T> c) {         boolean retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.addAll(index,c);         if(retValue&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }        /**      * Registers propertyChangeListeners to be fired when something changes in the ArrayList.      * Note, the whole ArrayList is an "indexedProperty" name specified and parent delineated in      * the constructor.      * @param listener PropertyChangeListener to register      */     public void addPropertyChangeListener(PropertyChangeListener listener) {         changes.addPropertyChangeListener(listener);     }     /**      * Registers propertyChangeListeners to be fired when something changes in the ArrayList.      * Note, the whole ArrayList is an "indexedProperty" name specified and parent delineated in      * the constructor.      * @param property propteryName must match what the property name of this ArrayList is or the listener will be ignored.      * @param listener the listener to register      */     public void addPropertyChangeListener(String property,PropertyChangeListener listener) {         if((property != null)&&property.equals(this.indexPropertyName)) {             changes.addPropertyChangeListener(property,listener);         }     }     /**      * This method reverses the order of the ArrayList.      */     public synchronized void invert() {         BeanArrayList<T> temp = new BeanArrayList<T>(this);         for(int i = 0; i < this.size(); i++) {             this.set(i, temp.get( temp.size() -1 ) );             temp.remove( temp.get( temp.size() -1 ) );         }     }     /**      * Removes the first occurrence of the specified element in this ArrayList      * If the ArrayList does not contain the element, it is unchanged.  More      * formally, removes the element with the lowest index i such that      * <code>(o==null ? get(i)==null : o.equals(get(i)))</code> (if such      * an element exists).      *      * @param o element to be removed from this ArrayList, if present.      * @return true if the ArrayList contained the specified element.      * @since 1.2      */     public boolean remove(Object o) {         boolean retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.remove(o);         if(retValue&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }     /**      * Removes the element at the specified position in this ArrayList.      * shifts any subsequent elements to the left (subtracts one from their      * indices).  Returns the element that was removed from the ArrayList.      *      * @return element that was removed      * @since 1.2      * @param index the index of the element to removed.      */     public T remove(int index) {         T retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.remove(index);         if((retValue != null)&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }     /**      * Removes from this ArrayList all of its elements that are contained in the      * specified Collection.      *      * @return true if this ArrayList changed as a result of the call.      * @since 1.2      * @param c a collection of elements to be removed from the ArrayList      */     public boolean removeAll(Collection<? > c) {         boolean retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.removeAll(c);         if(retValue&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }     /**      * Removes all components from this ArrayList and sets its size to zero.<p>      *      * This method is identical in functionality to the clear method      * (which is part of the List interface).      *      * @see        #clear      * @see        List      */     public void removeAllElements() {         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         super.clear();         if(old != null) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }     }     /**      * Removes the first (lowest-indexed) occurrence of the argument      * from this ArrayList. If the object is found in this ArrayList, each      * component in the ArrayList with an index greater or equal to the      * object's index is shifted downward to have an index one smaller      * than the value it had previously.<p>      *      * This method is identical in functionality to the remove(Object)      * method (which is part of the List interface).      *      * @param   obj   the component to be removed.      * @return  <code>true</code> if the argument was a component of this      *          ArrayList; <code>false</code> otherwise.      * @see        List#remove(Object)      * @see        List      */     public boolean removeElement(Object obj) {         boolean retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.remove(obj);                  if(retValue&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }     /**      * Deletes the component at the specified index. Each component in      * this ArrayList with an index greater or equal to the specified      * <code>index</code> is shifted downward to have an index one      * smaller than the value it had previously. The size of this ArrayList      * is decreased by <tt>1</tt>.<p>      *      * The index must be a value greater than or equal to <code>0</code>      * and less than the current size of the ArrayList. <p>      *      * This method is identical in functionality to the remove method      * (which is part of the List interface).  Note that the remove method      * returns the old value that was stored at the specified position.      *      * @see #size()      * @see #remove(int)      * @see List      * @param index the index of the object to remove.      */     public void removeElementAt(int index) {         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         super.remove(index);         if(old != null) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }     }     /**      * Removes propertyChangeListeners to be fired when something changes in the ArrayList.      * Note, the whole ArrayList is an "indexedProperty" name specified and parent delineated in      * the constructor.      * @param listener listener to remove      */     public void removePropertyChangeListener(PropertyChangeListener listener) {         changes.removePropertyChangeListener(listener);     }     /**      * Removes propertyChangeListeners to be fired when something changes in the ArrayList.      * Note, the whole ArrayList is an "indexedProperty" name specified and parent delineated in      * the constructor.      * @param property must match the current property name for the ArrayList or will be ignored      * @param listener listener to remove      */     public void removePropertyChangeListener(String property,PropertyChangeListener listener) {         if((property != null)&&property.equals(this.indexPropertyName)) {             changes.removePropertyChangeListener(property,listener);         }     }     /**      * Resets the contents of the ArrayList to the values provided.      * @param contents Array of object to replace the current contents with      */     public synchronized void resetContents(T[] contents) {         if((this.indexPropertyName != null)&&(this.changes != null)) {             changes.firePropertyChange(this.indexPropertyName,this.toTypedArray(),contents);         }         this.removeAllElements();         for(T t : contents)             this.add(t);     }     /**      * Retains only the elements in this ArrayList that are contained in the      * specified Collection.  In other words, removes from this ArrayList all      * of its elements that are not contained in the specified Collection.      *      * @return true if this ArrayList changed as a result of the call.      * @since 1.2      * @param c a collection of elements to be retained in this ArrayList      *          (all other elements are removed)      */     public boolean retainAll(Collection<? > c) {         boolean retValue;         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         retValue = super.retainAll(c);         if(retValue&&(old != null)) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }         return retValue;     }     /**      * Replaces the element at the specified position in this ArrayList with the      * specified element.      *      * @return the element previously at the specified position.      * @since 1.2      * @param index index of element to replace.      * @param element element to be stored at the specified position.      */     public T set(int index,T element) {         T retValue;         retValue = super.set(index,element);         if((this.indexPropertyName != null)&&(this.changes != null)) {             changes.fireIndexedPropertyChange(this.indexPropertyName,index,retValue,element);         }         return retValue;     }     /**      * performs a selection sort on all the beans in the ArrayList by PropertyName      *      * <p>You can use a mixture of bean classes as long as all the beans support      * the same property (getName() for instance), and all have the same return      * type.</p>      *      * <p>For optimal performance, it is recommended that if you have a      * mixed class set the you have them grouped with like classes together as this      * will minimize reflection inspections.</p>      * @param propertyName String value containing the property to sort      * on.      * @throws java.lang.IllegalAccessException Property was not accessible      * @throws java.beans.IntrospectionException Couldn't introspect      * @throws java.lang.reflect.InvocationTargetException Is the proper signature getProperty() ?      */     public void sortOnProperty(String propertyName) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         this.sortOnProperty(propertyName,true);     }     /**      * performs a selection sort on all the beans in the ArrayList by      * PropertyName      *      * <p>You can use a mixture of bean classes as long as all the beans      * support the same property (getName() for instance), and all have the      * same return type, or can be compareTo()ed each other.</p>      *      * <p>For optimal performance, it is recommended that if you have a      * mixed class set the you have them grouped with like classes together      * as this will minimize reflection inspections.</p>      * @param propertyName String value containing the property to sort on.      * @param ascending == sorts up if true, down if not.      * @throws java.lang.IllegalAccessException reflection exception      * @throws java.beans.IntrospectionException reflection exception      * @throws java.lang.reflect.InvocationTargetException reflection exception      */     public synchronized void sortOnProperty(String propertyName,boolean ascending) throws java.lang.IllegalAccessException,java.beans.IntrospectionException,java.lang.reflect.InvocationTargetException {         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         T temp = null;         String currentClass = "";         PropertyDescriptor pd = null;         HashMap cache = new HashMap();         for(int i = 0; i < (this.size() - 1); i++) {             for(int j = i + 1; j < this.size(); j++) {                 T o1 = this.get(i);                 if(!currentClass.equals(o1.getClass().getName())) {                     pd = (PropertyDescriptor)cache.get(o1.getClass().getName());                     if(pd == null) {                         PropertyDescriptor[] pds = Introspector.getBeanInfo(o1.getClass()).getPropertyDescriptors();                         boolean foundProperty = false;                         for(int pdi = 0; (pdi < pds.length)&&!foundProperty;                                 pdi++) {                             if(pds[pdi].getName().equals(propertyName)) {                                 pd = pds[pdi];                                 cache.put(o1.getClass().getName(),pd);                                 foundProperty = true;                             }                         }                     }                 }                 //System.out.println( "o1: "+o1+" "+pd);                 //System.out.println( propertyName +" "+ (pd == null ));                 Comparable oc1 = (Comparable)pd.getReadMethod().invoke(o1);                 T o2 = this.get(j);                 if(!currentClass.equals(o2.getClass().getName())) {                     pd = (PropertyDescriptor)cache.get(o2.getClass().getName());                     if(pd == null) {                         PropertyDescriptor[] pds = Introspector.getBeanInfo(o2.getClass()).getPropertyDescriptors();                         boolean foundProperty = false;                         for(int pdi = 0; (pdi < pds.length)&&!foundProperty;                                 pdi++) {                             if(pds[pdi].getName().equals(propertyName)) {                                 pd = pds[pdi];                                 foundProperty = true;                             }                         }                     }                 }                 Comparable oc2 = (Comparable)pd.getReadMethod().invoke(o2);                 if(ascending) {                     if((oc1 != oc2)&&((oc2 == null)||((oc1 != null)&&(oc2 != null)&&(oc2.compareTo(oc1) < 0)))) { //swap                         this.setElementAt(o2,i);                         this.setElementAt(o1,j);                     }                 } else {                     if((oc1 != oc2)&&((oc1 == null)||((oc1 != null)&&(oc2 != null)&&(oc1.compareTo(oc2) < 0)))) { //swap                         this.setElementAt(o2,i);                         this.setElementAt(o1,j);                     }                 }             }             if(old != null) {                 changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());             }         }     }     /**      * Returns an Array of the generic type associated with this ArrayList.      * @return Array representation of the current ArrayList.      */     public T[] toTypedArray() {         return (T[])this.toArray();     }     /**      * Removes from this List all of the elements whose index is between      * fromIndex, inclusive and toIndex, exclusive.  Shifts any succeeding      * elements to the left (reduces their index).      * This call shortens the ArrayList by (toIndex - fromIndex) elements.  (If      * toIndex==fromIndex, this operation has no effect.)      *      * @param fromIndex index of first element to be removed.      * @param toIndex index after last element to be removed.      */     protected void removeRange(int fromIndex,int toIndex) {         T[] old = null;         if((this.indexPropertyName != null)&&(this.changes != null)) {             old = this.toTypedArray();         }         super.removeRange(fromIndex,toIndex);         if(old != null) {             changes.firePropertyChange(this.indexPropertyName,old,this.toTypedArray());         }     } }