001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.dialogs.relation;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.IOException;
007    import java.util.ArrayList;
008    import java.util.List;
009    
010    import javax.swing.JOptionPane;
011    import javax.swing.SwingUtilities;
012    
013    import org.openstreetmap.josm.Main;
014    import org.openstreetmap.josm.data.osm.DataSet;
015    import org.openstreetmap.josm.data.osm.DataSetMerger;
016    import org.openstreetmap.josm.data.osm.DataSource;
017    import org.openstreetmap.josm.data.osm.Relation;
018    import org.openstreetmap.josm.gui.PleaseWaitRunnable;
019    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
020    import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
021    import org.openstreetmap.josm.io.OsmApi;
022    import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
023    import org.openstreetmap.josm.io.OsmTransferException;
024    import org.openstreetmap.josm.tools.CheckParameterUtil;
025    import 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<Relation> 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     */
053    public 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 ArrayList<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 > 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<Relation>();
082            this.child = child;
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                    for (DataSource src : referrers.dataSources) {
167                        getLayer().data.dataSources.add(src);
168                    }
169                    // FIXME: this is necessary because there are  dialogs listening
170                    // for DataChangeEvents which manipulate Swing components on this
171                    // thread.
172                    //
173                    SwingUtilities.invokeLater(
174                            new Runnable() {
175                                public void run() {
176                                    getLayer().onPostDownloadFromServer();
177                                }
178                            }
179                    );
180    
181                    if (visitor.getConflicts().isEmpty())
182                        return;
183                    getLayer().getConflicts().add(visitor.getConflicts());
184                    JOptionPane.showMessageDialog(
185                            Main.parent,
186                            tr("There were {0} conflicts during import.",
187                                    visitor.getConflicts().size()),
188                                    tr("Warning"),
189                                    JOptionPane.WARNING_MESSAGE
190                    );
191                }
192            } catch(Exception e) {
193                if (canceled) {
194                    System.out.println(tr("Warning: Ignoring exception because task was canceled. Exception: {0}", e.toString()));
195                    return;
196                }
197                lastException = e;
198            }
199        }
200    }