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.ArrayList;
008import java.util.Collection;
009import java.util.HashMap;
010import java.util.List;
011import java.util.Map;
012import java.util.Map.Entry;
013
014import org.openstreetmap.josm.data.osm.Changeset;
015import org.openstreetmap.josm.data.osm.DataSet;
016import org.openstreetmap.josm.data.osm.Node;
017import org.openstreetmap.josm.data.osm.OsmPrimitive;
018import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
019import org.openstreetmap.josm.data.osm.PrimitiveId;
020import org.openstreetmap.josm.data.osm.Relation;
021import org.openstreetmap.josm.data.osm.RelationMember;
022import org.openstreetmap.josm.data.osm.RelationMemberData;
023import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
024import org.openstreetmap.josm.data.osm.Way;
025import org.openstreetmap.josm.gui.progress.ProgressMonitor;
026import org.openstreetmap.josm.tools.Logging;
027
028/**
029 * Abstract Reader, allowing other implementations than OsmReader (PbfReader in PBF plugin for example)
030 * @author Vincent
031 *
032 */
033public abstract class AbstractReader {
034
035    /**
036     * The dataset to add parsed objects to.
037     */
038    protected DataSet ds = new DataSet();
039
040    protected Changeset uploadChangeset;
041
042    /** the map from external ids to read OsmPrimitives. External ids are
043     * longs too, but in contrast to internal ids negative values are used
044     * to identify primitives unknown to the OSM server
045     */
046    protected final Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<>();
047
048    /**
049     * Data structure for the remaining way objects
050     */
051    protected final Map<Long, Collection<Long>> ways = new HashMap<>();
052
053    /**
054     * Data structure for relation objects
055     */
056    protected final Map<Long, Collection<RelationMemberData>> relations = new HashMap<>();
057
058    /**
059     * Replies the parsed data set
060     *
061     * @return the parsed data set
062     */
063    public DataSet getDataSet() {
064        return ds;
065    }
066
067    /**
068     * Processes the parsed nodes after parsing. Just adds them to
069     * the dataset
070     *
071     */
072    protected void processNodesAfterParsing() {
073        for (OsmPrimitive primitive: externalIdMap.values()) {
074            if (primitive instanceof Node) {
075                this.ds.addPrimitive(primitive);
076            }
077        }
078    }
079
080    /**
081     * Processes the ways after parsing. Rebuilds the list of nodes of each way and
082     * adds the way to the dataset
083     *
084     * @throws IllegalDataException if a data integrity problem is detected
085     */
086    protected void processWaysAfterParsing() throws IllegalDataException {
087        for (Entry<Long, Collection<Long>> entry : ways.entrySet()) {
088            Long externalWayId = entry.getKey();
089            Way w = (Way) externalIdMap.get(new SimplePrimitiveId(externalWayId, OsmPrimitiveType.WAY));
090            List<Node> wayNodes = new ArrayList<>();
091            for (long id : entry.getValue()) {
092                Node n = (Node) externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE));
093                if (n == null) {
094                    if (id <= 0)
095                        throw new IllegalDataException(
096                                tr("Way with external ID ''{0}'' includes missing node with external ID ''{1}''.",
097                                        Long.toString(externalWayId),
098                                        Long.toString(id)));
099                    // create an incomplete node if necessary
100                    n = (Node) ds.getPrimitiveById(id, OsmPrimitiveType.NODE);
101                    if (n == null) {
102                        n = new Node(id);
103                        ds.addPrimitive(n);
104                    }
105                }
106                if (n.isDeleted()) {
107                    Logging.info(tr("Deleted node {0} is part of way {1}", Long.toString(id), Long.toString(w.getId())));
108                } else {
109                    wayNodes.add(n);
110                }
111            }
112            w.setNodes(wayNodes);
113            if (w.hasIncompleteNodes()) {
114                Logging.info(tr("Way {0} with {1} nodes is incomplete because at least one node was missing in the loaded data.",
115                        Long.toString(externalWayId), w.getNodesCount()));
116            }
117            ds.addPrimitive(w);
118        }
119    }
120
121    /**
122     * Completes the parsed relations with its members.
123     *
124     * @throws IllegalDataException if a data integrity problem is detected, i.e. if a
125     * relation member refers to a local primitive which wasn't available in the data
126     */
127    protected void processRelationsAfterParsing() throws IllegalDataException {
128
129        // First add all relations to make sure that when relation reference other relation, the referenced will be already in dataset
130        for (Long externalRelationId : relations.keySet()) {
131            Relation relation = (Relation) externalIdMap.get(
132                    new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
133            );
134            ds.addPrimitive(relation);
135        }
136
137        for (Entry<Long, Collection<RelationMemberData>> entry : relations.entrySet()) {
138            Long externalRelationId = entry.getKey();
139            Relation relation = (Relation) externalIdMap.get(
140                    new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
141            );
142            List<RelationMember> relationMembers = new ArrayList<>();
143            for (RelationMemberData rm : entry.getValue()) {
144                // lookup the member from the map of already created primitives
145                OsmPrimitive primitive = externalIdMap.get(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()));
146
147                if (primitive == null) {
148                    if (rm.getMemberId() <= 0)
149                        // relation member refers to a primitive with a negative id which was not
150                        // found in the data. This is always a data integrity problem and we abort
151                        // with an exception
152                        //
153                        throw new IllegalDataException(
154                                tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.",
155                                        Long.toString(externalRelationId),
156                                        Long.toString(rm.getMemberId())));
157
158                    // member refers to OSM primitive which was not present in the parsed data
159                    // -> create a new incomplete primitive and add it to the dataset
160                    //
161                    primitive = ds.getPrimitiveById(rm.getMemberId(), rm.getMemberType());
162                    if (primitive == null) {
163                        switch (rm.getMemberType()) {
164                        case NODE:
165                            primitive = new Node(rm.getMemberId()); break;
166                        case WAY:
167                            primitive = new Way(rm.getMemberId()); break;
168                        case RELATION:
169                            primitive = new Relation(rm.getMemberId()); break;
170                        default: throw new AssertionError(); // can't happen
171                        }
172
173                        ds.addPrimitive(primitive);
174                        externalIdMap.put(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()), primitive);
175                    }
176                }
177                if (primitive.isDeleted()) {
178                    Logging.info(tr("Deleted member {0} is used by relation {1}",
179                            Long.toString(primitive.getId()), Long.toString(relation.getId())));
180                } else {
181                    relationMembers.add(new RelationMember(rm.getRole(), primitive));
182                }
183            }
184            relation.setMembers(relationMembers);
185        }
186    }
187
188    protected void processChangesetAfterParsing() {
189        if (uploadChangeset != null) {
190            for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) {
191                ds.addChangeSetTag(e.getKey(), e.getValue());
192            }
193        }
194    }
195
196    protected final void prepareDataSet() throws IllegalDataException {
197        ds.beginUpdate();
198        try {
199            processNodesAfterParsing();
200            processWaysAfterParsing();
201            processRelationsAfterParsing();
202            processChangesetAfterParsing();
203        } finally {
204            ds.endUpdate();
205        }
206    }
207
208    protected abstract DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException;
209}