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        setCellRenderer(new RelationTreeCellRenderer());
043        addTreeWillExpandListener(new LazyRelationLoader());
044    }
045
046    /**
047     * constructor
048     */
049    public RelationTree() {
050        super();
051        build();
052    }
053
054    /**
055     * constructor
056     * @param model the tree model
057     */
058    public RelationTree(RelationTreeModel model) {
059        super(model);
060        build();
061    }
062
063    /**
064     * replies the parent dialog this tree is embedded in.
065     *
066     * @return the parent dialog; null, if there is no parent dialog
067     */
068    protected Dialog getParentDialog() {
069        Component c = this;
070        while (c != null && !(c instanceof Dialog)) {
071            c = c.getParent();
072        }
073        return (Dialog) c;
074    }
075
076    /**
077     * An adapter for TreeWillExpand-events. If a node is to be expanded which is
078     * not loaded yet this will trigger asynchronous loading of the respective
079     * relation.
080     *
081     */
082    class LazyRelationLoader implements TreeWillExpandListener {
083
084        @Override
085        public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
086            // do nothing
087        }
088
089        @Override
090        public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
091            TreePath path = event.getPath();
092            Relation parent = (Relation) event.getPath().getLastPathComponent();
093            if (!parent.isIncomplete() || parent.isNew())
094                // we don't load complete  or new relations
095                return;
096            // launch the download task
097            MainApplication.worker.submit(new RelationLoader(getParentDialog(), parent, path));
098        }
099    }
100
101    /**
102     * Asynchronous download task for a specific relation
103     *
104     */
105    class RelationLoader extends PleaseWaitRunnable {
106        private boolean canceled;
107        private Exception lastException;
108        private final Relation relation;
109        private DataSet ds;
110        private final TreePath path;
111
112        RelationLoader(Dialog dialog, Relation relation, TreePath path) {
113            super(
114                    tr("Load relation"),
115                    new PleaseWaitProgressMonitor(
116                            dialog
117                    ),
118                    false /* don't ignore exceptions */
119            );
120            this.relation = relation;
121            this.path = path;
122        }
123
124        @Override
125        protected void cancel() {
126            OsmApi.getOsmApi().cancel();
127            this.canceled = true;
128        }
129
130        @Override
131        protected void finish() {
132            if (canceled)
133                return;
134            if (lastException != null) {
135                Logging.error(lastException);
136                return;
137            }
138            DataSet editData = MainApplication.getLayerManager().getEditDataSet();
139            DataSetMerger visitor = new DataSetMerger(editData, ds);
140            visitor.merge();
141            if (!visitor.getConflicts().isEmpty()) {
142                editData.getConflicts().add(visitor.getConflicts());
143            }
144            final RelationTreeModel model = (RelationTreeModel) getModel();
145            SwingUtilities.invokeLater(() -> model.refreshNode(path));
146        }
147
148        @Override
149        protected void realRun() throws SAXException, IOException, OsmTransferException {
150            try {
151                OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true);
152                ds = reader.parseOsm(progressMonitor
153                        .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
154            } catch (OsmTransferException e) {
155                if (canceled) {
156                    Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
157                    return;
158                }
159                this.lastException = e;
160            }
161        }
162    }
163}