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

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

fix #14613 - Special HTML characters not escaped in GUI error messages

  • Property svn:eol-style set to native
File size: 16.4 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.openstreetmap.josm.tools.Utils;
49import org.xml.sax.SAXException;
50
51/**
52 * ChildRelationBrowser is a UI component which provides a tree-like view on the hierarchical
53 * structure of relations.
54 *
55 * @since 1828
56 */
57public class ChildRelationBrowser extends JPanel {
58    /** the tree with relation children */
59    private RelationTree childTree;
60    /**  the tree model */
61    private transient RelationTreeModel model;
62
63    /** the osm data layer this browser is related to */
64    private transient OsmDataLayer layer;
65
66    /**
67     * Replies the {@link OsmDataLayer} this editor is related to
68     *
69     * @return the osm data layer
70     */
71    protected OsmDataLayer getLayer() {
72        return layer;
73    }
74
75    /**
76     * builds the UI
77     */
78    protected void build() {
79        setLayout(new BorderLayout());
80        childTree = new RelationTree(model);
81        JScrollPane pane = new JScrollPane(childTree);
82        add(pane, BorderLayout.CENTER);
83
84        add(buildButtonPanel(), BorderLayout.SOUTH);
85    }
86
87    /**
88     * builds the panel with the command buttons
89     *
90     * @return the button panel
91     */
92    protected JPanel buildButtonPanel() {
93        JPanel pnl = new JPanel(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 Stack<Relation> relationsToDownload;
331        private final Set<Long> downloadedRelationIds;
332
333        DownloadAllChildrenTask(Dialog parent, Relation r) {
334            super(tr("Download relation members"), parent);
335            relationsToDownload = new Stack<>();
336            downloadedRelationIds = new HashSet<>();
337            relationsToDownload.push(r);
338        }
339
340        /**
341         * warns the user if a relation couldn't be loaded because it was deleted on
342         * the server (the server replied a HTTP code 410)
343         *
344         * @param r the relation
345         */
346        protected void warnBecauseOfDeletedRelation(Relation r) {
347            String message = tr("<html>The child relation<br>"
348                    + "{0}<br>"
349                    + "is deleted on the server. It cannot be loaded</html>",
350                    Utils.escapeReservedCharactersHTML(r.getDisplayName(DefaultNameFormatter.getInstance()))
351            );
352
353            JOptionPane.showMessageDialog(
354                    Main.parent,
355                    message,
356                    tr("Relation is deleted"),
357                    JOptionPane.WARNING_MESSAGE
358            );
359        }
360
361        /**
362         * Remembers the child relations to download
363         *
364         * @param parent the parent relation
365         */
366        protected void rememberChildRelationsToDownload(Relation parent) {
367            downloadedRelationIds.add(parent.getId());
368            for (RelationMember member: parent.getMembers()) {
369                if (member.isRelation()) {
370                    Relation child = member.getRelation();
371                    if (!downloadedRelationIds.contains(child.getId())) {
372                        relationsToDownload.push(child);
373                    }
374                }
375            }
376        }
377
378        /**
379         * Merges the primitives in <code>ds</code> to the dataset of the
380         * edit layer
381         *
382         * @param ds the data set
383         */
384        protected void mergeDataSet(DataSet ds) {
385            if (ds != null) {
386                final DataSetMerger visitor = new DataSetMerger(getLayer().data, ds);
387                visitor.merge();
388                if (!visitor.getConflicts().isEmpty()) {
389                    getLayer().getConflicts().add(visitor.getConflicts());
390                    conflictsCount += visitor.getConflicts().size();
391                }
392            }
393        }
394
395        @Override
396        protected void realRun() throws SAXException, IOException, OsmTransferException {
397            try {
398                while (!relationsToDownload.isEmpty() && !canceled) {
399                    Relation r = relationsToDownload.pop();
400                    if (r.isNew()) {
401                        continue;
402                    }
403                    rememberChildRelationsToDownload(r);
404                    progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
405                    OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
406                            true);
407                    DataSet dataSet = null;
408                    try {
409                        dataSet = reader.parseOsm(progressMonitor
410                                .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
411                    } catch (OsmApiException e) {
412                        if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) {
413                            warnBecauseOfDeletedRelation(r);
414                            continue;
415                        }
416                        throw e;
417                    }
418                    mergeDataSet(dataSet);
419                    refreshView(r);
420                }
421                SwingUtilities.invokeLater(Main.map::repaint);
422            } catch (OsmTransferException e) {
423                if (canceled) {
424                    Main.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
425                    return;
426                }
427                lastException = e;
428            }
429        }
430    }
431
432    /**
433     * The asynchronous task for downloading a set of relations
434     */
435    class DownloadRelationSetTask extends DownloadTask {
436        private final Set<Relation> relations;
437
438        DownloadRelationSetTask(Dialog parent, Set<Relation> relations) {
439            super(tr("Download relation members"), parent);
440            this.relations = relations;
441        }
442
443        protected void mergeDataSet(DataSet dataSet) {
444            if (dataSet != null) {
445                final DataSetMerger visitor = new DataSetMerger(getLayer().data, dataSet);
446                visitor.merge();
447                if (!visitor.getConflicts().isEmpty()) {
448                    getLayer().getConflicts().add(visitor.getConflicts());
449                    conflictsCount += visitor.getConflicts().size();
450                }
451            }
452        }
453
454        @Override
455        protected void realRun() throws SAXException, IOException, OsmTransferException {
456            try {
457                Iterator<Relation> it = relations.iterator();
458                while (it.hasNext() && !canceled) {
459                    Relation r = it.next();
460                    if (r.isNew()) {
461                        continue;
462                    }
463                    progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
464                    OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
465                            true);
466                    DataSet dataSet = reader.parseOsm(progressMonitor
467                            .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
468                    mergeDataSet(dataSet);
469                    refreshView(r);
470                }
471            } catch (OsmTransferException e) {
472                if (canceled) {
473                    Main.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
474                    return;
475                }
476                lastException = e;
477            }
478        }
479    }
480}
Note: See TracBrowser for help on using the repository browser.