source: josm/trunk/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialogManager.java@ 15772

Last change on this file since 15772 was 14463, checked in by Don-vip, 5 years ago

fix #17040 - fix memory leaks when calling history dialog

  • Property svn:eol-style set to native
File size: 8.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.history;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Dimension;
7import java.awt.Point;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.HashMap;
11import java.util.Iterator;
12import java.util.List;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Objects;
16import java.util.function.Predicate;
17
18import javax.swing.JOptionPane;
19import javax.swing.SwingUtilities;
20
21import org.openstreetmap.josm.data.osm.PrimitiveId;
22import org.openstreetmap.josm.data.osm.history.History;
23import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
24import org.openstreetmap.josm.gui.MainApplication;
25import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
26import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
27import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
28import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
29import org.openstreetmap.josm.gui.util.WindowGeometry;
30import org.openstreetmap.josm.tools.JosmRuntimeException;
31import org.openstreetmap.josm.tools.SubclassFilteredCollection;
32import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
33
34/**
35 * Manager allowing to show/hide history dialogs.
36 * @since 2019
37 */
38public final class HistoryBrowserDialogManager implements LayerChangeListener {
39
40 static final class UnloadedHistoryPredicate implements Predicate<PrimitiveId> {
41 private final HistoryDataSet hds = HistoryDataSet.getInstance();
42
43 @Override
44 public boolean test(PrimitiveId p) {
45 History h = hds.getHistory(p);
46 if (h == null)
47 // reload if the history is not in the cache yet
48 return true;
49 else
50 // reload if the history object of the selected object is not in the cache yet
51 return !p.isNew() && h.getByVersion(p.getUniqueId()) == null;
52 }
53 }
54
55 private static final String WINDOW_GEOMETRY_PREF = HistoryBrowserDialogManager.class.getName() + ".geometry";
56
57 private static HistoryBrowserDialogManager instance;
58
59 private final Map<Long, HistoryBrowserDialog> dialogs;
60
61 private final Predicate<PrimitiveId> unloadedHistoryPredicate = new UnloadedHistoryPredicate();
62
63 private final Predicate<PrimitiveId> notNewPredicate = p -> !p.isNew();
64
65 private static final List<HistoryHook> hooks = new ArrayList<>();
66
67 protected HistoryBrowserDialogManager() {
68 dialogs = new HashMap<>();
69 MainApplication.getLayerManager().addLayerChangeListener(this);
70 }
71
72 /**
73 * Replies the unique instance.
74 * @return the unique instance
75 */
76 public static synchronized HistoryBrowserDialogManager getInstance() {
77 if (instance == null) {
78 instance = new HistoryBrowserDialogManager();
79 }
80 return instance;
81 }
82
83 /**
84 * Determines if an history dialog exists for the given object id.
85 * @param id the object id
86 * @return {@code true} if an history dialog exists for the given object id, {@code false} otherwise
87 */
88 public boolean existsDialog(long id) {
89 return dialogs.containsKey(id);
90 }
91
92 private void show(long id, HistoryBrowserDialog dialog) {
93 if (dialogs.containsValue(dialog)) {
94 show(id);
95 } else {
96 placeOnScreen(dialog);
97 dialog.setVisible(true);
98 dialogs.put(id, dialog);
99 }
100 }
101
102 private void show(long id) {
103 if (dialogs.containsKey(id)) {
104 dialogs.get(id).toFront();
105 }
106 }
107
108 private boolean hasDialogWithCloseUpperLeftCorner(Point p) {
109 for (HistoryBrowserDialog dialog: dialogs.values()) {
110 Point corner = dialog.getLocation();
111 if (p.x >= corner.x -5 && corner.x + 5 >= p.x
112 && p.y >= corner.y -5 && corner.y + 5 >= p.y)
113 return true;
114 }
115 return false;
116 }
117
118 private void placeOnScreen(HistoryBrowserDialog dialog) {
119 WindowGeometry geometry = new WindowGeometry(WINDOW_GEOMETRY_PREF, WindowGeometry.centerOnScreen(new Dimension(850, 500)));
120 geometry.applySafe(dialog);
121 Point p = dialog.getLocation();
122 while (hasDialogWithCloseUpperLeftCorner(p)) {
123 p.x += 20;
124 p.y += 20;
125 }
126 dialog.setLocation(p);
127 }
128
129 /**
130 * Hides the specified history dialog and cleans associated resources.
131 * @param dialog History dialog to hide
132 */
133 public void hide(HistoryBrowserDialog dialog) {
134 for (Iterator<Entry<Long, HistoryBrowserDialog>> it = dialogs.entrySet().iterator(); it.hasNext();) {
135 if (Objects.equals(it.next().getValue(), dialog)) {
136 it.remove();
137 if (dialogs.isEmpty()) {
138 new WindowGeometry(dialog).remember(WINDOW_GEOMETRY_PREF);
139 }
140 break;
141 }
142 }
143 dialog.setVisible(false);
144 dialog.dispose();
145 }
146
147 /**
148 * Hides and destroys all currently visible history browser dialogs
149 *
150 */
151 public void hideAll() {
152 List<HistoryBrowserDialog> dialogs = new ArrayList<>();
153 dialogs.addAll(this.dialogs.values());
154 for (HistoryBrowserDialog dialog: dialogs) {
155 hide(dialog);
156 }
157 }
158
159 /**
160 * Show history dialog for the given history.
161 * @param h History to show
162 */
163 public void show(History h) {
164 if (h == null)
165 return;
166 if (existsDialog(h.getId())) {
167 show(h.getId());
168 } else {
169 HistoryBrowserDialog dialog = new HistoryBrowserDialog(h);
170 show(h.getId(), dialog);
171 }
172 }
173
174 /* ----------------------------------------------------------------------------- */
175 /* LayerChangeListener */
176 /* ----------------------------------------------------------------------------- */
177 @Override
178 public void layerAdded(LayerAddEvent e) {
179 // Do nothing
180 }
181
182 @Override
183 public void layerRemoving(LayerRemoveEvent e) {
184 // remove all history browsers if the number of layers drops to 0
185 if (e.getSource().getLayers().isEmpty()) {
186 hideAll();
187 }
188 }
189
190 @Override
191 public void layerOrderChanged(LayerOrderChangeEvent e) {
192 // Do nothing
193 }
194
195 /**
196 * Adds a new {@code HistoryHook}.
197 * @param hook hook to add
198 * @return {@code true} (as specified by {@link Collection#add})
199 * @since 13947
200 */
201 public static boolean addHistoryHook(HistoryHook hook) {
202 return hooks.add(Objects.requireNonNull(hook));
203 }
204
205 /**
206 * Removes an existing {@code HistoryHook}.
207 * @param hook hook to remove
208 * @return {@code true} if this list contained the specified element
209 * @since 13947
210 */
211 public static boolean removeHistoryHook(HistoryHook hook) {
212 return hooks.remove(Objects.requireNonNull(hook));
213 }
214
215 /**
216 * Show history dialog(s) for the given primitive(s).
217 * @param primitives The primitive(s) for which history will be displayed
218 */
219 public void showHistory(final Collection<? extends PrimitiveId> primitives) {
220 final List<PrimitiveId> realPrimitives = new ArrayList<>(primitives);
221 hooks.forEach(h -> h.modifyRequestedIds(realPrimitives));
222 final Collection<? extends PrimitiveId> notNewPrimitives = SubclassFilteredCollection.filter(realPrimitives, notNewPredicate);
223 if (notNewPrimitives.isEmpty()) {
224 JOptionPane.showMessageDialog(
225 MainApplication.getMainFrame(),
226 tr("Please select at least one already uploaded node, way, or relation."),
227 tr("Warning"),
228 JOptionPane.WARNING_MESSAGE);
229 return;
230 }
231
232 Collection<? extends PrimitiveId> toLoad = SubclassFilteredCollection.filter(realPrimitives, unloadedHistoryPredicate);
233 if (!toLoad.isEmpty()) {
234 HistoryLoadTask task = new HistoryLoadTask();
235 for (PrimitiveId p : notNewPrimitives) {
236 task.add(p);
237 }
238 MainApplication.worker.submit(task);
239 }
240
241 Runnable r = () -> {
242 try {
243 for (PrimitiveId p : notNewPrimitives) {
244 final History h = HistoryDataSet.getInstance().getHistory(p);
245 if (h == null) {
246 continue;
247 }
248 SwingUtilities.invokeLater(() -> show(h));
249 }
250 } catch (final JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
251 BugReportExceptionHandler.handleException(e);
252 }
253 };
254 MainApplication.worker.submit(r);
255 }
256}
Note: See TracBrowser for help on using the repository browser.