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