001/* 002 * Copyright (c) 2006-2012 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * IBM Corporation - initial API and implementation 011 */ 012package org.nuxeo.common.collections; 013 014import java.util.Arrays; 015import java.util.Comparator; 016 017/** 018 * Internal class to maintain a list of listeners. This class is a thread-safe list that is optimized for frequent reads 019 * and infrequent writes. Copy on write is used to ensure readers can access the list without synchronization overhead. 020 * Readers are given access to the underlying array data structure for reading, with the trust that they will not modify 021 * the underlying array. 022 * <p> 023 * Contains code from Eclipse org.eclipse.core.runtime.ListenerList class 024 * 025 * @see http://www.eclipse.org 026 */ 027public class ListenerList { 028 029 /** 030 * Mode constant (value 0) indicating that listeners should be compared using equality. 031 */ 032 public static final int EQUALITY = 0; 033 034 /** 035 * Mode constant (value 1) indicating that listeners should be compared using identity. 036 */ 037 public static final int IDENTITY = 1; 038 039 /** 040 * The empty array singleton instance. 041 */ 042 private static final Object[] EMPTY_ARRAY = new Object[0]; 043 044 /** 045 * Indicates the comparison mode used to determine if two listeners are equivalent. 046 */ 047 private final int compareMode; 048 049 /** 050 * The list of listeners. Initially <code>null</code> but initialized to an array of size capacity the first time a 051 * listener is added. Maintains invariant: <code>listeners != null</code>. 052 */ 053 private volatile Object[] listeners = EMPTY_ARRAY; 054 055 /** 056 * Used to order listeners 057 */ 058 private final Comparator<?> comparator; 059 060 /** 061 * Creates a listener list. 062 */ 063 public ListenerList() { 064 this(EQUALITY, null); 065 } 066 067 public ListenerList(Comparator<?> comparator) { 068 this(EQUALITY, comparator); 069 } 070 071 /** 072 * Creates a listener list using the provided comparison mode. 073 */ 074 public ListenerList(int mode, Comparator<?> comparator) { 075 compareMode = mode; 076 this.comparator = comparator; 077 } 078 079 /** 080 * Adds the given listener to this list. Has no effect if an equal listener is already registered. 081 * <p> 082 * This method is synchronized to protect against multiple threads adding or removing listeners concurrently. This 083 * does not block concurrent readers. 084 * 085 * @param listener the listener to add 086 */ 087 public synchronized void add(Object listener) { 088 // check for duplicates 089 final int oldSize = listeners.length; 090 for (int i = 0; i < oldSize; ++i) { 091 if (same(listener, listeners[i])) { 092 return; 093 } 094 } 095 // Thread safety: create new array to avoid affecting concurrent readers 096 Object[] newListeners = new Object[oldSize + 1]; 097 System.arraycopy(listeners, 0, newListeners, 0, oldSize); 098 newListeners[oldSize] = listener; 099 if (comparator != null) { 100 Arrays.sort(newListeners, (Comparator<Object>) comparator); 101 } 102 // atomic assignment 103 listeners = newListeners; 104 } 105 106 /** 107 * Returns an array containing all the registered listeners. The resulting array is unaffected by subsequent adds or 108 * removes. If there are no listeners registered, the result is an empty array singleton instance (no garbage is 109 * created). Use this method when notifying listeners, so that any modifications to the listener list during the 110 * notification will have no effect on the notification itself. 111 * <p> 112 * Note: callers must not modify the returned array. 113 * 114 * @return the list of registered listeners 115 */ 116 public Object[] getListeners() { 117 return listeners; 118 } 119 120 public synchronized Object[] getListenersCopy() { 121 Object[] tmp = new Object[listeners.length]; 122 System.arraycopy(listeners, 0, tmp, 0, listeners.length); 123 return tmp; 124 } 125 126 /** 127 * Returns whether this listener list is empty. 128 * 129 * @return <code>true</code> if there are no registered listeners, and <code>false</code> otherwise 130 */ 131 public boolean isEmpty() { 132 return listeners.length == 0; 133 } 134 135 /** 136 * Removes the given listener from this list. Has no effect if an identical listener was not already registered. 137 * <p> 138 * This method is synchronized to protect against multiple threads adding or removing listeners concurrently. This 139 * does not block concurrent readers. 140 * 141 * @param listener the listener 142 */ 143 public synchronized void remove(Object listener) { 144 int oldSize = listeners.length; 145 for (int i = 0; i < oldSize; ++i) { 146 if (same(listener, listeners[i])) { 147 if (oldSize == 1) { 148 listeners = EMPTY_ARRAY; 149 } else { 150 // Thread safety: create new array to avoid affecting concurrent readers 151 Object[] newListeners = new Object[oldSize - 1]; 152 System.arraycopy(listeners, 0, newListeners, 0, i); 153 System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1); 154 // atomic assignment to field 155 listeners = newListeners; 156 } 157 return; 158 } 159 } 160 } 161 162 /** 163 * Returns <code>true</code> if the two listeners are the same based on the specified comparison mode, and 164 * <code>false</code> otherwise. 165 */ 166 private boolean same(Object listener1, Object listener2) { 167 return compareMode == IDENTITY ? listener1 == listener2 : listener1.equals(listener2); 168 } 169 170 /** 171 * Returns the number of registered listeners. 172 * 173 * @return the number of registered listeners 174 */ 175 public int size() { 176 return listeners.length; 177 } 178 179}