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