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.io.IOException;
007import java.util.ArrayList;
008import java.util.List;
009
010import javax.swing.JOptionPane;
011import javax.swing.SwingUtilities;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.data.osm.DataSet;
015import org.openstreetmap.josm.data.osm.DataSetMerger;
016import org.openstreetmap.josm.data.osm.Relation;
017import org.openstreetmap.josm.gui.PleaseWaitRunnable;
018import org.openstreetmap.josm.gui.layer.OsmDataLayer;
019import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
020import org.openstreetmap.josm.io.OsmApi;
021import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
022import org.openstreetmap.josm.io.OsmTransferException;
023import org.openstreetmap.josm.tools.CheckParameterUtil;
024import org.xml.sax.SAXException;
025
026/**
027 * This is an asynchronous task for loading the parents of a given relation.
028 *
029 * Typical usage:
030 * <pre>
031 *  final ParentRelationLoadingTask task = new ParentRelationLoadingTask(
032 *                   child,   // the child relation
033 *                   Main.getLayerManager().getEditLayer(), // the edit layer
034 *                   true,  // load fully
035 *                   new PleaseWaitProgressMonitor()  // a progress monitor
036 *   );
037 *   task.setContinuation(
038 *       new Runnable() {
039 *          public void run() {
040 *              if (task.isCanceled() || task.hasError())
041 *                  return;
042 *              List&lt;Relation&gt; parents = task.getParents();
043 *              // do something with the parent relations
044 *       }
045 *   );
046 *
047 *   // start the task
048 *   Main.worker.submit(task);
049 * </pre>
050 *
051 */
052public class ParentRelationLoadingTask extends PleaseWaitRunnable {
053    private boolean canceled;
054    private Exception lastException;
055    private DataSet referrers;
056    private final boolean full;
057    private final OsmDataLayer layer;
058    private final Relation child;
059    private final List<Relation> parents;
060    private Runnable continuation;
061
062    /**
063     * Creates a new task for asynchronously downloading the parents of a child relation.
064     *
065     * @param child the child relation. Must not be null. Must have an id &gt; 0.
066     * @param layer  the OSM data layer. Must not be null.
067     * @param full if true, parent relations are fully downloaded (i.e. with their members)
068     * @param monitor the progress monitor to be used
069     *
070     * @throws IllegalArgumentException if child is null
071     * @throws IllegalArgumentException if layer is null
072     * @throws IllegalArgumentException if child.getId() == 0
073     */
074    public ParentRelationLoadingTask(Relation child, OsmDataLayer layer, boolean full, PleaseWaitProgressMonitor monitor) {
075        super(tr("Download referring relations"), monitor, false /* don't ignore exception */);
076        CheckParameterUtil.ensureValidPrimitiveId(child, "child");
077        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
078        referrers = null;
079        this.layer = layer;
080        parents = new ArrayList<>();
081        this.child = child;
082        this.full = full;
083    }
084
085    /**
086     * Set a continuation which is called upon the job finished.
087     *
088     * @param continuation the continuation
089     */
090    public void setContinuation(Runnable continuation) {
091        this.continuation = continuation;
092    }
093
094    /**
095     * Replies true if this has been canceled by the user.
096     *
097     * @return true if this has been canceled by the user.
098     */
099    public boolean isCanceled() {
100        return canceled;
101    }
102
103    /**
104     * Replies true if an exception has been caught during the execution of this task.
105     *
106     * @return true if an exception has been caught during the execution of this task.
107     */
108    public boolean hasError() {
109        return lastException != null;
110    }
111
112    protected OsmDataLayer getLayer() {
113        return layer;
114    }
115
116    public List<Relation> getParents() {
117        return parents;
118    }
119
120    @Override
121    protected void cancel() {
122        canceled = true;
123        OsmApi.getOsmApi().cancel();
124    }
125
126    protected void showLastException() {
127        String msg = lastException.getMessage();
128        if (msg == null) {
129            msg = lastException.toString();
130        }
131        JOptionPane.showMessageDialog(
132                Main.parent,
133                msg,
134                tr("Error"),
135                JOptionPane.ERROR_MESSAGE
136        );
137    }
138
139    @Override
140    protected void finish() {
141        if (canceled) return;
142        if (lastException != null) {
143            showLastException();
144            return;
145        }
146        parents.clear();
147        for (Relation parent : referrers.getRelations()) {
148            parents.add((Relation) getLayer().data.getPrimitiveById(parent));
149        }
150        if (continuation != null) {
151            continuation.run();
152        }
153    }
154
155    @Override
156    protected void realRun() throws SAXException, IOException, OsmTransferException {
157        try {
158            progressMonitor.indeterminateSubTask(null);
159            OsmServerBackreferenceReader reader = new OsmServerBackreferenceReader(child, full);
160            referrers = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
161            if (referrers != null) {
162                final DataSetMerger visitor = new DataSetMerger(getLayer().data, referrers);
163                visitor.merge();
164
165                // copy the merged layer's data source info
166                getLayer().data.dataSources.addAll(referrers.dataSources);
167                // FIXME: this is necessary because there are  dialogs listening
168                // for DataChangeEvents which manipulate Swing components on this thread.
169                //
170                SwingUtilities.invokeLater(
171                        new Runnable() {
172                            @Override
173                            public void run() {
174                                getLayer().onPostDownloadFromServer();
175                            }
176                        }
177                );
178
179                if (visitor.getConflicts().isEmpty())
180                    return;
181                getLayer().getConflicts().add(visitor.getConflicts());
182                JOptionPane.showMessageDialog(
183                        Main.parent,
184                        tr("There were {0} conflicts during import.",
185                                visitor.getConflicts().size()),
186                                tr("Warning"),
187                                JOptionPane.WARNING_MESSAGE
188                );
189            }
190        } catch (OsmTransferException e) {
191            if (canceled) {
192                Main.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
193                return;
194            }
195            lastException = e;
196        }
197    }
198}