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.Collection;
008import java.util.Map.Entry;
009
010import javax.json.Json;
011import javax.json.JsonArray;
012import javax.json.JsonNumber;
013import javax.json.JsonObject;
014import javax.json.JsonString;
015import javax.json.JsonValue;
016import javax.json.stream.JsonParser;
017import javax.json.stream.JsonParser.Event;
018
019import org.openstreetmap.josm.data.osm.DataSet;
020import org.openstreetmap.josm.data.osm.PrimitiveData;
021import org.openstreetmap.josm.data.osm.RelationData;
022import org.openstreetmap.josm.data.osm.RelationMemberData;
023import org.openstreetmap.josm.data.osm.Tagged;
024import org.openstreetmap.josm.data.osm.WayData;
025import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
026import org.openstreetmap.josm.gui.progress.ProgressMonitor;
027import org.openstreetmap.josm.tools.Logging;
028import org.openstreetmap.josm.tools.UncheckedParseException;
029
030/**
031 * Parser for the Osm API (JSON output). Read from an input stream and construct a dataset out of it.
032 *
033 * For each json element, there is a dedicated method.
034 * @since 14086
035 */
036public class OsmJsonReader extends AbstractReader {
037
038    protected JsonParser parser;
039
040    /**
041     * constructor (for private and subclasses use only)
042     *
043     * @see #parseDataSet(InputStream, ProgressMonitor)
044     */
045    protected OsmJsonReader() {
046        // Restricts visibility
047    }
048
049    protected void setParser(JsonParser parser) {
050        this.parser = parser;
051    }
052
053    protected void parse() throws IllegalDataException {
054        while (parser.hasNext()) {
055            Event event = parser.next();
056            if (event == Event.START_OBJECT) {
057                parseRoot(parser.getObject());
058            }
059        }
060        parser.close();
061    }
062
063    private void parseRoot(JsonObject object) throws IllegalDataException {
064        parseVersion(object.get("version").toString());
065        parseDownloadPolicy("download", object.getString("download", null));
066        parseUploadPolicy("upload", object.getString("upload", null));
067        parseLocked(object.getString("locked", null));
068        parseElements(object.getJsonArray("elements"));
069        parseRemark(object.getString("remark", null));
070    }
071
072    private void parseRemark(String remark) {
073        ds.setRemark(remark);
074    }
075
076    private void parseElements(JsonArray jsonArray) throws IllegalDataException {
077        for (JsonValue value : jsonArray) {
078            if (value instanceof JsonObject) {
079                JsonObject item = (JsonObject) value;
080                switch (item.getString("type")) {
081                case "node":
082                    parseNode(item);
083                    break;
084                case "way":
085                    parseWay(item);
086                    break;
087                case "relation":
088                    parseRelation(item);
089                    break;
090                default:
091                    parseUnknown(item);
092                }
093            } else {
094                throw new IllegalDataException("Unexpected JSON item: " + value);
095            }
096        }
097    }
098
099    /**
100     * Read out the common attributes and put them into current OsmPrimitive.
101     * @param item current JSON object
102     * @param current primitive to update
103     * @throws IllegalDataException if there is an error processing the underlying JSON source
104     */
105    private void readCommon(JsonObject item, PrimitiveData current) throws IllegalDataException {
106        try {
107            parseId(current, item.getJsonNumber("id").longValue());
108            parseTimestamp(current, item.getString("timestamp", null));
109            JsonNumber uid = item.getJsonNumber("uid");
110            if (uid != null) {
111                parseUser(current, item.getString("user", null), uid.longValue());
112            }
113            parseVisible(current, item.getString("visible", null));
114            JsonNumber version = item.getJsonNumber("version");
115            if (version != null) {
116                parseVersion(current, version.intValue());
117            }
118            parseAction(current, item.getString("action", null));
119            JsonNumber changeset = item.getJsonNumber("changeset");
120            if (changeset != null) {
121                parseChangeset(current, changeset.intValue());
122            }
123        } catch (UncheckedParseException e) {
124            throw new IllegalDataException(e);
125        }
126    }
127
128    private static void readTags(JsonObject item, Tagged t) {
129        JsonObject tags = item.getJsonObject("tags");
130        if (tags != null) {
131            for (Entry<String, JsonValue> entry : tags.entrySet()) {
132                t.put(entry.getKey(), ((JsonString) entry.getValue()).getString());
133            }
134        }
135    }
136
137    private void parseNode(JsonObject item) throws IllegalDataException {
138        parseNode(item.getJsonNumber("lat").doubleValue(),
139                  item.getJsonNumber("lon").doubleValue(), nd -> readCommon(item, nd), n -> readTags(item, n));
140    }
141
142    private void parseWay(JsonObject item) throws IllegalDataException {
143        parseWay(wd -> readCommon(item, wd), (w, nodeIds) -> readWayNodesAndTags(item, w, nodeIds));
144    }
145
146    private static void readWayNodesAndTags(JsonObject item, WayData w, Collection<Long> nodeIds) {
147        for (JsonValue v : item.getJsonArray("nodes")) {
148            nodeIds.add(((JsonNumber) v).longValue());
149        }
150        readTags(item, w);
151    }
152
153    private void parseRelation(JsonObject item) throws IllegalDataException {
154        parseRelation(rd -> readCommon(item, rd), (r, members) -> readRelationMembersAndTags(item, r, members));
155    }
156
157    private void readRelationMembersAndTags(JsonObject item, RelationData r, Collection<RelationMemberData> members)
158            throws IllegalDataException {
159        JsonArray jsonArray = item.getJsonArray("members");
160        if (jsonArray != null) {
161            for (JsonValue v : jsonArray) {
162                JsonObject o = v.asJsonObject();
163                members.add(parseRelationMember(r, ((JsonNumber) o.get("ref")).longValue(), o.getString("type"), o.getString("role")));
164            }
165        }
166        readTags(item, r);
167    }
168
169    protected void parseUnknown(JsonObject element, boolean printWarning) {
170        if (printWarning) {
171            Logging.info(tr("Undefined element ''{0}'' found in input stream. Skipping.", element));
172        }
173    }
174
175    private void parseUnknown(JsonObject element) {
176        parseUnknown(element, true);
177    }
178
179    @Override
180    protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
181        return doParseDataSet(source, progressMonitor, ir -> {
182            setParser(Json.createParser(ir));
183            parse();
184        });
185    }
186
187    /**
188     * Parse the given input source and return the dataset.
189     *
190     * @param source the source input stream. Must not be null.
191     * @param progressMonitor the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed
192     *
193     * @return the dataset with the parsed data
194     * @throws IllegalDataException if an error was found while parsing the data from the source
195     * @throws IllegalArgumentException if source is null
196     */
197    public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
198        return new OsmJsonReader().doParseDataSet(source, progressMonitor);
199    }
200}