/* ListenerList.java (c) 2011-2016 Edward Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package ejs.base.utils; import java.io.Serializable; import java.lang.reflect.Array; import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; /** * A thread-safe list of listeners. * @author ejs * */ public class ListenerList<T> implements Iterable<T>, Serializable { private static final long serialVersionUID = -4548014975540398630L; public interface IFire<T> { void fire(T listener); } private static final Object[] NO_LISTENERS = new Object[0]; private Set<T> listeners; private transient Object[] listenerArray; public ListenerList() { listeners = new HashSet<T>(1); listenerArray = NO_LISTENERS; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return listenerArray.length + " listeners"; } /** * Add a listener, ignoring duplicates * @param listener */ public void add(T listener) { if (listener == null) throw new NullPointerException(); synchronized (listeners) { if (!listeners.contains(listener)) { listeners.add(listener); listenerArray = listeners.toArray(new Object[listeners.size()]); } } } /** * Remove a listener, ignoring missing entries * @param listener */ public void remove(T listener) { synchronized (listeners) { if (listeners.remove(listener)) { if (listeners.isEmpty()) { listenerArray = NO_LISTENERS; } else { listenerArray = listeners.toArray(new Object[listeners.size()]); } } } } public void fire(IFire<T> fire) { Object[] localArray = listenerArray; for (Object obj : localArray) { @SuppressWarnings("unchecked") T listener = (T) obj; try { fire.fire(listener); } catch (Throwable t) { t.printStackTrace(); } } } @Override public Iterator<T> iterator() { synchronized (listeners) { return new Iterator<T>() { Object[] localArray = listenerArray; int idx = 0; @Override public boolean hasNext() { return idx < localArray.length; } @SuppressWarnings("unchecked") @Override public T next() { if (idx >= localArray.length) throw new NoSuchElementException(); return (T) localArray[idx++]; } @SuppressWarnings("unchecked") @Override public void remove() { if (idx == 0) throw new IllegalStateException(); ListenerList.this.remove((T) localArray[idx - 1]); } }; } } public void clear() { synchronized (listeners) { listeners.clear(); listenerArray = NO_LISTENERS; } } /** * @return */ public boolean isEmpty() { return listenerArray == NO_LISTENERS; } /** * Get the array of listeners (faster than iterating or invoking #fire(), but you must * iterate and handle exceptions from each callback yourself) * @return */ public Object[] toArray() { return listenerArray; } /** * Create an array of the given type. * @param klass type of array element * @param els the elements to insert, may be <code>null</code> * @return new array whose element type is 'klass', or <code>null</code> if els is <code>null</code> */ @SuppressWarnings("unchecked") public T[] toArray(Class<? extends T> klass) { T[] arr = (T[]) Array.newInstance(klass, listenerArray.length); System.arraycopy(listenerArray, 0, arr, 0, listenerArray.length); return arr; } }