source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java@ 8856

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

fix #11957 - partial revert of r8851 - do not replace Stack by ArrayDeque because of different iteration behaviour + add unit test

  • Property svn:eol-style set to native
File size: 16.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Dialog;
10import java.awt.FlowLayout;
11import java.awt.event.ActionEvent;
12import java.io.IOException;
13import java.net.HttpURLConnection;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Set;
18import java.util.Stack;
19
20import javax.swing.AbstractAction;
21import javax.swing.JButton;
22import javax.swing.JOptionPane;
23import javax.swing.JPanel;
24import javax.swing.JScrollPane;
25import javax.swing.SwingUtilities;
26import javax.swing.event.TreeSelectionEvent;
27import javax.swing.event.TreeSelectionListener;
28import javax.swing.tree.TreePath;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.osm.DataSet;
32import org.openstreetmap.josm.data.osm.DataSetMerger;
33import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
34import org.openstreetmap.josm.data.osm.Relation;
35import org.openstreetmap.josm.data.osm.RelationMember;
36import org.openstreetmap.josm.gui.DefaultNameFormatter;
37import org.openstreetmap.josm.gui.ExceptionDialogUtil;
38import org.openstreetmap.josm.gui.PleaseWaitRunnable;
39import org.openstreetmap.josm.gui.layer.OsmDataLayer;
40import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
41import org.openstreetmap.josm.gui.progress.ProgressMonitor;
42import org.openstreetmap.josm.io.OsmApi;
43import org.openstreetmap.josm.io.OsmApiException;
44import org.openstreetmap.josm.io.OsmServerObjectReader;
45import org.openstreetmap.josm.io.OsmTransferException;
46import org.openstreetmap.josm.tools.CheckParameterUtil;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.xml.sax.SAXException;
49
50/**
51 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
52 * structure of relations.
53 *
54 * @since 1828
55 */
56public class ChildRelationBrowser extends JPanel {
57 /** the tree with relation children */
58 private RelationTree childTree;
59 /** the tree model */
60 private transient RelationTreeModel model;
61
62 /** the osm data layer this browser is related to */
63 private transient OsmDataLayer layer;
64
65 /**
66 * Replies the {@link OsmDataLayer} this editor is related to
67 *
68 * @return the osm data layer
69 */
70 protected OsmDataLayer getLayer() {
71 return layer;
72 }
73
74 /**
75 * builds the UI
76 */
77 protected void build() {
78 setLayout(new BorderLayout());
79 childTree = new RelationTree(model);
80 JScrollPane pane = new JScrollPane(childTree);
81 add(pane, BorderLayout.CENTER);
82
83 add(buildButtonPanel(), BorderLayout.SOUTH);
84 }
85
86 /**
87 * builds the panel with the command buttons
88 *
89 * @return the button panel
90 */
91 protected JPanel buildButtonPanel() {
92 JPanel pnl = new JPanel();
93 pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
94
95 // ---
96 DownloadAllChildRelationsAction downloadAction = new DownloadAllChildRelationsAction();
97 pnl.add(new JButton(downloadAction));
98
99 // ---
100 DownloadSelectedAction downloadSelectedAction = new DownloadSelectedAction();
101 childTree.addTreeSelectionListener(downloadSelectedAction);
102 pnl.add(new JButton(downloadSelectedAction));
103
104 // ---
105 EditAction editAction = new EditAction();
106 childTree.addTreeSelectionListener(editAction);
107 pnl.add(new JButton(editAction));
108
109 return pnl;
110 }
111
112 /**
113 * constructor
114 *
115 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
116 * @throws IllegalArgumentException if layer is null
117 */
118 public ChildRelationBrowser(OsmDataLayer layer) {
119 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
120 this.layer = layer;
121 model = new RelationTreeModel();
122 build();
123 }
124
125 /**
126 * constructor
127 *
128 * @param layer the {@link OsmDataLayer} this browser is related to. Must not be null.
129 * @param root the root relation
130 * @throws IllegalArgumentException if layer is null
131 */
132 public ChildRelationBrowser(OsmDataLayer layer, Relation root) {
133 this(layer);
134 populate(root);
135 }
136
137 /**
138 * populates the browser with a relation
139 *
140 * @param r the relation
141 */
142 public void populate(Relation r) {
143 model.populate(r);
144 }
145
146 /**
147 * populates the browser with a list of relation members
148 *
149 * @param members the list of relation members
150 */
151
152 public void populate(List<RelationMember> members) {
153 model.populate(members);
154 }
155
156 /**
157 * replies the parent dialog this browser is embedded in
158 *
159 * @return the parent dialog; null, if there is no {@link Dialog} as parent dialog
160 */
161 protected Dialog getParentDialog() {
162 Component c = this;
163 while (c != null && !(c instanceof Dialog)) {
164 c = c.getParent();
165 }
166 return (Dialog) c;
167 }
168
169 /**
170 * Action for editing the currently selected relation
171 *
172 *
173 */
174 class EditAction extends AbstractAction implements TreeSelectionListener {
175 EditAction() {
176 putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to."));
177 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
178 putValue(NAME, tr("Edit"));
179 refreshEnabled();
180 }
181
182 protected void refreshEnabled() {
183 TreePath[] selection = childTree.getSelectionPaths();
184 setEnabled(selection != null && selection.length > 0);
185 }
186
187 public void run() {
188 TreePath[] selection = childTree.getSelectionPaths();
189 if (selection == null || selection.length == 0) return;
190 // do not launch more than 10 relation editors in parallel
191 //
192 for (int i = 0; i < Math.min(selection.length, 10); i++) {
193 Relation r = (Relation) selection[i].getLastPathComponent();
194 if (r.isIncomplete()) {
195 continue;
196 }
197 RelationEditor editor = RelationEditor.getEditor(getLayer(), r, null);
198 editor.setVisible(true);
199 }
200 }
201
202 @Override
203 public void actionPerformed(ActionEvent e) {
204 if (!isEnabled())
205 return;
206 run();
207 }
208
209 @Override
210 public void valueChanged(TreeSelectionEvent e) {
211 refreshEnabled();
212 }
213 }
214
215 /**
216 * Action for downloading all child relations for a given parent relation.
217 * Recursively.
218 */
219 class DownloadAllChildRelationsAction extends AbstractAction{
220 DownloadAllChildRelationsAction() {
221 putValue(SHORT_DESCRIPTION, tr("Download all child relations (recursively)"));
222 putValue(SMALL_ICON, ImageProvider.get("download"));
223 putValue(NAME, tr("Download All Children"));
224 }
225
226 public void run() {
227 Main.worker.submit(new DownloadAllChildrenTask(getParentDialog(), (Relation) model.getRoot()));
228 }
229
230 @Override
231 public void actionPerformed(ActionEvent e) {
232 if (!isEnabled())
233 return;
234 run();
235 }
236 }
237
238 /**
239 * Action for downloading all selected relations
240 */
241 class DownloadSelectedAction extends AbstractAction implements TreeSelectionListener {
242 DownloadSelectedAction() {
243 putValue(SHORT_DESCRIPTION, tr("Download selected relations"));
244 // FIXME: replace with better icon
245 //
246 putValue(SMALL_ICON, ImageProvider.get("download"));
247 putValue(NAME, tr("Download Selected Children"));
248 updateEnabledState();
249 }
250
251 protected void updateEnabledState() {
252 TreePath[] selection = childTree.getSelectionPaths();
253 setEnabled(selection != null && selection.length > 0);
254 }
255
256 public void run() {
257 TreePath[] selection = childTree.getSelectionPaths();
258 if (selection == null || selection.length == 0)
259 return;
260 Set<Relation> relations = new HashSet<>();
261 for (TreePath aSelection : selection) {
262 relations.add((Relation) aSelection.getLastPathComponent());
263 }
264 Main.worker.submit(new DownloadRelationSetTask(getParentDialog(), relations));
265 }
266
267 @Override
268 public void actionPerformed(ActionEvent e) {
269 if (!isEnabled())
270 return;
271 run();
272 }
273
274 @Override
275 public void valueChanged(TreeSelectionEvent e) {
276 updateEnabledState();
277 }
278 }
279
280 abstract class DownloadTask extends PleaseWaitRunnable {
281 protected boolean canceled;
282 protected int conflictsCount;
283 protected Exception lastException;
284
285 DownloadTask(String title, Dialog parent) {
286 super(title, new PleaseWaitProgressMonitor(parent), false);
287 }
288
289 @Override
290 protected void cancel() {
291 canceled = true;
292 OsmApi.getOsmApi().cancel();
293 }
294
295 protected void refreshView(Relation relation) {
296 for (int i = 0; i < childTree.getRowCount(); i++) {
297 Relation reference = (Relation) childTree.getPathForRow(i).getLastPathComponent();
298 if (reference == relation) {
299 model.refreshNode(childTree.getPathForRow(i));
300 }
301 }
302 }
303
304 @Override
305 protected void finish() {
306 if (canceled)
307 return;
308 if (lastException != null) {
309 ExceptionDialogUtil.explainException(lastException);
310 return;
311 }
312
313 if (conflictsCount > 0) {
314 JOptionPane.showMessageDialog(
315 Main.parent,
316 trn("There was {0} conflict during import.",
317 "There were {0} conflicts during import.",
318 conflictsCount, conflictsCount),
319 trn("Conflict in data", "Conflicts in data", conflictsCount),
320 JOptionPane.WARNING_MESSAGE
321 );
322 }
323 }
324 }
325
326 /**
327 * The asynchronous task for downloading relation members.
328 */
329 class DownloadAllChildrenTask extends DownloadTask {
330 private final Relation relation;
331 private final Stack<Relation> relationsToDownload;
332 private final Set<Long> downloadedRelationIds;
333
334 DownloadAllChildrenTask(Dialog parent, Relation r) {
335 super(tr("Download relation members"), parent);
336 this.relation = r;
337 relationsToDownload = new Stack<>();
338 downloadedRelationIds = new HashSet<>();
339 relationsToDownload.push(this.relation);
340 }
341
342 /**
343 * warns the user if a relation couldn't be loaded because it was deleted on
344 * the server (the server replied a HTTP code 410)
345 *
346 * @param r the relation
347 */
348 protected void warnBecauseOfDeletedRelation(Relation r) {
349 String message = tr("<html>The child relation<br>"
350 + "{0}<br>"
351 + "is deleted on the server. It cannot be loaded</html>",
352 r.getDisplayName(DefaultNameFormatter.getInstance())
353 );
354
355 JOptionPane.showMessageDialog(
356 Main.parent,
357 message,
358 tr("Relation is deleted"),
359 JOptionPane.WARNING_MESSAGE
360 );
361 }
362
363 /**
364 * Remembers the child relations to download
365 *
366 * @param parent the parent relation
367 */
368 protected void rememberChildRelationsToDownload(Relation parent) {
369 downloadedRelationIds.add(parent.getId());
370 for (RelationMember member: parent.getMembers()) {
371 if (member.isRelation()) {
372 Relation child = member.getRelation();
373 if (!downloadedRelationIds.contains(child.getId())) {
374 relationsToDownload.push(child);
375 }
376 }
377 }
378 }
379
380 /**
381 * Merges the primitives in <code>ds</code> to the dataset of the
382 * edit layer
383 *
384 * @param ds the data set
385 */
386 protected void mergeDataSet(DataSet ds) {
387 if (ds != null) {
388 final DataSetMerger visitor = new DataSetMerger(getLayer().data, ds);
389 visitor.merge();
390 if (!visitor.getConflicts().isEmpty()) {
391 getLayer().getConflicts().add(visitor.getConflicts());
392 conflictsCount += visitor.getConflicts().size();
393 }
394 }
395 }
396
397 @Override
398 protected void realRun() throws SAXException, IOException, OsmTransferException {
399 try {
400 while (!relationsToDownload.isEmpty() && !canceled) {
401 Relation r = relationsToDownload.pop();
402 if (r.isNew()) {
403 continue;
404 }
405 rememberChildRelationsToDownload(r);
406 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
407 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
408 true);
409 DataSet dataSet = null;
410 try {
411 dataSet = reader.parseOsm(progressMonitor
412 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
413 } catch (OsmApiException e) {
414 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
415 warnBecauseOfDeletedRelation(r);
416 continue;
417 }
418 throw e;
419 }
420 mergeDataSet(dataSet);
421 refreshView(r);
422 }
423 SwingUtilities.invokeLater(new Runnable() {
424 @Override
425 public void run() {
426 Main.map.repaint();
427 }
428 });
429 } catch (Exception e) {
430 if (canceled) {
431 Main.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
432 return;
433 }
434 lastException = e;
435 }
436 }
437 }
438
439 /**
440 * The asynchronous task for downloading a set of relations
441 */
442 class DownloadRelationSetTask extends DownloadTask {
443 private final Set<Relation> relations;
444
445 DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
446 super(tr("Download relation members"), parent);
447 this.relations = relations;
448 }
449
450 protected void mergeDataSet(DataSet dataSet) {
451 if (dataSet != null) {
452 final DataSetMerger visitor = new DataSetMerger(getLayer().data, dataSet);
453 visitor.merge();
454 if (!visitor.getConflicts().isEmpty()) {
455 getLayer().getConflicts().add(visitor.getConflicts());
456 conflictsCount += visitor.getConflicts().size();
457 }
458 }
459 }
460
461 @Override
462 protected void realRun() throws SAXException, IOException, OsmTransferException {
463 try {
464 Iterator<Relation> it = relations.iterator();
465 while (it.hasNext() && !canceled) {
466 Relation r = it.next();
467 if (r.isNew()) {
468 continue;
469 }
470 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
471 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
472 true);
473 DataSet dataSet = reader.parseOsm(progressMonitor
474 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
475 mergeDataSet(dataSet);
476 refreshView(r);
477 }
478 } catch (Exception e) {
479 if (canceled) {
480 Main.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
481 return;
482 }
483 lastException = e;
484 }
485 }
486 }
487}
Note: See TracBrowser for help on using the repository browser.