/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or 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 io.janusproject.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; /** * A collection of listeners. * * <p>This collection is thread-safe. * * <p>This class is inspirated by <code>EventListenerList</code>. * * <p>This class is copied from the <a href="http://www.arakhne.org/afc">Arakhnê Foundation Classes</a>. * * @param <L> is the type of listeners. * @author $Author: galland$ * @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. */ public ListenerCollection() { // } /** * Replies if this collection is empty. * * @return <code>true</code> if this collection does not contains any listener, otherwise <code>false</code> */ 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><b>WARNING!!!</b> 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. */ 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. */ @SuppressWarnings("unchecked") public <T extends EventListener> T[] getListeners(Class<T> type) { final Object[] iListeners = this.listeners; final int listenerCount = getListenerCount(iListeners, type); final T[] result = (T[]) Array.newInstance(type, listenerCount); int j = 0; for (int i = iListeners.length - 2; i >= 0; i -= 2) { if (iListeners[i] == type) { result[j++] = type.cast(iListeners[i + 1]); } } return result; } /** * Returns the total number of listeners for this listener list. * * @return the total number of listeners for this listener list. */ public int size() { return this.listeners.length / 2; } /** * Returns the total number of listeners of the supplied type for this listener list. * * @param type - type of the listeners to consider. * @return the total number of listeners of the supplied type for this listener list. */ public synchronized 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 <T extends EventListener> void add(Class<T> type, T listener) { assert listener != null; synchronized (this) { Object[] ilisteners = this.listeners; if (ilisteners == NULL) { // if this is the first listener added, // initialize the lists ilisteners = new Object[] {type, listener}; } else { // Otherwise copy the array and add the new listener final int i = ilisteners.length; final Object[] tmp = new Object[i + 2]; System.arraycopy(ilisteners, 0, tmp, 0, i); tmp[i] = type; tmp[i + 1] = listener; ilisteners = tmp; } this.listeners = ilisteners; } } /** * 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 <T extends EventListener> void remove(Class<T> type, T listener) { assert listener != null; synchronized (this) { Object[] ilisteners = this.listeners; // Is l on the list? int index = -1; for (int i = ilisteners.length - 2; i >= 0; i -= 2) { if ((ilisteners[i] == type) && (ilisteners[i + 1].equals(listener))) { index = i; break; } } // If so, remove it if (index != -1) { final Object[] tmp = new Object[ilisteners.length - 2]; // Copy the list up to index System.arraycopy(ilisteners, 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(ilisteners, index + 2, tmp, index, tmp.length - index); } // set the listener array to the new array or null ilisteners = (tmp.length == 0) ? NULL : tmp; } this.listeners = ilisteners; } } // Serialization support. 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 listener = (EventListener) lList[i + 1]; if ((listener != null) && (listener instanceof Serializable)) { stream.writeObject(t.getName()); stream.writeObject(listener); } } 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 listener = (EventListener) stream.readObject(); add((Class<EventListener>) Class.forName((String) listenerTypeOrNull, true, cl), listener); } } @Override public String toString() { final Object[] lList = this.listeners; String txt = "EventListenerList: "; //$NON-NLS-1$ txt += lList.length / 2 + " listeners: "; //$NON-NLS-1$ for (int i = 0; i <= lList.length - 2; i += 2) { txt += " type " + ((Class<?>) lList[i]).getName(); //$NON-NLS-1$ txt += " listener " + lList[i + 1]; //$NON-NLS-1$ } return txt; } }