001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.actions.downloadtasks;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    import static org.openstreetmap.josm.tools.I18n.trn;
006    
007    import java.io.IOException;
008    import java.text.MessageFormat;
009    import java.util.Collection;
010    import java.util.Set;
011    import java.util.HashSet;
012    import java.util.HashMap;
013    import java.util.Map;
014    import java.util.Map.Entry;
015    
016    import javax.swing.JOptionPane;
017    import javax.swing.SwingUtilities;
018    
019    import org.openstreetmap.josm.Main;
020    import org.openstreetmap.josm.data.osm.DataSet;
021    import org.openstreetmap.josm.data.osm.DataSetMerger;
022    import org.openstreetmap.josm.data.osm.Node;
023    import org.openstreetmap.josm.data.osm.OsmPrimitive;
024    import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
025    import org.openstreetmap.josm.data.osm.PrimitiveId;
026    import org.openstreetmap.josm.data.osm.Way;
027    import org.openstreetmap.josm.gui.PleaseWaitRunnable;
028    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
029    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
030    import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
031    import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
032    import org.openstreetmap.josm.io.OsmServerReader;
033    import org.openstreetmap.josm.io.OsmTransferException;
034    import org.openstreetmap.josm.tools.CheckParameterUtil;
035    import org.openstreetmap.josm.tools.ExceptionUtil;
036    import org.xml.sax.SAXException;
037    
038    /**
039     * The asynchronous task for downloading referring primitives
040     *
041     */
042    public class DownloadReferrersTask extends PleaseWaitRunnable {
043        private boolean canceled;
044        private Exception lastException;
045        private OsmServerReader reader;
046        /** the target layer */
047        private OsmDataLayer targetLayer;
048        /** the collection of child primitives */
049        private Map<Long, OsmPrimitiveType> children;
050        /** the parents */
051        private DataSet parents;
052    
053        /**
054         * constructor
055         *
056         * @param targetLayer  the target layer for the downloaded primitives. Must not be null.
057         * @param children the collection of child primitives for which parents are to be downloaded
058         *
059         */
060        public DownloadReferrersTask(OsmDataLayer targetLayer, Collection<OsmPrimitive> children) {
061            super("Download referrers", false /* don't ignore exception*/);
062            CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
063            canceled = false;
064            this.children = new HashMap<Long, OsmPrimitiveType>();
065            if (children != null) {
066                for (OsmPrimitive p: children) {
067                    if (! p.isNew()) {
068                        this.children.put(p.getId(), OsmPrimitiveType.from(p));
069                    }
070                }
071            }
072            this.targetLayer = targetLayer;
073            parents = new DataSet();
074        }
075    
076        /**
077         * constructor
078         *
079         * @param targetLayer  the target layer for the downloaded primitives. Must not be null.
080         * @param primitives  the collection of children for which parents are to be downloaded. Children
081         * are specified by their id and  their type.
082         *
083         */
084        public DownloadReferrersTask(OsmDataLayer targetLayer, Map<Long, OsmPrimitiveType> children) {
085            super("Download referrers", false /* don't ignore exception*/);
086            CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
087            canceled = false;
088            this.children = new HashMap<Long, OsmPrimitiveType>();
089            if (children != null) {
090                for (Entry<Long, OsmPrimitiveType> entry : children.entrySet()) {
091                    if (entry.getKey() > 0 && entry.getValue() != null) {
092                        children.put(entry.getKey(), entry.getValue());
093                    }
094                }
095            }
096            this.targetLayer = targetLayer;
097            parents = new DataSet();
098        }
099    
100        /**
101         * constructor
102         *
103         * @param targetLayer  the target layer. Must not be null.
104         * @param id the primitive id. id > 0 required.
105         * @param type the primitive type. type != null required
106         * @exception IllegalArgumentException thrown if id <= 0
107         * @exception IllegalArgumentException thrown if type == null
108         * @exception IllegalArgumentException thrown if targetLayer == null
109         *
110         */
111        public DownloadReferrersTask(OsmDataLayer targetLayer, long id, OsmPrimitiveType type) throws IllegalArgumentException {
112            super("Download referrers", false /* don't ignore exception*/);
113            CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
114            if (id <= 0)
115                throw new IllegalArgumentException(MessageFormat.format("Id > 0 required, got {0}", id));
116            CheckParameterUtil.ensureParameterNotNull(type, "type");
117            canceled = false;
118            this.children = new HashMap<Long, OsmPrimitiveType>();
119            this.children.put(id, type);
120            this.targetLayer = targetLayer;
121            parents = new DataSet();
122        }
123    
124        /**
125         * constructor
126         *
127         * @param targetLayer  the target layer. Must not be null.
128         * @param primitiveId a PrimitiveId object.
129         * @exception IllegalArgumentException thrown if id <= 0
130         * @exception IllegalArgumentException thrown if targetLayer == null
131         *
132         */
133        public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId) throws IllegalArgumentException {
134            super("Download referrers", false /* don't ignore exception*/);
135            CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
136            if (primitiveId.isNew())
137                throw new IllegalArgumentException(MessageFormat.format("Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId()));
138            canceled = false;
139            this.children = new HashMap<Long, OsmPrimitiveType>();
140            this.children.put(primitiveId.getUniqueId(), primitiveId.getType());
141            this.targetLayer = targetLayer;
142            parents = new DataSet();
143        }
144    
145        @Override
146        protected void cancel() {
147            canceled = true;
148            synchronized(this) {
149                if (reader != null) {
150                    reader.cancel();
151                }
152            }
153        }
154    
155        @Override
156        protected void finish() {
157            if (canceled)
158                return;
159            if (lastException != null) {
160                ExceptionUtil.explainException(lastException);
161                return;
162            }
163    
164            DataSetMerger visitor = new DataSetMerger(targetLayer.data, parents);
165            visitor.merge();
166            SwingUtilities.invokeLater(
167                    new Runnable() {
168                        public void run() {
169                            targetLayer.onPostDownloadFromServer();
170                            Main.map.mapView.repaint();
171                        }
172                    }
173            );
174            if (visitor.getConflicts().isEmpty())
175                return;
176            targetLayer.getConflicts().add(visitor.getConflicts());
177            JOptionPane.showMessageDialog(
178                    Main.parent,
179                    trn("There was {0} conflict during import.",
180                            "There were {0} conflicts during import.",
181                            visitor.getConflicts().size(),
182                            visitor.getConflicts().size()
183                    ),
184                    trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()),
185                    JOptionPane.WARNING_MESSAGE
186            );
187            Main.map.conflictDialog.unfurlDialog();
188            Main.map.repaint();
189        }
190    
191        protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException{
192            reader = new OsmServerBackreferenceReader(id, type);
193            DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
194            synchronized(this) { // avoid race condition in cancel()
195                reader = null;
196            }
197            Collection<Way> ways = ds.getWays();
198    
199            DataSetMerger merger;
200            if (!ways.isEmpty()) {
201                Set<Node> nodes = new HashSet<Node>();
202                for (Way w: ways) {
203                    // Ensure each node is only listed once
204                    nodes.addAll(w.getNodes());
205                }
206                // Don't retrieve any nodes we've already grabbed
207                nodes.removeAll(targetLayer.data.getNodes());
208                if (!nodes.isEmpty()) {
209                    reader = new MultiFetchServerObjectReader();
210                    ((MultiFetchServerObjectReader)reader).append(nodes);
211                    DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
212                    synchronized(this) { // avoid race condition in cancel()
213                        reader = null;
214                    }
215                    merger = new DataSetMerger(ds, wayNodes);
216                    merger.merge();
217                }
218            }
219            merger = new DataSetMerger(parents, ds);
220            merger.merge();
221        }
222    
223        @Override
224        protected void realRun() throws SAXException, IOException, OsmTransferException {
225            try {
226                progressMonitor.setTicksCount(children.size());
227                int i=1;
228                for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) {
229                    if (canceled)
230                        return;
231                    String msg = "";
232                    switch(entry.getValue()) {
233                    case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1,children.size(), entry.getKey()); break;
234                    case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1,children.size(), entry.getKey()); break;
235                    case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1,children.size(), entry.getKey()); break;
236                    }
237                    progressMonitor.subTask(msg);
238                    downloadParents(entry.getKey(), entry.getValue(), progressMonitor);
239                    i++;
240                }
241            } catch(Exception e) {
242                if (canceled)
243                    return;
244                lastException = e;
245            }
246        }
247    }