// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.command; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import javax.swing.JLabel; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.conflict.ConflictCollection; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.DefaultNameFormatter; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.tools.ImageProvider; /** * Physically removes an {@see OsmPrimitive} from the dataset of the edit * layer and disconnects any references from {@see Way}s or {@see Relation}s * to this primitive. * * This command is necessary if a local {@see OsmPrimitive} has been deleted on * the server by another user and if the local user decides to delete his version * too. If he only deleted it "logically" JOSM would try to delete it on the server * which would result in an non resolvable conflict. * */ public class PurgePrimitivesCommand extends ConflictResolveCommand{ static private final Logger logger = Logger.getLogger(PurgePrimitivesCommand.class.getName()); /** the primitives to purge */ private Collection<OsmPrimitive> toPurge; /** the set of primitives to purge as consequence of purging * {@see #primitive}, including {@see #primitive} */ private Set<OsmPrimitive> purgedPrimitives; private Set<OsmPrimitive> origVersionsOfTouchedPrimitives; protected void init(Collection<OsmPrimitive> toPurge) { this.toPurge = toPurge; this.purgedPrimitives = new HashSet<OsmPrimitive>(); this.origVersionsOfTouchedPrimitives = new HashSet<OsmPrimitive>(); } /** * constructor * @param primitive the primitive to purge * */ public PurgePrimitivesCommand(OsmPrimitive primitive) { init(Collections.singleton(primitive)); } /** * constructor * @param layer the OSM data layer * @param primitive the primitive to purge * */ public PurgePrimitivesCommand(OsmDataLayer layer, OsmPrimitive primitive) { super(layer); init(Collections.singleton(primitive)); } /** * constructor * @param layer the OSM data layer * @param primitives the primitives to purge * */ public PurgePrimitivesCommand(OsmDataLayer layer, Collection<OsmPrimitive> primitives) { super(layer); init(primitives); } /** * Replies a collection with the purged primitives * * @return a collection with the purged primitives */ public Collection<OsmPrimitive> getPurgedPrimitives() { return purgedPrimitives; } protected MutableTreeNode getDescription(OsmPrimitive primitive) { return new DefaultMutableTreeNode( new JLabel( tr("Purged object ''{0}''", primitive.getDisplayName(DefaultNameFormatter.getInstance())), ImageProvider.get("data", "object"), JLabel.HORIZONTAL ) ); } protected MutableTreeNode getDescription(Collection<OsmPrimitive> primitives) { DefaultMutableTreeNode root = new DefaultMutableTreeNode( trn("Purged {0} object", "Purged {0} objects", primitives.size(), primitives.size()) ); for (OsmPrimitive p : primitives) { root.add(getDescription(p)); } return root; } @Override public MutableTreeNode description() { if (purgedPrimitives.size() == 1) return getDescription(purgedPrimitives.iterator().next()); else return getDescription(purgedPrimitives); } /** * Purges an {@see OsmPrimitive} <code>child</code> from a {@see DataSet}. * * @param child the primitive to purge * @param hive the hive of {@see OsmPrimitive}s we remember other {@see OsmPrimitive} * we have to purge because we purge <code>child</code>. * */ protected void removeReferecesToPrimitive(OsmPrimitive child, Set<OsmPrimitive> hive) { hive.remove(child); for (OsmPrimitive parent: child.getReferrers()) { if (toPurge.contains(parent)) // parent itself is to be purged. This method is going to be // invoked for parent later return; if (parent instanceof Way) { Way w = (Way)parent; if (!origVersionsOfTouchedPrimitives.contains(w)) { origVersionsOfTouchedPrimitives.add(w); } w.removeNode((Node)child); // if a way ends up with less than two nodes we // remember it on the "hive" // if (w.getNodesCount() < 2) { System.out.println(tr("Warning: Purging way {0} because number of nodes dropped below 2. Current is {1}", w.getId(),w.getNodesCount())); hive.add(w); } } else if (parent instanceof Relation) { Relation r = (Relation)parent; if (!origVersionsOfTouchedPrimitives.contains(r)) { origVersionsOfTouchedPrimitives.add(r); } System.out.println(tr("Removing reference from relation {0}",r.getId())); r.removeMembersFor(child); } else { // should not happen. parent can't be a node } } } @Override public boolean executeCommand() { HashSet<OsmPrimitive> hive = new HashSet<OsmPrimitive>(); // iteratively purge the primitive and all primitives // which violate invariants after they lose a reference to // the primitive (i.e. ways which end up with less than two // nodes) hive.addAll(toPurge); while(! hive.isEmpty()) { OsmPrimitive p = hive.iterator().next(); removeReferecesToPrimitive(p, hive); getLayer().data.removePrimitive(p); purgedPrimitives.add(p); ConflictCollection conflicts = getLayer().getConflicts(); if (conflicts.hasConflictForMy(p)) { rememberConflict(conflicts.getConflictForMy(p)); conflicts.remove(p); } } return super.executeCommand(); } @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { modified.addAll(origVersionsOfTouchedPrimitives); } @Override public void undoCommand() { if (! Main.map.mapView.hasLayer(getLayer())) { logger.warning(tr("Cannot undo command ''{0}'' because layer ''{1}'' is not present any more", this.toString(), getLayer().toString() )); return; } Main.map.mapView.setActiveLayer(getLayer()); // restore purged primitives // for (OsmPrimitive purged : purgedPrimitives) { getLayer().data.addPrimitive(purged); } reconstituteConflicts(); // will restore the primitives referring to one // of the purged primitives super.undoCommand(); } }