001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.relation;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Dialog;
008import java.io.IOException;
009
010import javax.swing.JTree;
011import javax.swing.SwingUtilities;
012import javax.swing.event.TreeExpansionEvent;
013import javax.swing.event.TreeWillExpandListener;
014import javax.swing.tree.ExpandVetoException;
015import javax.swing.tree.TreePath;
016
017import org.openstreetmap.josm.data.osm.DataSet;
018import org.openstreetmap.josm.data.osm.DataSetMerger;
019import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
020import org.openstreetmap.josm.data.osm.Relation;
021import org.openstreetmap.josm.gui.MainApplication;
022import org.openstreetmap.josm.gui.PleaseWaitRunnable;
023import org.openstreetmap.josm.gui.progress.ProgressMonitor;
024import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
025import org.openstreetmap.josm.io.OsmApi;
026import org.openstreetmap.josm.io.OsmServerObjectReader;
027import org.openstreetmap.josm.io.OsmTransferException;
028import org.openstreetmap.josm.tools.Logging;
029import org.xml.sax.SAXException;
030
031/**
032 * This is a {@link JTree} rendering the hierarchical structure of {@link Relation}s.
033 *
034 * @see RelationTreeModel
035 */
036public class RelationTree extends JTree {
037    /**
038     * builds the UI
039     */
040    protected void build() {
041        setRootVisible(false);
042        setShowsRootHandles(true);
043        setCellRenderer(new RelationTreeCellRenderer());
044        addTreeWillExpandListener(new LazyRelationLoader());
045    }
046
047    /**
048     * constructor
049     */
050    public RelationTree() {
051        super();
052        build();
053    }
054
055    /**
056     * constructor
057     * @param model the tree model
058     */
059    public RelationTree(RelationTreeModel model) {
060        super(model);
061        build();
062    }
063
064    /**
065     * replies the parent dialog this tree is embedded in.
066     *
067     * @return the parent dialog; null, if there is no parent dialog
068     */
069    protected Dialog getParentDialog() {
070        Component c = this;
071        while (c != null && !(c instanceof Dialog)) {
072            c = c.getParent();
073        }
074        return (Dialog) c;
075    }
076
077    /**
078     * An adapter for TreeWillExpand-events. If a node is to be expanded which is
079     * not loaded yet this will trigger asynchronous loading of the respective
080     * relation.
081     *
082     */
083    class LazyRelationLoader implements TreeWillExpandListener {
084
085        @Override
086        public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
087            // do nothing
088        }
089
090        @Override
091        public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
092            TreePath path = event.getPath();
093            Relation parent = (Relation) event.getPath().getLastPathComponent();
094            if (!parent.isIncomplete() || parent.isNew())
095                // we don't load complete  or new relations
096                return;
097            // launch the download task
098            MainApplication.worker.submit(new RelationLoader(getParentDialog(), parent, path));
099        }
100    }
101
102    /**
103     * Asynchronous download task for a specific relation
104     *
105     */
106    class RelationLoader extends PleaseWaitRunnable {
107        private boolean canceled;
108        private Exception lastException;
109        private final Relation relation;
110        private DataSet ds;
111        private final TreePath path;
112
113        RelationLoader(Dialog dialog, Relation relation, TreePath path) {
114            super(
115                    tr("Load relation"),
116                    new PleaseWaitProgressMonitor(
117                            dialog
118                    ),
119                    false /* don't ignore exceptions */
120            );
121            this.relation = relation;
122            this.path = path;
123        }
124
125        @Override
126        protected void cancel() {
127            OsmApi.getOsmApi().cancel();
128            this.canceled = true;
129        }
130
131        @Override
132        protected void finish() {
133            if (canceled)
134                return;
135            if (lastException != null) {
136                Logging.error(lastException);
137                return;
138            }
139            DataSet editData = MainApplication.getLayerManager().getEditDataSet();
140            DataSetMerger visitor = new DataSetMerger(editData, ds);
141            visitor.merge();
142            if (!visitor.getConflicts().isEmpty()) {
143                editData.getConflicts().add(visitor.getConflicts());
144            }
145            final RelationTreeModel model = (RelationTreeModel) getModel();
146            SwingUtilities.invokeLater(() -> model.refreshNode(path));
147        }
148
149        @Override
150        protected void realRun() throws SAXException, IOException, OsmTransferException {
151            try {
152                OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true);
153                ds = reader.parseOsm(progressMonitor
154                        .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
155            } catch (OsmTransferException e) {
156                if (canceled) {
157                    Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
158                    return;
159                }
160                this.lastException = e;
161            }
162        }
163    }
164}