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.InputStream;
007import java.util.Arrays;
008
009import javax.xml.stream.XMLStreamConstants;
010import javax.xml.stream.XMLStreamException;
011
012import org.openstreetmap.josm.data.coor.LatLon;
013import org.openstreetmap.josm.data.osm.DataSet;
014import org.openstreetmap.josm.data.osm.NoteData;
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
016import org.openstreetmap.josm.gui.progress.ProgressMonitor;
017import org.openstreetmap.josm.tools.Pair;
018
019/**
020 * Reader for <a href="http://wiki.openstreetmap.org/wiki/OsmChange">OsmChange</a> file format.
021 */
022public class OsmChangeReader extends OsmReader {
023
024    /**
025     * List of possible actions.
026     */
027    private static final String[] ACTIONS = {"create", "modify", "delete"};
028
029    protected final NoteData noteData = new NoteData();
030
031    /**
032     * constructor (for private and subclasses use only)
033     *
034     * @see #parseDataSet(InputStream, ProgressMonitor)
035     */
036    protected OsmChangeReader() {
037        // Restricts visibility
038    }
039
040    @Override
041    protected void parseRoot() throws XMLStreamException {
042        if ("osmChange".equals(parser.getLocalName())) {
043            parseOsmChange();
044        } else {
045            parseUnknown();
046        }
047    }
048
049    private void parseOsmChange() throws XMLStreamException {
050        String v = parser.getAttributeValue(null, "version");
051        if (v == null) {
052            throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
053        }
054        if (!"0.6".equals(v)) {
055            throwException(tr("Unsupported version: {0}", v));
056        }
057        ds.setVersion(v);
058        while (parser.hasNext()) {
059            int event = parser.next();
060            if (event == XMLStreamConstants.START_ELEMENT) {
061                if (Arrays.asList(ACTIONS).contains(parser.getLocalName())) {
062                    parseCommon(parser.getLocalName());
063                } else {
064                    parseUnknown();
065                }
066            } else if (event == XMLStreamConstants.END_ELEMENT) {
067                return;
068            }
069        }
070    }
071
072    private void parseCommon(String action) throws XMLStreamException {
073        while (parser.hasNext()) {
074            int event = parser.next();
075            if (event == XMLStreamConstants.START_ELEMENT) {
076                OsmPrimitive p = null;
077                switch (parser.getLocalName()) {
078                case "node":
079                    p = parseNode();
080                    break;
081                case "way":
082                    p = parseWay();
083                    break;
084                case "relation":
085                    p = parseRelation();
086                    break;
087                case "note":
088                    parseNote();
089                    break;
090                default:
091                    parseUnknown();
092                }
093                if (p != null && action != null) {
094                    if ("modify".equals(action)) {
095                        p.setModified(true);
096                    } else if ("delete".equals(action)) {
097                        p.setDeleted(true);
098                    }
099                }
100            } else if (event == XMLStreamConstants.END_ELEMENT) {
101                return;
102            }
103        }
104    }
105
106    private void parseNote() throws XMLStreamException {
107        LatLon location = NoteReader.parseLatLon(s -> parser.getAttributeValue(null, s));
108        String text = null;
109        while (parser.hasNext()) {
110            int event = parser.next();
111            if (event == XMLStreamConstants.START_ELEMENT) {
112                switch (parser.getLocalName()) {
113                case "comment":
114                    text = parser.getAttributeValue(null, "text");
115                    jumpToEnd();
116                    break;
117                default:
118                    parseUnknown();
119                }
120            } else if (event == XMLStreamConstants.END_ELEMENT) {
121                break;
122            }
123        }
124        if (location != null && text != null && !text.isEmpty()) {
125            noteData.createNote(location, text);
126        }
127    }
128
129    /**
130     * Replies the parsed notes data.
131     * @return the parsed notes data
132     * @since 14101
133     */
134    public final NoteData getNoteData() {
135        return noteData;
136    }
137
138    /**
139     * Parse the given input source and return the dataset.
140     *
141     * @param source the source input stream. Must not be <code>null</code>.
142     * @param progressMonitor  the progress monitor. If <code>null</code>,
143     * {@link org.openstreetmap.josm.gui.progress.NullProgressMonitor#INSTANCE} is assumed
144     *
145     * @return the dataset with the parsed data
146     * @throws IllegalDataException if the an error was found while parsing the data from the source
147     * @throws IllegalArgumentException if source is <code>null</code>
148     */
149    public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
150        return new OsmChangeReader().doParseDataSet(source, progressMonitor);
151    }
152
153    /**
154     * Parse the given input source and return the dataset and notes, if any (OsmAnd extends the osmChange format by adding notes).
155     *
156     * @param source the source input stream. Must not be <code>null</code>.
157     * @param progressMonitor  the progress monitor. If <code>null</code>,
158     * {@link org.openstreetmap.josm.gui.progress.NullProgressMonitor#INSTANCE} is assumed
159     *
160     * @return the dataset with the parsed data
161     * @throws IllegalDataException if the an error was found while parsing the data from the source
162     * @throws IllegalArgumentException if source is <code>null</code>
163     * @since 14101
164     */
165    public static Pair<DataSet, NoteData> parseDataSetAndNotes(InputStream source, ProgressMonitor progressMonitor)
166            throws IllegalDataException {
167        OsmChangeReader osmChangeReader = new OsmChangeReader();
168        osmChangeReader.doParseDataSet(source, progressMonitor);
169        return new Pair<>(osmChangeReader.getDataSet(), osmChangeReader.getNoteData());
170    }
171}