| /*=============================================================================# |
| # Copyright (c) 2004, 2021 IBM Corporation and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 |
| # |
| # Contributors: |
| # IBM Corporation - initial API and implementation |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - extended generalized API |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ecommons.collections; |
| |
| import java.lang.reflect.Array; |
| import java.util.Arrays; |
| |
| |
| /** |
| * ListenerList for common usage with support of generics. |
| * |
| * This class is a thread safe list that is designed for storing lists of items. |
| * The implementation is optimized for minimal memory footprint, frequent reads |
| * and infrequent writes. Modification of the list is synchronized and relatively |
| * expensive, while accessing the items is very fast. Readers are given access |
| * to the underlying array data structure for reading, with the trust that they will |
| * not modify the underlying array. |
| * <p> |
| * <a name="same">A item list handles the <i>same</i> item being added |
| * multiple times, and tolerates removal of items that are the same as other |
| * items in the list. For this purpose, items can be compared with each other |
| * using either equality or identity, as specified in the list constructor. |
| * </p> |
| * <p> |
| * Use the <code>getListeners</code> method when notifying items. The recommended |
| * code sequence for notifying all registered items of say, |
| * <code>FooListener.eventHappened</code>, is: |
| * |
| * <pre> |
| * Object[] listeners = myListenerList.toArray(); |
| * for (int i = 0; i < listeners.length; ++i) { |
| * ((FooListener) listeners[i]).eventHappened(event); |
| * } |
| * </pre> |
| * |
| * </p><p> |
| * This class can be used without OSGi running. |
| * </p> |
| * |
| * @deprecated |
| */ |
| @Deprecated |
| public final class FastList<T> { |
| |
| /** |
| * Mode constant (value 0) indicating that items should be considered |
| * the <a href="#same">same</a> if they are equal. |
| */ |
| public static final int EQUALITY = 0; |
| |
| /** |
| * Mode constant (value 1) indicating that items should be considered |
| * the <a href="#same">same</a> if they are identical. |
| */ |
| public static final int IDENTITY = 1; |
| |
| |
| /** |
| * Indicates the comparison mode used to determine if two |
| * items are equivalent |
| */ |
| private final boolean identity; |
| |
| private final Class<T> type; |
| private final T[] emptyArray; |
| |
| /** |
| * The list of items. Initially empty but initialized |
| * to an array of size capacity the first time a item is added. |
| * Maintains invariant: items != null |
| */ |
| private volatile T[] items; |
| |
| |
| /** |
| * Creates a item list in which items are compared using equality. |
| */ |
| public FastList(final Class<T> type) { |
| this(type, EQUALITY, null); |
| } |
| |
| /** |
| * Creates a item list using the provided comparison mode. |
| * |
| * @param mode The mode used to determine if items are the <a href="#same">same</a>. |
| */ |
| public FastList(final Class<T> type, final int mode) { |
| this(type, mode, null); |
| } |
| |
| /** |
| * Creates a item list using the provided comparison mode |
| * and initial items. |
| * |
| * @param mode The mode used to determine if items are the <a href="#same">same</a> |
| * @param initial array with the initial items, the array is used directly |
| */ |
| public FastList(final Class<T> type, final int mode, final T[] initial) { |
| if (mode != EQUALITY && mode != IDENTITY) { |
| throw new IllegalArgumentException(); |
| } |
| this.type = type; |
| this.identity = mode == IDENTITY; |
| |
| emptyArray = (T[]) Array.newInstance(type, 0); |
| items = (initial != null) ? initial : emptyArray; |
| } |
| |
| |
| /** |
| * Adds a item to this list. This method has no effect if the <a href="#same">same</a> |
| * item is already registered. |
| * |
| * @param item the non-<code>null</code> item to add |
| */ |
| public synchronized void add(final T item) { |
| // This method is synchronized to protect against multiple threads adding |
| // or removing items concurrently. This does not block concurrent readers. |
| if (item == null) { |
| throw new IllegalArgumentException(); |
| } |
| // check for duplicates |
| final int oldSize = items.length; |
| for (int i = 0; i < oldSize; ++i) { |
| final Object item2 = items[i]; |
| if (identity ? item == item2 : item.equals(item2)) { |
| return; |
| } |
| } |
| // Thread safety: create new array to avoid affecting concurrent readers |
| final T[] newListeners = (T[]) Array.newInstance(type, oldSize + 1); |
| System.arraycopy(items, 0, newListeners, 0, oldSize); |
| newListeners[oldSize] = item; |
| //atomic assignment |
| this.items = newListeners; |
| } |
| |
| /** |
| * Returns an array containing all the registered items. |
| * The resulting array is unaffected by subsequent adds or removes. |
| * If there are no items registered, the result is an empty array. |
| * Use this method when notifying items, so that any modifications |
| * to the item list during the notification will have no effect on |
| * the notification itself. |
| * <p> |
| * Note: Callers of this method <b>must not</b> modify the returned array. |
| * |
| * @return the list of registered items |
| */ |
| public T[] toArray() { |
| return items; |
| } |
| |
| /** |
| * Returns whether this item list is empty. |
| * |
| * @return <code>true</code> if there are no registered items, and |
| * <code>false</code> otherwise |
| */ |
| public boolean isEmpty() { |
| return items.length == 0; |
| } |
| |
| /** |
| * Removes a item from this list. Has no effect if the <a href="#same">same</a> |
| * item was not already registered. |
| * |
| * @param item the non-<code>null</code> item to remove |
| */ |
| public synchronized void remove(final T item) { |
| // This method is synchronized to protect against multiple threads adding |
| // or removing items concurrently. This does not block concurrent readers. |
| if (item == null) { |
| throw new IllegalArgumentException(); |
| } |
| final int oldSize = items.length; |
| for (int i = 0; i < oldSize; ++i) { |
| final Object item2 = items[i]; |
| if (identity ? item == item2 : item.equals(item2)) { |
| if (oldSize == 1) { |
| items = emptyArray; |
| } else { |
| // Thread safety: create new array to avoid affecting concurrent readers |
| final T[] newListeners = (T[]) Array.newInstance(type, oldSize - 1); |
| System.arraycopy(items, 0, newListeners, 0, i); |
| System.arraycopy(items, i + 1, newListeners, i, oldSize - i - 1); |
| //atomic assignment to field |
| this.items = newListeners; |
| } |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Replaces an item from this list with a new item. If the old item is not in the list, |
| * the new item is added to the list. |
| * |
| * @param oldItem the item to replace |
| * @param newItem the non-<code>null</code> item to add |
| */ |
| public synchronized void replace(final T oldItem, final T newItem) { |
| // This method is synchronized to protect against multiple threads adding |
| // or removing items concurrently. This does not block concurrent readers. |
| if (newItem == null) { |
| throw new IllegalArgumentException(); |
| } |
| int remove = -1; |
| final int oldSize = items.length; |
| if (oldItem != null) { |
| if (identity ? oldItem != newItem : !oldItem.equals(newItem)) { |
| for (int i = 0; i < oldSize; ++i) { |
| final Object item2 = items[i]; |
| if (identity ? newItem == item2 : newItem.equals(item2)) { |
| remove = i; |
| break; |
| } |
| } |
| } |
| for (int i = 0; i < oldSize; ++i) { |
| final Object item2 = items[i]; |
| if (identity ? oldItem == item2 : oldItem.equals(item2)) { |
| // Thread safety: create new array to avoid affecting concurrent readers |
| if (remove >= 0 && remove != i) { |
| final T[] newListeners = (T[]) Array.newInstance(type, oldSize - 1); |
| System.arraycopy(items, 0, newListeners, 0, remove); |
| System.arraycopy(items, remove + 1, newListeners, remove, oldSize - remove - 1); |
| newListeners[i < remove ? i : (i - 1)] = newItem; |
| this.items = newListeners; |
| } |
| else { |
| final T[] newListeners = (T[]) Array.newInstance(type, oldSize); |
| System.arraycopy(items, 0, newListeners, 0, oldSize); |
| newListeners[i] = newItem; |
| this.items = newListeners; |
| } |
| return; |
| } |
| } |
| } |
| if (remove >= 0) { |
| final T[] newListeners = (T[]) Array.newInstance(type, oldSize); |
| System.arraycopy(items, 0, newListeners, 0, remove); |
| System.arraycopy(items, remove + 1, newListeners, remove, oldSize - remove - 1); |
| newListeners[oldSize - 1] = newItem; |
| this.items = newListeners; |
| } |
| else { |
| final T[] newListeners = (T[]) Array.newInstance(type, oldSize + 1); |
| System.arraycopy(items, 0, newListeners, 0, oldSize); |
| newListeners[oldSize] = newItem; |
| this.items = newListeners; |
| } |
| } |
| |
| /** |
| * Returns the number of registered items. |
| * |
| * @return the number of registered items |
| */ |
| public int size() { |
| return items.length; |
| } |
| |
| /** |
| * Removes all items from this list. |
| * |
| * @return the previous registered items |
| */ |
| public synchronized T[] clear() { |
| final T[] oldListeners = items; |
| items = emptyArray; |
| return oldListeners; |
| } |
| |
| |
| @Override |
| public String toString() { |
| return Arrays.toString(items); |
| } |
| |
| } |