1 /******************************************************************************* 2 * Copyright (c) 2004, 2019 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.ui.internal.preferences; 15 16 import java.util.ArrayList; 17 import java.util.Collection; 18 import java.util.Collections; 19 import java.util.HashMap; 20 import java.util.HashSet; 21 import java.util.List; 22 import java.util.Map; 23 24 /** 25 * @since 3.1 26 */ 27 public final class PropertyListenerList { 28 private Map<String, List<IPropertyMapListener>> listeners; 29 private List<IPropertyMapListener> globalListeners; 30 private static String[] singlePropertyDelta; 31 private static Object mutex = new Object(); 32 PropertyListenerList()33 public PropertyListenerList() { 34 } 35 firePropertyChange(String prefId)36 public void firePropertyChange(String prefId) { 37 String[] delta; 38 39 // Optimization: as long as we're not being called recursively, 40 // we can reuse the same delta object to avoid repeated memory 41 // allocation. 42 synchronized (mutex) { 43 if (singlePropertyDelta != null) { 44 delta = singlePropertyDelta; 45 singlePropertyDelta = null; 46 } else { 47 delta = new String[] { prefId }; 48 } 49 } 50 51 delta[0] = prefId; 52 53 firePropertyChange(delta); 54 55 // Optimization: allow this same delta object to be reused at a later 56 // time 57 if (singlePropertyDelta == null) { 58 synchronized (mutex) { 59 singlePropertyDelta = delta; 60 } 61 } 62 } 63 firePropertyChange(String[] propertyIds)64 public void firePropertyChange(String[] propertyIds) { 65 if (globalListeners != null) { 66 for (IPropertyMapListener next : globalListeners) { 67 next.propertyChanged(propertyIds); 68 } 69 } 70 71 if (listeners != null) { 72 73 // To avoid temporary memory allocation, we try to simply move the 74 // result pointer around if possible. We only allocate a HashSet 75 // to compute which listeners we care about 76 Collection<IPropertyMapListener> result = Collections.emptySet(); 77 HashSet<IPropertyMapListener> union = null; 78 79 for (String property : propertyIds) { 80 List<IPropertyMapListener> existingListeners = listeners.get(property); 81 82 if (existingListeners != null) { 83 if (result.isEmpty()) { 84 result = existingListeners; 85 } else { 86 if (union == null) { 87 union = new HashSet<>(); 88 union.addAll(result); 89 result = union; 90 } 91 92 union.addAll(existingListeners); 93 } 94 } 95 } 96 97 for (IPropertyMapListener next : result) { 98 next.propertyChanged(propertyIds); 99 } 100 } 101 } 102 add(IPropertyMapListener newListener)103 public void add(IPropertyMapListener newListener) { 104 if (globalListeners == null) { 105 globalListeners = new ArrayList<>(); 106 } 107 108 globalListeners.add(newListener); 109 newListener.listenerAttached(); 110 } 111 112 /** 113 * Adds a listener which will be notified when the given property changes 114 * 115 * @param propertyId 116 * @param newListener 117 * @since 3.1 118 */ addInternal(String propertyId, IPropertyMapListener newListener)119 private void addInternal(String propertyId, IPropertyMapListener newListener) { 120 if (listeners == null) { 121 listeners = new HashMap<>(); 122 } 123 124 List<IPropertyMapListener> listenerList = listeners.get(propertyId); 125 126 if (listenerList == null) { 127 listenerList = new ArrayList<>(1); 128 listeners.put(propertyId, listenerList); 129 } 130 131 if (!listenerList.contains(newListener)) { 132 listenerList.add(newListener); 133 } 134 } 135 add(String[] propertyIds, IPropertyMapListener newListener)136 public void add(String[] propertyIds, IPropertyMapListener newListener) { 137 for (String id : propertyIds) { 138 addInternal(id, newListener); 139 } 140 newListener.listenerAttached(); 141 } 142 remove(String propertyId, IPropertyMapListener toRemove)143 public void remove(String propertyId, IPropertyMapListener toRemove) { 144 if (listeners == null) { 145 return; 146 } 147 List<IPropertyMapListener> listenerList = listeners.get(propertyId); 148 149 if (listenerList != null) { 150 listenerList.remove(toRemove); 151 152 if (listenerList.isEmpty()) { 153 listeners.remove(propertyId); 154 155 if (listeners.isEmpty()) { 156 listeners = null; 157 } 158 } 159 } 160 } 161 removeAll()162 public void removeAll() { 163 globalListeners = null; 164 listeners = null; 165 } 166 remove(IPropertyMapListener toRemove)167 public void remove(IPropertyMapListener toRemove) { 168 if (globalListeners != null) { 169 globalListeners.remove(toRemove); 170 if (globalListeners.isEmpty()) { 171 globalListeners = null; 172 } 173 } 174 175 if (listeners != null) { 176 for (String key : listeners.keySet()) { 177 remove(key, toRemove); 178 } 179 } 180 } 181 isEmpty()182 public boolean isEmpty() { 183 return globalListeners == null && listeners == null; 184 } 185 } 186