/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.util; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.util.EventListener; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.vmutil.ReflectionUtil; /** * A collection of listeners. * * <p>This collection is thread-safe. * * <p>This class is inspirated by <code>EventListenerList</code>. * * @param <L> is the type of listeners. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public class ListenerCollection<L extends EventListener> { private static final Object[] NULL = new Object[0]; /** Listeners. */ protected transient Object[] listeners = NULL; /** Construct a listener collection. */ public ListenerCollection() { // } /** Replies if this collection is empty. * * @return <code>true</code> if this collection does not * contains any listener, otherwise <code>false</code> */ @Pure public boolean isEmpty() { return this.listeners == NULL; } /** Clear this collection. */ public void clear() { this.listeners = NULL; } /** * Passes back the event listener list as an array * of ListenerType-listener pairs. Note that for * performance reasons, this implementation passes back * the actual data structure in which the listener data * is stored internally! * This method is guaranteed to pass back a non-null * array, so that no null-checking is required in * fire methods. A zero-length array of Object should * be returned if there are currently no listeners. * * <p>WARNING!!! Absolutely NO modification of * the data contained in this array should be made -- if * any such manipulation is necessary, it should be done * on a copy of the array returned rather than the array * itself. * * @return the listeners. */ @Pure public Object[] getListenerList() { return this.listeners; } /** * Return an array of all the listeners of the given type. * * @param <T> is the type of expected listeners. * @param type is the type of expected listeners. * @return all of the listeners of the specified type. * @throws ClassCastException if the supplied class is not assignable to EventListener */ @SuppressWarnings("unchecked") @Pure public <T extends EventListener> T[] getListeners(Class<T> type) { final Object[] l = this.listeners; final int n = getListenerCount(l, type); final T[] result = (T[]) Array.newInstance(type, n); int j = 0; for (int i = l.length - 2; i >= 0; i -= 2) { if (l[i] == type) { result[j++] = type.cast(l[i + 1]); } } return result; } /** * Returns the total number of listeners for this listener list. * * @return the total number of listeners for this listener list. */ @Pure public int size() { return this.listeners.length / 2; } /** * Returns the total number of listeners of the supplied type * for this listener list. * * @param type the type of the listeners. * @return the total number of listeners of the supplied type * for this listener list. */ @Pure public int getListenerCount(Class<?> type) { return getListenerCount(this.listeners, type); } private static int getListenerCount(Object[] list, Class<?> type) { int count = 0; for (int i = 0; i < list.length; i += 2) { if (type == (Class<?>) list[i]) { ++count; } } return count; } /** * Adds the listener as a listener of the specified type. * * @param <T> the type of the listener to be added * @param type the type of the listener to be added * @param listener the listener to be added */ public synchronized <T extends EventListener> void add(Class<T> type, T listener) { assert listener != null; if (this.listeners == NULL) { // if this is the first listener added, // initialize the lists this.listeners = new Object[] {type, listener}; } else { // Otherwise copy the array and add the new listener final int i = this.listeners.length; final Object[] tmp = new Object[i + 2]; System.arraycopy(this.listeners, 0, tmp, 0, i); tmp[i] = type; tmp[i + 1] = listener; this.listeners = tmp; } } /** * Removes the listener as a listener of the specified type. * * @param <T> the type of the listener to be removed * @param type the type of the listener to be removed * @param listener the listener to be removed */ public synchronized <T extends EventListener> void remove(Class<T> type, T listener) { assert listener != null; // Is l on the list? int index = -1; for (int i = this.listeners.length - 2; i >= 0; i -= 2) { if ((this.listeners[i] == type) && (this.listeners[i + 1].equals(listener))) { index = i; break; } } // If so, remove it if (index != -1) { final Object[] tmp = new Object[this.listeners.length - 2]; // Copy the list up to index System.arraycopy(this.listeners, 0, tmp, 0, index); // Copy from two past the index, up to // the end of tmp (which is two elements // shorter than the old list) if (index < tmp.length) { System.arraycopy(this.listeners, index + 2, tmp, index, tmp.length - index); } // set the listener array to the new array or null this.listeners = (tmp.length == 0) ? NULL : tmp; } } /** Serialization support. * * @param stream the stream. * @throws IOException when something wrong occurs. */ private void writeObject(ObjectOutputStream stream) throws IOException { final Object[] lList = this.listeners; stream.defaultWriteObject(); // Save the non-null event listeners: for (int i = 0; i < lList.length; i += 2) { final Class<?> t = (Class<?>) lList[i]; final EventListener l = (EventListener) lList[i + 1]; if ((l != null) && (l instanceof Serializable)) { stream.writeObject(t.getName()); stream.writeObject(l); } } stream.writeObject(null); } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { this.listeners = NULL; stream.defaultReadObject(); Object listenerTypeOrNull; while (null != (listenerTypeOrNull = stream.readObject())) { final ClassLoader cl = Thread.currentThread().getContextClassLoader(); final EventListener l = (EventListener) stream.readObject(); add((Class<EventListener>) Class.forName((String) listenerTypeOrNull, true, cl), l); } } @Override @Pure public String toString() { return ReflectionUtil.toString(this); } }