001    package org.openstreetmap.josm.actions.downloadtasks;
002    
003    import java.util.Date;
004    import java.util.HashMap;
005    import java.util.Iterator;
006    import java.util.List;
007    import java.util.Map;
008    import java.util.concurrent.Future;
009    
010    import org.openstreetmap.josm.Main;
011    import org.openstreetmap.josm.data.Bounds;
012    import org.openstreetmap.josm.data.osm.DataSet;
013    import org.openstreetmap.josm.data.osm.Node;
014    import org.openstreetmap.josm.data.osm.NodeData;
015    import org.openstreetmap.josm.data.osm.OsmPrimitive;
016    import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
017    import org.openstreetmap.josm.data.osm.PrimitiveData;
018    import org.openstreetmap.josm.data.osm.PrimitiveId;
019    import org.openstreetmap.josm.data.osm.RelationData;
020    import org.openstreetmap.josm.data.osm.RelationMemberData;
021    import org.openstreetmap.josm.data.osm.WayData;
022    import org.openstreetmap.josm.data.osm.history.History;
023    import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
024    import org.openstreetmap.josm.data.osm.history.HistoryDataSetListener;
025    import org.openstreetmap.josm.data.osm.history.HistoryNode;
026    import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
027    import org.openstreetmap.josm.data.osm.history.HistoryRelation;
028    import org.openstreetmap.josm.data.osm.history.HistoryWay;
029    import org.openstreetmap.josm.gui.history.HistoryLoadTask;
030    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
031    import org.openstreetmap.josm.io.OsmServerLocationReader;
032    import org.openstreetmap.josm.io.OsmServerReader;
033    import org.openstreetmap.josm.io.OsmTransferException;
034    
035    public class DownloadOsmChangeTask extends DownloadOsmTask {
036    
037        @Override
038        public boolean acceptsUrl(String url) {
039            return url != null && (
040                    url.matches("http://.*/api/0.6/changeset/\\p{Digit}+/download") // OSM API 0.6 changesets
041                 || url.matches("http://.*/.*\\.osc")                               // Remote .osc files
042                    );
043        }
044    
045        /* (non-Javadoc)
046         * @see org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask#download(boolean, org.openstreetmap.josm.data.Bounds, org.openstreetmap.josm.gui.progress.ProgressMonitor)
047         */
048        @Override
049        public Future<?> download(boolean newLayer, Bounds downloadArea,
050                ProgressMonitor progressMonitor) {
051            return null;
052        }
053    
054        /* (non-Javadoc)
055         * @see org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask#loadUrl(boolean, java.lang.String, org.openstreetmap.josm.gui.progress.ProgressMonitor)
056         */
057        @Override
058        public Future<?> loadUrl(boolean new_layer, String url,
059                ProgressMonitor progressMonitor) {
060            downloadTask = new DownloadTask(new_layer,
061                    new OsmServerLocationReader(url),
062                    progressMonitor);
063            // Extract .osc filename from URL to set the new layer name
064            extractOsmFilename("http://.*/(.*\\.osc)", url);
065            return Main.worker.submit(downloadTask);
066        }
067    
068        protected class DownloadTask extends DownloadOsmTask.DownloadTask {
069    
070            public DownloadTask(boolean newLayer, OsmServerReader reader,
071                    ProgressMonitor progressMonitor) {
072                super(newLayer, reader, progressMonitor);
073            }
074    
075            /* (non-Javadoc)
076             * @see org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask.DownloadTask#parseDataSet()
077             */
078            @Override
079            protected DataSet parseDataSet() throws OsmTransferException {
080                return reader.parseOsmChange(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
081            }
082    
083            /* (non-Javadoc)
084             * @see org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask.DownloadTask#finish()
085             */
086            @Override
087            protected void finish() {
088                super.finish();
089                if (isFailed() || isCanceled() || downloadedData == null)
090                    return; // user canceled download or error occurred
091                try {
092                    // A changeset does not contain all referred primitives, this is the map of incomplete ones
093                    // For each incomplete primitive, we'll have to get its state at date it was referred
094                    Map<OsmPrimitive, Date> toLoad = new HashMap<OsmPrimitive, Date>();
095                    for (OsmPrimitive p : downloadedData.allNonDeletedPrimitives()) {
096                        if (p.isIncomplete()) {
097                            Date timestamp = null;
098                            for (OsmPrimitive ref : p.getReferrers()) {
099                                if (!ref.isTimestampEmpty()) {
100                                    timestamp = ref.getTimestamp();
101                                    break;
102                                }
103                            }
104                            toLoad.put(p, timestamp);
105                        }
106                    }
107                    if (isCanceled()) return;
108                    // Let's load all required history
109                    Main.worker.submit(new HistoryLoaderAndListener(toLoad));
110                } catch (Exception e) {
111                    rememberException(e);
112                    setFailed(true);
113                }
114            }
115        }
116        
117        /**
118         * Loads history and updates incomplete primitives.
119         */
120        private static class HistoryLoaderAndListener extends HistoryLoadTask implements HistoryDataSetListener {
121    
122            private final Map<OsmPrimitive, Date> toLoad;
123    
124            public HistoryLoaderAndListener(Map<OsmPrimitive, Date> toLoad) {
125                this.toLoad = toLoad;
126                add(toLoad.keySet());
127                // Updating process is done after all history requests have been made
128                HistoryDataSet.getInstance().addHistoryDataSetListener(this);
129            }
130    
131            @Override
132            public void historyUpdated(HistoryDataSet source, PrimitiveId id) {
133                Map<OsmPrimitive, Date> toLoadNext = new HashMap<OsmPrimitive, Date>();
134                for (Iterator<OsmPrimitive> it = toLoad.keySet().iterator(); it.hasNext();) {
135                    OsmPrimitive p = it.next();
136                    History history = source.getHistory(p.getPrimitiveId());
137                    Date date = toLoad.get(p);
138                    // If the history has been loaded and a timestamp is known
139                    if (history != null && date != null) {
140                        // Lookup for the primitive version at the specified timestamp
141                        HistoryOsmPrimitive hp = history.getByDate(date);
142                        if (hp != null) {
143                            PrimitiveData data = null;
144    
145                            switch (p.getType()) {
146                            case NODE:
147                                data = new NodeData();
148                                ((NodeData)data).setCoor(((HistoryNode)hp).getCoords());
149                                break;
150                            case WAY:
151                                data = new WayData();
152                                List<Long> nodeIds = ((HistoryWay)hp).getNodes();
153                                ((WayData)data).setNodes(nodeIds);
154                                // Find incomplete nodes to load at next run
155                                for (Long nodeId : nodeIds) {
156                                    if (p.getDataSet().getPrimitiveById(nodeId, OsmPrimitiveType.NODE) == null) {
157                                        Node n = new Node(nodeId);
158                                        p.getDataSet().addPrimitive(n);
159                                        toLoadNext.put(n, date);
160                                    }
161                                }
162                                break;
163                            case RELATION:
164                                data = new RelationData();
165                                List<RelationMemberData> members = ((HistoryRelation)hp).getMembers();
166                                ((RelationData)data).setMembers(members);
167                                break;
168                            default: throw new AssertionError("Unknown primitive type");
169                            }
170    
171                            data.setUser(hp.getUser());
172                            try {
173                                data.setVisible(hp.isVisible());
174                            } catch (IllegalStateException e) {
175                                System.err.println("Cannot change visibility for "+p+": "+e.getMessage());
176                            }
177                            data.setTimestamp(hp.getTimestamp());
178                            data.setKeys(hp.getTags());
179                            data.setOsmId(hp.getChangesetId(), (int) hp.getVersion());
180    
181                            // Load the history data
182                            try {
183                                p.load(data);
184                                // Forget this primitive
185                                it.remove();
186                            } catch (AssertionError e) {
187                                System.err.println("Cannot load "+p + ": " + e.getMessage());
188                            }
189                        }
190                    }
191                }
192                source.removeHistoryDataSetListener(this);
193                if (toLoadNext.isEmpty()) {
194                    // No more primitive to update. Processing is finished
195                    // Be sure all updated primitives are correctly drawn
196                    Main.map.repaint();
197                } else {
198                    // Some primitives still need to be loaded
199                    // Let's load all required history
200                    Main.worker.submit(new HistoryLoaderAndListener(toLoadNext));
201                }
202            }
203    
204            @Override
205            public void historyDataSetCleared(HistoryDataSet source) {
206            }
207        }
208    }