001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.io;
003    
004    import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    
007    import java.io.IOException;
008    import java.util.List;
009    import java.util.Set;
010    
011    import org.openstreetmap.josm.actions.AutoScaleAction;
012    import org.openstreetmap.josm.data.osm.DataSet;
013    import org.openstreetmap.josm.data.osm.DataSetMerger;
014    import org.openstreetmap.josm.data.osm.Node;
015    import org.openstreetmap.josm.data.osm.OsmPrimitive;
016    import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
017    import org.openstreetmap.josm.data.osm.PrimitiveId;
018    import org.openstreetmap.josm.data.osm.Relation;
019    import org.openstreetmap.josm.data.osm.Way;
020    import org.openstreetmap.josm.gui.ExceptionDialogUtil;
021    import org.openstreetmap.josm.gui.PleaseWaitRunnable;
022    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
023    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
024    import org.openstreetmap.josm.gui.util.GuiHelper;
025    import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
026    import org.openstreetmap.josm.io.OsmServerObjectReader;
027    import org.openstreetmap.josm.io.OsmTransferException;
028    import org.xml.sax.SAXException;
029    
030    public class DownloadPrimitivesTask extends PleaseWaitRunnable {
031        private DataSet ds;
032        private boolean canceled;
033        private Exception lastException;
034        private List<PrimitiveId> ids;
035    
036        private Set<PrimitiveId> missingPrimitives;
037    
038        private OsmDataLayer layer;
039        private boolean fullRelation;
040        private MultiFetchServerObjectReader multiObjectReader;
041        private OsmServerObjectReader objectReader;
042    
043        /**
044         * Creates the  task
045         *
046         * @param layer the layer in which primitives are updated. Must not be null.
047         * @param toUpdate a collection of primitives to update from the server. Set to
048         * the empty collection if null.
049         * @param fullRelation true if a full download is required, i.e.,
050         * a download including the immediate children of a relation.
051         * @throws IllegalArgumentException thrown if layer is null.
052         */
053        public DownloadPrimitivesTask(OsmDataLayer layer, List<PrimitiveId>  ids, boolean fullRelation) throws IllegalArgumentException {
054            super(tr("Download objects"), false /* don't ignore exception */);
055            ensureParameterNotNull(layer, "layer");
056            this.ids = ids;
057            this.layer = layer;
058            this.fullRelation = fullRelation;
059        }
060    
061        @Override
062        protected void cancel() {
063            canceled = true;
064            synchronized(this) {
065                if (multiObjectReader != null) {
066                    multiObjectReader.cancel();
067                }
068                if (objectReader != null) {
069                    objectReader.cancel();
070                }
071            }
072        }
073    
074        @Override
075        protected void finish() {
076            if (canceled)
077                return;
078            if (lastException != null) {
079                ExceptionDialogUtil.explainException(lastException);
080                return;
081            }
082            GuiHelper.runInEDTAndWait(new Runnable() {
083                public void run() {
084                    layer.mergeFrom(ds);
085                    AutoScaleAction.zoomTo(ds.allPrimitives());
086                    layer.onPostDownloadFromServer();
087                }
088            });
089        }
090    
091        protected void initMultiFetchReader(MultiFetchServerObjectReader reader) {
092            getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to download ..."));
093            for (PrimitiveId id : ids) {
094                OsmPrimitive osm = layer.data.getPrimitiveById(id);
095                if (osm == null) {
096                    switch (id.getType()) {
097                        case NODE: 
098                            osm = new Node(id.getUniqueId());
099                            break;
100                        case WAY:
101                            osm = new Way(id.getUniqueId());
102                            break;
103                        case RELATION:
104                            osm = new Relation(id.getUniqueId());
105                            break;
106                        default: throw new AssertionError();
107                    }
108                }
109                reader.append(osm);
110            }
111        }
112    
113        @Override
114        protected void realRun() throws SAXException, IOException, OsmTransferException {
115            this.ds = new DataSet();
116            DataSet theirDataSet;
117            try {
118                synchronized(this) {
119                    if (canceled) return;
120                    multiObjectReader = new MultiFetchServerObjectReader();
121                }
122                initMultiFetchReader(multiObjectReader);
123                theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
124                missingPrimitives = multiObjectReader.getMissingPrimitives();
125                synchronized(this) {
126                    multiObjectReader = null;
127                }
128                DataSetMerger merger = new DataSetMerger(ds, theirDataSet);
129                merger.merge();
130                
131                // if incomplete relation members exist, download them too
132                for (Relation r : ds.getRelations()) {
133                    if (canceled) return;
134                    if (r.hasIncompleteMembers()) {
135                        synchronized(this) {
136                            if (canceled) return;
137                            objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation);
138                        }
139                        theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
140                        synchronized (this) {
141                            objectReader = null;
142                        }
143                        merger = new DataSetMerger(ds, theirDataSet);
144                        merger.merge();
145                    }
146                }
147    
148                // a way loaded with MultiFetch may have incomplete nodes because at least one of its
149                // nodes isn't present in the local data set. We therefore fully load all
150                // ways with incomplete nodes.
151                //
152                for (Way w : ds.getWays()) {
153                    if (canceled) return;
154                    if (w.hasIncompleteNodes()) {
155                        synchronized(this) {
156                            if (canceled) return;
157                            objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
158                        }
159                        theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
160                        synchronized (this) {
161                            objectReader = null;
162                        }
163                        merger = new DataSetMerger(ds, theirDataSet);
164                        merger.merge();
165                    }
166                }
167                
168            } catch(Exception e) {
169                if (canceled) return;
170                lastException = e;
171            }
172        }
173        
174        /**
175         * replies the set of ids of all primitives for which a fetch request to the
176         * server was submitted but which are not available from the server (the server
177         * replied a return code of 404)
178         *
179         * @return the set of ids of missing primitives
180         */
181        public Set<PrimitiveId> getMissingPrimitives() {
182            return missingPrimitives;
183        }
184    
185    }