source: josm/trunk/src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java@ 12973

Last change on this file since 12973 was 12973, checked in by Don-vip, 7 years ago

see #15008, see #15421 - collect debug data when a DataIntegrityProblemException occurs when firing a selection event

  • Property svn:eol-style set to native
File size: 8.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.event;
3
4import java.util.Collections;
5import java.util.HashSet;
6import java.util.List;
7import java.util.Objects;
8import java.util.concurrent.CopyOnWriteArrayList;
9import java.util.stream.Stream;
10
11import javax.swing.SwingUtilities;
12
13import org.openstreetmap.josm.data.SelectionChangedListener;
14import org.openstreetmap.josm.data.osm.DataIntegrityProblemException;
15import org.openstreetmap.josm.data.osm.DataSelectionListener;
16import org.openstreetmap.josm.data.osm.DataSet;
17import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
18import org.openstreetmap.josm.gui.MainApplication;
19import org.openstreetmap.josm.gui.layer.MainLayerManager;
20import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
21import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
22import org.openstreetmap.josm.tools.bugreport.BugReport;
23
24/**
25 * Similar like {@link DatasetEventManager}, just for selection events.
26 *
27 * It allows to register listeners to global selection events for the selection in the current edit layer.
28 *
29 * If you want to listen to selections to a specific data layer,
30 * you can register a listener to that layer by using {@link DataSet#addSelectionListener(DataSelectionListener)}
31 *
32 * @since 2912
33 */
34public class SelectionEventManager implements DataSelectionListener, ActiveLayerChangeListener {
35
36 private static final SelectionEventManager INSTANCE = new SelectionEventManager();
37
38 /**
39 * Returns the unique instance.
40 * @return the unique instance
41 */
42 public static SelectionEventManager getInstance() {
43 return INSTANCE;
44 }
45
46 private interface ListenerInfo {
47 void fire(SelectionChangeEvent event);
48 }
49
50 private static class OldListenerInfo implements ListenerInfo {
51 private final SelectionChangedListener listener;
52
53 OldListenerInfo(SelectionChangedListener listener) {
54 this.listener = listener;
55 }
56
57 @Override
58 public void fire(SelectionChangeEvent event) {
59 listener.selectionChanged(event.getSelection());
60 }
61
62 @Override
63 public int hashCode() {
64 return Objects.hash(listener);
65 }
66
67 @Override
68 public boolean equals(Object o) {
69 if (this == o) return true;
70 if (o == null || getClass() != o.getClass()) return false;
71 OldListenerInfo that = (OldListenerInfo) o;
72 return Objects.equals(listener, that.listener);
73 }
74 }
75
76 private static class DataListenerInfo implements ListenerInfo {
77 private final DataSelectionListener listener;
78
79 DataListenerInfo(DataSelectionListener listener) {
80 this.listener = listener;
81 }
82
83 @Override
84 public void fire(SelectionChangeEvent event) {
85 listener.selectionChanged(event);
86 }
87
88 @Override
89 public int hashCode() {
90 return Objects.hash(listener);
91 }
92
93 @Override
94 public boolean equals(Object o) {
95 if (this == o) return true;
96 if (o == null || getClass() != o.getClass()) return false;
97 DataListenerInfo that = (DataListenerInfo) o;
98 return Objects.equals(listener, that.listener);
99 }
100 }
101
102 private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<>();
103 private final CopyOnWriteArrayList<ListenerInfo> immedatelyListeners = new CopyOnWriteArrayList<>();
104
105 /**
106 * Constructs a new {@code SelectionEventManager}.
107 */
108 protected SelectionEventManager() {
109 MainLayerManager layerManager = MainApplication.getLayerManager();
110 // We do not allow for destructing this object.
111 // Currently, this is a singleton class, so this is not required.
112 layerManager.addAndFireActiveLayerChangeListener(this);
113 }
114
115 /**
116 * Registers a new {@code SelectionChangedListener}.
117 *
118 * It is preferred to add a DataSelectionListener - that listener will receive more information about the event.
119 * @param listener listener to add
120 * @param fireMode Set this to IN_EDT_CONSOLIDATED if you want the event to be fired in the EDT thread.
121 * Set it to IMMEDIATELY if you want the event to fire in the thread that caused the selection update.
122 */
123 public void addSelectionListener(SelectionChangedListener listener, FireMode fireMode) {
124 if (fireMode == FireMode.IN_EDT) {
125 throw new UnsupportedOperationException("IN_EDT mode not supported, you probably want to use IN_EDT_CONSOLIDATED.");
126 } else if (fireMode == FireMode.IN_EDT_CONSOLIDATED) {
127 inEDTListeners.addIfAbsent(new OldListenerInfo(listener));
128 } else {
129 immedatelyListeners.addIfAbsent(new OldListenerInfo(listener));
130 }
131 }
132
133 /**
134 * Adds a selection listener that gets notified for selections immediately.
135 * @param listener The listener to add.
136 * @since 12098
137 */
138 public void addSelectionListener(DataSelectionListener listener) {
139 immedatelyListeners.addIfAbsent(new DataListenerInfo(listener));
140 }
141
142 /**
143 * Adds a selection listener that gets notified for selections later in the EDT thread.
144 * Events are sent in the right order but may be delayed.
145 * @param listener The listener to add.
146 * @since 12098
147 */
148 public void addSelectionListenerForEdt(DataSelectionListener listener) {
149 inEDTListeners.addIfAbsent(new DataListenerInfo(listener));
150 }
151
152 /**
153 * Unregisters a {@code SelectionChangedListener}.
154 * @param listener listener to remove
155 */
156 public void removeSelectionListener(SelectionChangedListener listener) {
157 remove(new OldListenerInfo(listener));
158 }
159
160 /**
161 * Unregisters a {@code DataSelectionListener}.
162 * @param listener listener to remove
163 * @since 12098
164 */
165 public void removeSelectionListener(DataSelectionListener listener) {
166 remove(new DataListenerInfo(listener));
167 }
168
169 private void remove(ListenerInfo searchListener) {
170 inEDTListeners.remove(searchListener);
171 immedatelyListeners.remove(searchListener);
172 }
173
174 @Override
175 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
176 DataSet oldDataSet = e.getPreviousEditDataSet();
177 if (oldDataSet != null) {
178 // Fake a selection removal
179 // Relying on this allows components to not have to monitor layer changes.
180 // If we would not do this, e.g. the move command would have a hard time tracking which layer
181 // the last moved selection was in.
182 selectionChanged(new SelectionReplaceEvent(oldDataSet,
183 new HashSet<>(oldDataSet.getAllSelected()), Stream.empty()));
184 oldDataSet.removeSelectionListener(this);
185 }
186 DataSet newDataSet = e.getSource().getEditDataSet();
187 if (newDataSet != null) {
188 newDataSet.addSelectionListener(this);
189 // Fake a selection add
190 selectionChanged(new SelectionReplaceEvent(newDataSet,
191 Collections.emptySet(), newDataSet.getAllSelected().stream()));
192 }
193 }
194
195 @Override
196 public void selectionChanged(SelectionChangeEvent event) {
197 fireEvent(immedatelyListeners, event);
198 SwingUtilities.invokeLater(() -> fireEvent(inEDTListeners, event));
199 }
200
201 private static void fireEvent(List<ListenerInfo> listeners, SelectionChangeEvent event) {
202 for (ListenerInfo listener: listeners) {
203 try {
204 listener.fire(event);
205 } catch (DataIntegrityProblemException e) {
206 throw BugReport.intercept(e).put("event", event).put("listeners", listeners);
207 }
208 }
209 }
210
211 /**
212 * Only to be used during unit tests, to reset the state. Do not use it in plugins/other code.
213 * Called after the layer manager was reset by the test framework.
214 */
215 public void resetState() {
216 inEDTListeners.clear();
217 immedatelyListeners.clear();
218 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(this);
219 }
220}
Note: See TracBrowser for help on using the repository browser.