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.Relation; 022import org.openstreetmap.josm.data.osm.RelationMemberData; 023import org.openstreetmap.josm.data.osm.Tagged; 024import org.openstreetmap.josm.data.osm.Way; 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, Way 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, Relation r, Collection<RelationMemberData> members) 158 throws IllegalDataException { 159 for (JsonValue v : item.getJsonArray("members")) { 160 JsonObject o = v.asJsonObject(); 161 members.add(parseRelationMember(r, ((JsonNumber) o.get("ref")).longValue(), o.getString("type"), o.getString("role"))); 162 } 163 readTags(item, r); 164 } 165 166 protected void parseUnknown(JsonObject element, boolean printWarning) { 167 if (printWarning) { 168 Logging.info(tr("Undefined element ''{0}'' found in input stream. Skipping.", element)); 169 } 170 } 171 172 private void parseUnknown(JsonObject element) { 173 parseUnknown(element, true); 174 } 175 176 @Override 177 protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 178 return doParseDataSet(source, progressMonitor, ir -> { 179 setParser(Json.createParser(ir)); 180 parse(); 181 }); 182 } 183 184 /** 185 * Parse the given input source and return the dataset. 186 * 187 * @param source the source input stream. Must not be null. 188 * @param progressMonitor the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed 189 * 190 * @return the dataset with the parsed data 191 * @throws IllegalDataException if an error was found while parsing the data from the source 192 * @throws IllegalArgumentException if source is null 193 */ 194 public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 195 return new OsmJsonReader().doParseDataSet(source, progressMonitor); 196 } 197}