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());
}
}
}
Related examples in the same category