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