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