// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.undelete; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.AutoScaleAction; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.RelationMemberData; import org.openstreetmap.josm.data.osm.SimplePrimitiveId; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.history.History; import org.openstreetmap.josm.data.osm.history.HistoryDataSet; import org.openstreetmap.josm.data.osm.history.HistoryNode; import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; import org.openstreetmap.josm.data.osm.history.HistoryRelation; import org.openstreetmap.josm.data.osm.history.HistoryWay; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.gui.history.HistoryLoadTask; import org.openstreetmap.josm.gui.io.DownloadPrimitivesTask; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.tools.Shortcut; public class UndeleteAction extends JosmAction { private final class Worker implements Runnable { private final OsmPrimitive parent; private final OsmDataLayer layer; private final List<PrimitiveId> ids; private Worker(OsmPrimitive parent, OsmDataLayer layer, List<PrimitiveId> ids) { this.parent = parent; this.layer = layer; this.ids = ids; } @Override public void run() { List<Node> nodes = new ArrayList<>(); for (PrimitiveId pid : ids) { OsmPrimitive primitive = layer.data.getPrimitiveById(pid); if (primitive == null) { try { final Long id = pid.getUniqueId(); final OsmPrimitiveType type = pid.getType(); History h = HistoryDataSet.getInstance().getHistory(id, type); if (h == null) { Main.warn("Cannot find history for " + type + " " + id); return; } HistoryOsmPrimitive hPrimitive1 = h.getLatest(); HistoryOsmPrimitive hPrimitive2; boolean visible = hPrimitive1.isVisible(); if (visible) { // If the object is not deleted we get the real object DownloadPrimitivesTask download = new DownloadPrimitivesTask(layer, Collections.singletonList(pid), true); download.run(); primitive = layer.data.getPrimitiveById(id, type); } else { if (type.equals(OsmPrimitiveType.NODE)) { // We get version and user from the latest version, // coordinates and tags from n-1 version hPrimitive2 = h.getByVersion(h.getNumVersions() - 1); Node node = new Node(id, (int) hPrimitive1.getVersion()); HistoryNode hNode = (HistoryNode) hPrimitive2; if (hNode != null) { node.setCoor(hNode.getCoords()); } primitive = node; } else if (type.equals(OsmPrimitiveType.WAY)) { // We get version and user from the latest version, // nodes and tags from n-1 version hPrimitive1 = h.getLatest(); hPrimitive2 = h.getByVersion(h.getNumVersions() - 1); Way way = new Way(id, (int) hPrimitive1.getVersion()); HistoryWay hWay = (HistoryWay) hPrimitive2; // System.out.println(tr("Primitive {0} version {1}: {2} nodes", // hPrimitive2.getId(), hPrimitive2.getVersion(), // hWay.getNumNodes())); List<PrimitiveId> nodeIds = new ArrayList<>(); if (hWay != null) { for (Long i : hWay.getNodes()) { nodeIds.add(new SimplePrimitiveId(i, OsmPrimitiveType.NODE)); } } undelete(false, nodeIds, way); primitive = way; } else { primitive = new Relation(); hPrimitive1 = h.getLatest(); hPrimitive2 = h.getByVersion(h.getNumVersions() - 1); Relation rel = new Relation(id, (int) hPrimitive1.getVersion()); HistoryRelation hRel = (HistoryRelation) hPrimitive2; if (hRel != null) { List<RelationMember> members = new ArrayList<>(hRel.getNumMembers()); for (RelationMemberData m : hRel.getMembers()) { OsmPrimitive p = layer.data.getPrimitiveById(m.getMemberId(), m.getMemberType()); if (p == null) { switch (m.getMemberType()) { case NODE: p = new Node(m.getMemberId()); break; case CLOSEDWAY: case WAY: p = new Way(m.getMemberId()); break; case MULTIPOLYGON: case RELATION: p = new Relation(m.getMemberId()); break; } layer.data.addPrimitive(p); } members.add(new RelationMember(m.getRole(), p)); } rel.setMembers(members); } primitive = rel; } if (hPrimitive2 != null) { primitive.setChangesetId((int) hPrimitive1.getChangesetId()); primitive.setTimestamp(hPrimitive1.getTimestamp()); primitive.setUser(hPrimitive1.getUser()); primitive.setVisible(hPrimitive1.isVisible()); primitive.setKeys(hPrimitive2.getTags()); primitive.setModified(true); layer.data.addPrimitive(primitive); } else { final String msg = OsmPrimitiveType.NODE.equals(type) ? tr("Unable to undelete node {0}. Object has likely been redacted", id) : OsmPrimitiveType.WAY.equals(type) ? tr("Unable to undelete way {0}. Object has likely been redacted", id) : OsmPrimitiveType.RELATION.equals(type) ? tr("Unable to undelete relation {0}. Object has likely been redacted", id) : null; GuiHelper.runInEDT(() -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show()); Main.warn(msg); } } } catch (Exception t) { Main.error(t); } } if (parent != null && primitive instanceof Node) { nodes.add((Node) primitive); } } if (parent instanceof Way && !nodes.isEmpty()) { ((Way) parent).setNodes(nodes); Main.map.repaint(); } GuiHelper.runInEDT(() -> AutoScaleAction.zoomTo(layer.data.allNonDeletedPrimitives())); } } public UndeleteAction() { super(tr("Undelete object..."), "undelete", tr("Undelete object by id"), Shortcut.registerShortcut("tools:undelete", tr("File: {0}", tr("Undelete object...")), KeyEvent.VK_U, Shortcut.ALT_SHIFT), true); } @Override public void actionPerformed(ActionEvent e) { UndeleteDialog dialog = new UndeleteDialog(Main.parent); if (dialog.showDialog().getValue() != 1) return; Main.pref.put("undelete.newlayer", dialog.isNewLayerSelected()); Main.pref.put("undelete.osmid", dialog.getOsmIdsString()); undelete(dialog.isNewLayerSelected(), dialog.getOsmIds(), null); } /** * // TODO: undelete relation members if necessary */ public void undelete(boolean newLayer, final List<PrimitiveId> ids, final OsmPrimitive parent) { Main.info("Undeleting "+ids+(parent == null ? "" : " with parent "+parent)); OsmDataLayer tmpLayer = Main.getLayerManager().getEditLayer(); if ((tmpLayer == null) || newLayer) { tmpLayer = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null); Main.getLayerManager().addLayer(tmpLayer); } final OsmDataLayer layer = tmpLayer; HistoryLoadTask task = new HistoryLoadTask(); for (PrimitiveId id : ids) { task.add(id); } Main.worker.execute(task); Main.worker.submit(new Worker(parent, layer, ids)); } }