001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.io.StringReader; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.Date; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.Map; 014import java.util.Set; 015 016import javax.xml.parsers.ParserConfigurationException; 017 018import org.openstreetmap.josm.data.osm.Changeset; 019import org.openstreetmap.josm.data.osm.DataSet; 020import org.openstreetmap.josm.data.osm.OsmPrimitive; 021import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 022import org.openstreetmap.josm.data.osm.PrimitiveId; 023import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 024import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 025import org.openstreetmap.josm.gui.progress.ProgressMonitor; 026import org.openstreetmap.josm.tools.CheckParameterUtil; 027import org.openstreetmap.josm.tools.Utils; 028import org.openstreetmap.josm.tools.XmlParsingException; 029import org.xml.sax.Attributes; 030import org.xml.sax.InputSource; 031import org.xml.sax.Locator; 032import org.xml.sax.SAXException; 033import org.xml.sax.helpers.DefaultHandler; 034 035public class DiffResultProcessor { 036 037 private static class DiffResultEntry { 038 private long newId; 039 private int newVersion; 040 } 041 042 /** 043 * mapping from old id to new id and version, the result of parsing the diff result 044 * replied by the server 045 */ 046 private final Map<PrimitiveId, DiffResultEntry> diffResults = new HashMap<>(); 047 /** 048 * the set of processed primitives *after* the new id, the new version and the new changeset id is set 049 */ 050 private final Set<OsmPrimitive> processed; 051 /** 052 * the collection of primitives being uploaded 053 */ 054 private final Collection<? extends OsmPrimitive> primitives; 055 056 /** 057 * Creates a diff result reader 058 * 059 * @param primitives the collection of primitives which have been uploaded. If null, 060 * assumes an empty collection. 061 */ 062 public DiffResultProcessor(Collection<? extends OsmPrimitive> primitives) { 063 if (primitives == null) { 064 primitives = Collections.emptyList(); 065 } 066 this.primitives = primitives; 067 this.processed = new HashSet<>(); 068 } 069 070 /** 071 * Parse the response from a diff upload to the OSM API. 072 * 073 * @param diffUploadResponse the response. Must not be null. 074 * @param progressMonitor a progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null 075 * @throws IllegalArgumentException if diffUploadRequest is null 076 * @throws XmlParsingException if the diffUploadRequest can't be parsed successfully 077 * 078 */ 079 public void parse(String diffUploadResponse, ProgressMonitor progressMonitor) throws XmlParsingException { 080 if (progressMonitor == null) { 081 progressMonitor = NullProgressMonitor.INSTANCE; 082 } 083 CheckParameterUtil.ensureParameterNotNull(diffUploadResponse, "diffUploadResponse"); 084 try { 085 progressMonitor.beginTask(tr("Parsing response from server...")); 086 InputSource inputSource = new InputSource(new StringReader(diffUploadResponse)); 087 Utils.parseSafeSAX(inputSource, new Parser()); 088 } catch (XmlParsingException e) { 089 throw e; 090 } catch (IOException | ParserConfigurationException | SAXException e) { 091 throw new XmlParsingException(e); 092 } finally { 093 progressMonitor.finishTask(); 094 } 095 } 096 097 /** 098 * Postprocesses the diff result read and parsed from the server. 099 * 100 * Uploaded objects are assigned their new id (if they got assigned a new 101 * id by the server), their new version (if the version was incremented), 102 * and the id of the changeset to which they were uploaded. 103 * 104 * @param cs the current changeset. Ignored if null. 105 * @param monitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null 106 * @return the collection of processed primitives 107 */ 108 protected Set<OsmPrimitive> postProcess(Changeset cs, ProgressMonitor monitor) { 109 if (monitor == null) { 110 monitor = NullProgressMonitor.INSTANCE; 111 } 112 DataSet ds = null; 113 if (!primitives.isEmpty()) { 114 ds = primitives.iterator().next().getDataSet(); 115 } 116 if (ds != null) { 117 ds.beginUpdate(); 118 } 119 try { 120 monitor.beginTask("Postprocessing uploaded data ..."); 121 monitor.setTicksCount(primitives.size()); 122 monitor.setTicks(0); 123 for (OsmPrimitive p : primitives) { 124 monitor.worked(1); 125 DiffResultEntry entry = diffResults.get(p.getPrimitiveId()); 126 if (entry == null) { 127 continue; 128 } 129 processed.add(p); 130 if (!p.isDeleted()) { 131 p.setOsmId(entry.newId, entry.newVersion); 132 p.setVisible(true); 133 } else { 134 p.setVisible(false); 135 } 136 if (cs != null && !cs.isNew()) { 137 p.setChangesetId(cs.getId()); 138 p.setUser(cs.getUser()); 139 // TODO is there a way to obtain the timestamp for non-closed changesets? 140 p.setTimestamp(Utils.firstNonNull(cs.getClosedAt(), new Date())); 141 } 142 } 143 return processed; 144 } finally { 145 if (ds != null) { 146 ds.endUpdate(); 147 } 148 monitor.finishTask(); 149 } 150 } 151 152 private class Parser extends DefaultHandler { 153 private Locator locator; 154 155 @Override 156 public void setDocumentLocator(Locator locator) { 157 this.locator = locator; 158 } 159 160 protected void throwException(String msg) throws XmlParsingException { 161 throw new XmlParsingException(msg).rememberLocation(locator); 162 } 163 164 @Override 165 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 166 try { 167 switch (qName) { 168 case "diffResult": 169 // the root element, ignore 170 break; 171 case "node": 172 case "way": 173 case "relation": 174 PrimitiveId id = new SimplePrimitiveId( 175 Long.parseLong(atts.getValue("old_id")), 176 OsmPrimitiveType.fromApiTypeName(qName) 177 ); 178 DiffResultEntry entry = new DiffResultEntry(); 179 if (atts.getValue("new_id") != null) { 180 entry.newId = Long.parseLong(atts.getValue("new_id")); 181 } 182 if (atts.getValue("new_version") != null) { 183 entry.newVersion = Integer.parseInt(atts.getValue("new_version")); 184 } 185 diffResults.put(id, entry); 186 break; 187 default: 188 throwException(tr("Unexpected XML element with name ''{0}''", qName)); 189 } 190 } catch (NumberFormatException e) { 191 throw new XmlParsingException(e).rememberLocation(locator); 192 } 193 } 194 } 195}