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