// License: GPL. Copyright 2007 by Immanuel Scholz and others package org.openstreetmap.josm.data.osm; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.geom.Area; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.openstreetmap.josm.data.SelectionChangedListener; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent; import org.openstreetmap.josm.data.osm.event.DataChangedEvent; import org.openstreetmap.josm.data.osm.event.DataSetListener; import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; /** * DataSet is the data behind the application. It can consists of only a few points up to the whole * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc. * * Note that DataSet is not an osm-primitive and so has no key association but a few members to * store some information. * * @author imi */ public class DataSet implements Cloneable { private static class IdHash implements Hash<PrimitiveId,OsmPrimitive> { public int getHashCode(PrimitiveId k) { return (int)k.getUniqueId() ^ k.getType().hashCode(); } public boolean equals(PrimitiveId key, OsmPrimitive value) { if (key == null || value == null) return false; return key.getUniqueId() == value.getUniqueId() && key.getType() == value.getType(); } } private Storage<OsmPrimitive> allPrimitives = new Storage<OsmPrimitive>(new IdHash()); private Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new IdHash()); private List<DataSetListener> listeners = new ArrayList<DataSetListener>(); // Number of open calls to beginUpdate private int updateCount; private int highlightUpdateCount; /** * This method can be used to detect changes in highlight state of primitives. If highlighting was changed * then the method will return different number. * @return */ public int getHighlightUpdateCount() { return highlightUpdateCount; } /** * The API version that created this data set, if any. */ private String version; /** * Replies the API version this dataset was created from. May be null. * * @return the API version this dataset was created from. May be null. */ public String getVersion() { return version; } /** * Sets the API version this dataset was created from. * * @param version the API version, i.e. "0.5" or "0.6" */ public void setVersion(String version) { this.version = version; } /** * All nodes goes here, even when included in other data (ways etc). This enables the instant * conversion of the whole DataSet by iterating over this data structure. */ private QuadBuckets<Node> nodes = new QuadBuckets<Node>(); /** * Replies an unmodifiable collection of nodes in this dataset * * @return an unmodifiable collection of nodes in this dataset */ public Collection<Node> getNodes() { return Collections.unmodifiableCollection(nodes); } public List<Node> searchNodes(BBox bbox) { return nodes.search(bbox); } /** * All ways (Streets etc.) in the DataSet. * * The way nodes are stored only in the way list. */ private QuadBuckets<Way> ways = new QuadBuckets<Way>(); /** * Replies an unmodifiable collection of ways in this dataset * * @return an unmodifiable collection of ways in this dataset */ public Collection<Way> getWays() { return Collections.unmodifiableCollection(ways); } public List<Way> searchWays(BBox bbox) { return ways.search(bbox); } /** * All relations/relationships */ private Collection<Relation> relations = new LinkedList<Relation>(); /** * Replies an unmodifiable collection of relations in this dataset * * @return an unmodifiable collection of relations in this dataset */ public Collection<Relation> getRelations() { return Collections.unmodifiableCollection(relations); } public List<Relation> searchRelations(BBox bbox) { // QuadBuckets might be useful here (don't forget to do reindexing after some of rm is changed) List<Relation> result = new ArrayList<Relation>(); for (Relation r: relations) { if (r.getBBox().intersects(bbox)) { result.add(r); } } return result; } /** * All data sources of this DataSet. */ public Collection<DataSource> dataSources = new LinkedList<DataSource>(); /** * @return A collection containing all primitives of the dataset. Data are not ordered */ public Collection<OsmPrimitive> allPrimitives() { return Collections.unmodifiableCollection(allPrimitives); } /** * @return A collection containing all not-deleted primitives (except keys). */ public Collection<OsmPrimitive> allNonDeletedPrimitives() { return new DatasetCollection(allPrimitives, OsmPrimitive.nonDeletedPredicate); } public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() { return new DatasetCollection(allPrimitives, OsmPrimitive.nonDeletedCompletePredicate); } public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() { return new DatasetCollection(allPrimitives, OsmPrimitive.nonDeletedPhysicalPredicate); } public Collection<OsmPrimitive> allModifiedPrimitives() { return new DatasetCollection(allPrimitives, OsmPrimitive.modifiedPredicate); } /** * Adds a primitive to the dataset * * @param primitive the primitive. */ public void addPrimitive(OsmPrimitive primitive) { if (getPrimitiveById(primitive) != null) throw new DataIntegrityProblemException( tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString())); primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reinexRelation to work properly) if (primitive instanceof Node) { nodes.add((Node) primitive); } else if (primitive instanceof Way) { ways.add((Way) primitive); } else if (primitive instanceof Relation) { relations.add((Relation) primitive); } allPrimitives.add(primitive); primitive.setDataset(this); firePrimitivesAdded(Collections.singletonList(primitive), false); } public OsmPrimitive addPrimitive(PrimitiveData data) { OsmPrimitive result; if (data instanceof NodeData) { result = new Node(); } else if (data instanceof WayData) { result = new Way(); } else if (data instanceof RelationData) { result = new Relation(); } else throw new AssertionError(); result.setDataset(this); result.load(data); addPrimitive(result); return result; } /** * Removes a primitive from the dataset. This method only removes the * primitive form the respective collection of primitives managed * by this dataset, i.e. from {@see #nodes}, {@see #ways}, or * {@see #relations}. References from other primitives to this * primitive are left unchanged. * * @param primitive the primitive */ public void removePrimitive(PrimitiveId primitiveId) { OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId); if (primitive == null) return; if (primitive instanceof Node) { nodes.remove(primitive); } else if (primitive instanceof Way) { ways.remove(primitive); } else if (primitive instanceof Relation) { relations.remove(primitive); } selectedPrimitives.remove(primitive); allPrimitives.remove(primitive); primitive.setDataset(null); errors.remove(primitive); firePrimitivesRemoved(Collections.singletonList(primitive), false); } /*--------------------------------------------------- * SELECTION HANDLING *---------------------------------------------------*/ /** * A list of listeners to selection changed events. The list is static, as listeners register * themselves for any dataset selection changes that occur, regardless of the current active * dataset. (However, the selection does only change in the active layer) */ public static final Collection<SelectionChangedListener> selListeners = Collections.synchronizedList(new LinkedList<SelectionChangedListener>()); /** * Notifies all registered {@see SelectionChangedListener} about the current selection in * this dataset. * */ public void fireSelectionChanged(){ synchronized (selListeners) { List<? extends OsmPrimitive> currentSelection = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(selectedPrimitives)); for (SelectionChangedListener l : selListeners) { l.selectionChanged(currentSelection); } } } LinkedHashSet<OsmPrimitive> selectedPrimitives = new LinkedHashSet<OsmPrimitive>(); public Collection<OsmPrimitive> getSelectedNodesAndWays() { Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>(); for (OsmPrimitive osm : selectedPrimitives) { if (osm instanceof Way || osm instanceof Node) { sel.add(osm); } } return sel; } /** * Replies an unmodifiable collection of primitives currently selected * in this dataset * * @return unmodifiable collection of primitives */ public Collection<OsmPrimitive> getSelected() { return Collections.unmodifiableSet(selectedPrimitives); } /** * Return selected nodes. */ public Collection<Node> getSelectedNodes() { List<Node> result = new ArrayList<Node>(selectedPrimitives.size()); for (OsmPrimitive primitive:selectedPrimitives) { if (primitive instanceof Node) { result.add((Node)primitive); } } return result; } /** * Return selected ways. */ public Collection<Way> getSelectedWays() { List<Way> result = new ArrayList<Way>(selectedPrimitives.size()); for (OsmPrimitive primitive:selectedPrimitives) { if (primitive instanceof Way) { result.add((Way)primitive); } } return result; } /** * Return selected relations. */ public Collection<Relation> getSelectedRelations() { List<Relation> result = new ArrayList<Relation>(selectedPrimitives.size() / 10); for (OsmPrimitive primitive:selectedPrimitives) { if (primitive instanceof Relation) { result.add((Relation)primitive); } } return result; } public boolean isSelected(OsmPrimitive osm) { return selectedPrimitives.contains(osm); } public void toggleSelected(Collection<? extends PrimitiveId> osm) { boolean changed = false; for (PrimitiveId o : osm) { changed = changed | this.__toggleSelected(o); } if (changed) { fireSelectionChanged(); } } public void toggleSelected(PrimitiveId... osm) { toggleSelected(Arrays.asList(osm)); } private boolean __toggleSelected(PrimitiveId primitiveId) { OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId); if (primitive == null) return false; if (!selectedPrimitives.remove(primitive)) { selectedPrimitives.add(primitive); } return true; } /** * Sets the current selection to the primitives in <code>selection</code>. * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true. * * @param selection the selection * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise */ public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) { boolean wasEmpty = selectedPrimitives.isEmpty(); selectedPrimitives = new LinkedHashSet<OsmPrimitive>(); addSelected(selection, fireSelectionChangeEvent); if (!wasEmpty && selectedPrimitives.isEmpty() && fireSelectionChangeEvent) { fireSelectionChanged(); } } /** * Sets the current selection to the primitives in <code>selection</code> * and notifies all {@see SelectionChangedListener}. * * @param selection the selection */ public void setSelected(Collection<? extends PrimitiveId> selection) { setSelected(selection, true /* fire selection change event */); } public void setSelected(PrimitiveId... osm) { if (osm.length == 1 && osm[0] == null) { setSelected(); return; } List<PrimitiveId> list = Arrays.asList(osm); setSelected(list); } /** * Adds the primitives in <code>selection</code> to the current selection * and notifies all {@see SelectionChangedListener}. * * @param selection the selection */ public void addSelected(Collection<? extends PrimitiveId> selection) { addSelected(selection, true /* fire selection change event */); } public void addSelected(PrimitiveId... osm) { addSelected(Arrays.asList(osm)); } /** * Adds the primitives in <code>selection</code> to the current selection. * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true. * * @param selection the selection * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise */ public void addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) { boolean changed = false; for (PrimitiveId id: selection) { OsmPrimitive primitive = getPrimitiveByIdChecked(id); if (primitive != null) { changed = changed | selectedPrimitives.add(primitive); } } if (fireSelectionChangeEvent && changed) { fireSelectionChanged(); } } /** * Remove the selection from every value in the collection. * @param list The collection to remove the selection from. */ public void clearSelection(PrimitiveId... osm) { clearSelection(Arrays.asList(osm)); } public void clearSelection(Collection<? extends PrimitiveId> list) { boolean changed = false; for (PrimitiveId id:list) { OsmPrimitive primitive = getPrimitiveById(id); if (primitive != null) { changed = changed | selectedPrimitives.remove(primitive); } } if (changed) { fireSelectionChanged(); } } public void clearSelection() { if (!selectedPrimitives.isEmpty()) { selectedPrimitives.clear(); fireSelectionChanged(); } } /*------------------------------------------------------ * FILTERED / DISABLED HANDLING *-----------------------------------------------------*/ public void setDisabled(OsmPrimitive... osm) { if (osm.length == 1 && osm[0] == null) { setDisabled(); return; } clearDisabled(nodes); clearDisabled(ways); clearDisabled(relations); for (OsmPrimitive o : osm) if (o != null) { o.setDisabled(true); } } public void setFiltered(Collection<? extends OsmPrimitive> selection) { clearFiltered(nodes); clearFiltered(ways); clearFiltered(relations); for (OsmPrimitive osm : selection) { osm.setFiltered(true); } } public void setFiltered(OsmPrimitive... osm) { if (osm.length == 1 && osm[0] == null) { setFiltered(); return; } clearFiltered(nodes); clearFiltered(ways); clearFiltered(relations); for (OsmPrimitive o : osm) if (o != null) { o.setFiltered(true); } } public void setDisabled(Collection<? extends OsmPrimitive> selection) { clearDisabled(nodes); clearDisabled(ways); clearDisabled(relations); for (OsmPrimitive osm : selection) { osm.setDisabled(true); } } /** * Remove the filtered parameter from every value in the collection. * @param list The collection to remove the filtered parameter from. */ private void clearFiltered(Collection<? extends OsmPrimitive> list) { if (list == null) return; for (OsmPrimitive osm : list) { osm.setFiltered(false); } } /** * Remove the disabled parameter from every value in the collection. * @param list The collection to remove the disabled parameter from. */ private void clearDisabled(Collection<? extends OsmPrimitive> list) { if (list == null) return; for (OsmPrimitive osm : list) { osm.setDisabled(false); } } @Override public DataSet clone() { DataSet ds = new DataSet(); HashMap<OsmPrimitive, OsmPrimitive> primitivesMap = new HashMap<OsmPrimitive, OsmPrimitive>(); for (Node n : nodes) { Node newNode = new Node(n); primitivesMap.put(n, newNode); ds.addPrimitive(newNode); } for (Way w : ways) { Way newWay = new Way(w); primitivesMap.put(w, newWay); List<Node> newNodes = new ArrayList<Node>(); for (Node n: w.getNodes()) { newNodes.add((Node)primitivesMap.get(n)); } newWay.setNodes(newNodes); ds.addPrimitive(newWay); } // Because relations can have other relations as members we first clone all relations // and then get the cloned members for (Relation r : relations) { Relation newRelation = new Relation(r, r.isNew()); newRelation.setMembers(null); primitivesMap.put(r, newRelation); ds.addPrimitive(newRelation); } for (Relation r : relations) { Relation newRelation = (Relation)primitivesMap.get(r); List<RelationMember> newMembers = new ArrayList<RelationMember>(); for (RelationMember rm: r.getMembers()) { newMembers.add(new RelationMember(rm.getRole(), primitivesMap.get(rm.getMember()))); } newRelation.setMembers(newMembers); } for (DataSource source : dataSources) { ds.dataSources.add(new DataSource(source.bounds, source.origin)); } ds.version = version; return ds; } /** * Returns the total area of downloaded data (the "yellow rectangles"). * @return Area object encompassing downloaded data. */ public Area getDataSourceArea() { if (dataSources.isEmpty()) return null; Area a = new Area(); for (DataSource source : dataSources) { // create area from data bounds a.add(new Area(source.bounds.asRect())); } return a; } /** * returns a primitive with a given id from the data set. null, if no such primitive * exists * * @param id uniqueId of the primitive. Might be < 0 for newly created primitives * @param type the type of the primitive. Must not be null. * @return the primitive * @exception NullPointerException thrown, if type is null */ public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) { return getPrimitiveById(new SimplePrimitiveId(id, type), false); } public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) { return getPrimitiveById(primitiveId, false); } public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId, boolean createNew) { OsmPrimitive result = primitivesMap.get(primitiveId); if (result == null && createNew) { switch (primitiveId.getType()) { case NODE: result = new Node(primitiveId.getUniqueId(), true); break; case WAY: result = new Way(primitiveId.getUniqueId(), true); break; case RELATION: result = new Relation(primitiveId.getUniqueId(), true); break; } addPrimitive(result); } return result; } /** * Show message and stack trace in log in case primitive is not found * @param primitiveId * @return Primitive by id. */ private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) { OsmPrimitive result = getPrimitiveById(primitiveId); if (result == null) { System.out.println(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this " + " at http://josm.openstreetmap.de . This is not a critical error, it should be safe to continue in your work.", primitiveId.getType(), Long.toString(primitiveId.getUniqueId()))); new Exception().printStackTrace(); } return result; } public Set<Long> getPrimitiveIds() { HashSet<Long> ret = new HashSet<Long>(); for (OsmPrimitive primitive : nodes) { ret.add(primitive.getId()); } for (OsmPrimitive primitive : ways) { ret.add(primitive.getId()); } for (OsmPrimitive primitive : relations) { ret.add(primitive.getId()); } return ret; } protected void deleteWay(Way way) { way.setNodes(null); way.setDeleted(true); } /** * removes all references from ways in this dataset to a particular node * * @param node the node */ public void unlinkNodeFromWays(Node node) { for (Way way: ways) { List<Node> nodes = way.getNodes(); if (nodes.remove(node)) { if (nodes.size() < 2) { deleteWay(way); } else { way.setNodes(nodes); } } } } /** * removes all references from relations in this dataset to this primitive * * @param primitive the primitive */ public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) { for (Relation relation : relations) { List<RelationMember> members = relation.getMembers(); Iterator<RelationMember> it = members.iterator(); boolean removed = false; while(it.hasNext()) { RelationMember member = it.next(); if (member.getMember().equals(primitive)) { it.remove(); removed = true; } } if (removed) { relation.setMembers(members); } } } /** * removes all references from from other primitives to the * referenced primitive * * @param referencedPrimitive the referenced primitive */ public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) { if (referencedPrimitive instanceof Node) { unlinkNodeFromWays((Node)referencedPrimitive); unlinkPrimitiveFromRelations(referencedPrimitive); } else { unlinkPrimitiveFromRelations(referencedPrimitive); } } /** * Replies a list of parent relations which refer to the relation * <code>child</code>. Replies an empty list if child is null. * * @param child the child relation * @return a list of parent relations which refer to the relation * <code>child</code> */ public List<Relation> getParentRelations(Relation child) { ArrayList<Relation> parents = new ArrayList<Relation>(); if (child == null) return parents; for (Relation parent : relations) { if (parent == child) { continue; } for (RelationMember member: parent.getMembers()) { if (member.refersTo(child)) { parents.add(parent); break; } } } return parents; } /** * Replies true if there is at least one primitive in this dataset with * {@see OsmPrimitive#isModified()} == <code>true</code>. * * @return true if there is at least one primitive in this dataset with * {@see OsmPrimitive#isModified()} == <code>true</code>. */ public boolean isModified() { for (Node n: nodes) { if (n.isModified()) return true; } for (Way w: ways) { if (w.isModified()) return true; } for (Relation r: relations) { if (r.isModified()) return true; } return false; } private void reindexNode(Node node, LatLon newCoor) { nodes.remove(node); node.setCoorInternal(newCoor); nodes.add(node); for (OsmPrimitive primitive: node.getReferrers()) { if (primitive instanceof Way) { reindexWay((Way)primitive); } else { reindexRelation((Relation) primitive); } } } private void reindexWay(Way way) { BBox before = way.getBBox(); ways.remove(way); way.updatePosition(); ways.add(way); if (!way.getBBox().equals(before)) { for (OsmPrimitive primitive: way.getReferrers()) { reindexRelation((Relation)primitive); } } } private void reindexRelation(Relation relation) { BBox before = relation.getBBox(); relation.updatePosition(); if (!before.equals(relation.getBBox())) { for (OsmPrimitive primitive: relation.getReferrers()) { reindexRelation((Relation) primitive); } } } public void addDataSetListener(DataSetListener dsl) { listeners.add(dsl); } public void removeDataSetListener(DataSetListener dsl) { listeners.remove(dsl); } /** * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}. * {@link DataSetListener#dataChanged()} event is triggered after end of changes * <br> * Typical usecase should look like this: * <pre> * ds.beginUpdate(); * try { * ... * } finally { * ds.endUpdate(); * } * </pre> */ public void beginUpdate() { updateCount++; } /** * @see DataSet#beginUpdate() */ public void endUpdate() { if (updateCount > 0) { updateCount--; if (updateCount == 0) { fireDataChanged(); } } else throw new AssertionError("endUpdate called without beginUpdate"); } private void fireEvent(AbstractDatasetChangedEvent event) { if (updateCount == 0) { for (DataSetListener dsl : listeners) { event.fire(dsl); } } } private void fireDataChanged() { fireEvent(new DataChangedEvent(this)); } void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) { fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete)); } void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) { fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete)); } void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) { fireEvent(new TagsChangedEvent(this, prim, originalKeys)); } void fireRelationMembersChanged(Relation r) { reindexRelation(r); fireEvent(new RelationMembersChangedEvent(this, r)); } void fireNodeMoved(Node node, LatLon newCoor) { reindexNode(node, newCoor); fireEvent(new NodeMovedEvent(this, node)); } void fireWayNodesChanged(Way way) { reindexWay(way); fireEvent(new WayNodesChangedEvent(this, way)); } void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) { fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId)); } void fireHighlightingChanged(OsmPrimitive primitive) { highlightUpdateCount++; } public void clenupDeletedPrimitives() { if (cleanupDeleted(nodes.iterator()) | cleanupDeleted(ways.iterator()) | cleanupDeleted(relations.iterator())) { fireSelectionChanged(); } } private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) { boolean changed = false; while (it.hasNext()) { OsmPrimitive primitive = it.next(); if (primitive.isDeleted()) { selectedPrimitives.remove(primitive); allPrimitives.remove(primitive); primitive.setDataset(null); changed = true; it.remove(); } } return changed; } /** * Removes all primitives from the dataset and resets the currently selected primitives * to the empty collection. Also notifies selection change listeners if necessary. * */ public void clear() { clearSelection(); for (OsmPrimitive primitive:allPrimitives) { primitive.setDataset(null); } nodes.clear(); ways.clear(); relations.clear(); allPrimitives.clear(); } // TODO Should be completely part of validator private Map<OsmPrimitive, List<String>> errors = new HashMap<OsmPrimitive, List<String>>(); public void addError(OsmPrimitive primitive, String error) { List<String> perrors = errors.get(primitive); if (perrors == null) { perrors = new ArrayList<String>(); } perrors.add(error); errors.put(primitive, perrors); } /** * Replies the list of errors registered for this primitive. * * @param primitive the primitive for which errors are queried * @return the list of errors. Never null. * @deprecated should be moved to the validator plugin */ @Deprecated public List<String> getErrors(OsmPrimitive primitive) { List<String> ret = errors.get(primitive); if (ret == null) { ret = Collections.emptyList(); } return ret; } public void clearErrors() { errors.clear(); } }