/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.libraries.designtime.swing;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* An event listener list that stores the listeners as weak references. This list behaves like the {@link
* javax.swing.event.EventListenerList}, but does not create the risk of memory leaks when maintaining listener lists
* for global objects.
* <p/>
* The list is fully synchronized and safe to be used in multi-threading environments.
*
* @author Thomas Morgner
*/
public class WeakEventListenerList {
private static final Object[] EMPTY_ARRAY = new Object[ 0 ];
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private Object[] data;
private int size;
private final int increment;
private int count;
/**
* Creates a new listener list with a increment of 10.
*/
public WeakEventListenerList() {
data = EMPTY_ARRAY;
increment = 10;
}
/**
* Adds a new listener of the given type to this list.
*
* @param t the type of the listener.
* @param l the listener.
* @param <T> the type of the listener, must be a subclass of {@code java.util.EventListener}.
*/
public <T extends EventListener> void add( final Class<T> t, final T l ) {
lock.writeLock().lock();
try {
ensureCapacity( size + 2 );
data[ size ] = t;
data[ size + 1 ] = new WeakReference( l );
size += 2;
} finally {
lock.writeLock().unlock();
}
}
/**
* Removes an existing listener of the given type from this eventlistener list.
*
* @param t the type of the listener.
* @param l the listener.
* @param <T> the type of the listener, must be a subclass of {@code java.util.EventListener}.
*/
public <T extends EventListener> void remove( final Class<T> t, final T l ) {
lock.writeLock().lock();
try {
final int position = findInternal( t, l );
if ( position < 0 ) {
return;
}
final int shiftElements = size - position - 2;
if ( shiftElements == 0 ) {
data[ position ] = null;
data[ position + 1 ] = null;
size -= 2;
return;
}
size -= 2;
System.arraycopy( data, position + 2, data, position, shiftElements );
data[ size ] = null;
data[ size + 1 ] = null;
} finally {
lock.writeLock().unlock();
}
}
/**
* Tries to find the given listener <code>l</code>, which has to be stored under the given type.
*
* @param t the type of the listener.
* @param l the listener.
* @param <T> the type of the listener, must be a subclass of {@code java.util.EventListener}.
* @return the index of the listener, or -1 if the listener is not an element of this collection.
*/
private <T extends EventListener> int findInternal( final Class<T> t, final T l ) {
for ( int i = 0; i < size; i += 2 ) {
final WeakReference l2 = (WeakReference) data[ i + 1 ];
if ( l2 == null ) {
continue;
}
final Object o = l2.get();
if ( o == null ) {
data[ i ] = null;
data[ i + 1 ] = null;
count += 1;
} else if ( o == l ) {
if ( data[ i ] == t ) {
return i;
}
}
}
return -1;
}
/**
* Returns the number of listeners of any type stored in this eventlistener list.
*
* @return the overall size of this list.
*/
public int getSize() {
lock.readLock().lock();
try {
return size / 2;
} finally {
lock.readLock().unlock();
}
}
/**
* Counts the number of listeners of a given type that are contained in this list.
*
* @param t the type of the listener.
* @return the number of listeners.
*/
public int getListenerCount( final Class<?> t ) {
lock.readLock().lock();
try {
int retval = 0;
for ( int i = 0; i < size; i += 2 ) {
if ( data[ i ] != t ) {
continue;
}
final WeakReference l2 = (WeakReference) data[ i + 1 ];
final Object o = l2.get();
if ( o == null ) {
data[ i ] = null;
data[ i + 1 ] = null;
count += 1;
} else {
retval += 1;
}
}
return retval;
} finally {
lock.readLock().unlock();
}
}
/**
* Returns all listeners of the given type that are stored in this list.
*
* @param t the type of the listener to be retrieved.
* @param <T> a implementation or derived interface of EventListener.
* @return the array containing all listeners.
*/
public <T extends EventListener> T[] getListeners( final Class<T> t ) {
final ArrayList list = new ArrayList();
lock.readLock().lock();
try {
for ( int i = 0; i < size; i += 2 ) {
if ( data[ i ] != t ) {
continue;
}
final WeakReference l2 = (WeakReference) data[ i + 1 ];
final Object o = l2.get();
if ( o == null ) {
data[ i ] = null;
data[ i + 1 ] = null;
count += 1;
} else {
list.add( o );
}
}
} finally {
lock.readLock().unlock();
}
pack();
final T[] result = (T[]) Array.newInstance( t, list.size() );
return (T[]) list.toArray( result );
}
/**
* Removes any null-entries from this list.
*/
private void pack() {
if ( count < 5 ) {
lock.writeLock().lock();
try {
int correction = 0;
for ( int i = 0; i < size; i += 2 ) {
final Object o = data[ i ];
if ( o == null ) {
correction += 2;
continue;
}
data[ i - correction ] = o;
data[ i - correction + 1 ] = data[ i + 1 ];
}
} finally {
lock.writeLock().unlock();
}
}
}
/**
* Ensures, that the list backend can store at least <code>c</code> elements. This method does nothing, if the new
* capacity is less than the current capacity.
*
* @param c the new capacity of the list.
*/
private void ensureCapacity( final int c ) {
if ( data.length < c ) {
final Object[] newData = new Object[ Math.max( data.length + increment, c ) ];
System.arraycopy( data, 0, newData, 0, size );
data = newData;
}
}
}