001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.datatransfer.importers;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.datatransfer.UnsupportedFlavorException;
007import java.io.IOException;
008import java.util.ArrayList;
009import java.util.EnumMap;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013import java.util.Objects;
014import java.util.stream.Collectors;
015
016import javax.swing.TransferHandler.TransferSupport;
017
018import org.openstreetmap.josm.command.AddPrimitivesCommand;
019import org.openstreetmap.josm.data.UndoRedoHandler;
020import org.openstreetmap.josm.data.coor.EastNorth;
021import org.openstreetmap.josm.data.osm.NodeData;
022import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
023import org.openstreetmap.josm.data.osm.PrimitiveData;
024import org.openstreetmap.josm.data.osm.RelationData;
025import org.openstreetmap.josm.data.osm.RelationMemberData;
026import org.openstreetmap.josm.data.osm.WayData;
027import org.openstreetmap.josm.data.projection.ProjectionRegistry;
028import org.openstreetmap.josm.gui.ExtendedDialog;
029import org.openstreetmap.josm.gui.MainApplication;
030import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
031import org.openstreetmap.josm.gui.layer.OsmDataLayer;
032import org.openstreetmap.josm.tools.JosmRuntimeException;
033import org.openstreetmap.josm.tools.bugreport.BugReport;
034
035/**
036 * This transfer support allows us to transfer primitives. This is the default paste action when primitives were copied.
037 * @author Michael Zangl
038 * @since 10604
039 */
040public final class PrimitiveDataPaster extends AbstractOsmDataPaster {
041    /**
042     * Create a new {@link PrimitiveDataPaster}
043     */
044    public PrimitiveDataPaster() {
045        super(PrimitiveTransferData.DATA_FLAVOR);
046    }
047
048    @Override
049    public boolean importData(TransferSupport support, final OsmDataLayer layer, EastNorth pasteAt)
050            throws UnsupportedFlavorException, IOException {
051        PrimitiveTransferData pasteBuffer = (PrimitiveTransferData) support.getTransferable().getTransferData(df);
052        // Allow to cancel paste if there are incomplete primitives
053        if (pasteBuffer.hasIncompleteData() && !confirmDeleteIncomplete()) {
054            return false;
055        }
056
057        EastNorth center = pasteBuffer.getCenter();
058        EastNorth offset = center == null || pasteAt == null ? new EastNorth(0, 0) : pasteAt.subtract(center);
059
060        AddPrimitivesCommand command = createNewPrimitives(pasteBuffer, offset, layer);
061
062        /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
063        UndoRedoHandler.getInstance().add(command);
064        return true;
065    }
066
067    private static AddPrimitivesCommand createNewPrimitives(PrimitiveTransferData pasteBuffer, EastNorth offset, OsmDataLayer layer) {
068        // Make a copy of pasteBuffer and map from old id to copied data id
069        List<PrimitiveData> bufferCopy = new ArrayList<>();
070        List<PrimitiveData> toSelect = new ArrayList<>();
071        EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = generateNewPrimitives(pasteBuffer, bufferCopy, toSelect);
072
073        // Update references in copied buffer
074        for (PrimitiveData data : bufferCopy) {
075            try {
076                if (data instanceof NodeData) {
077                    NodeData nodeData = (NodeData) data;
078                    nodeData.setEastNorth(nodeData.getEastNorth(ProjectionRegistry.getProjection()).add(offset));
079                } else if (data instanceof WayData) {
080                    updateNodes(newIds.get(OsmPrimitiveType.NODE), data);
081                } else if (data instanceof RelationData) {
082                    updateMembers(newIds, data);
083                }
084            } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
085                throw BugReport.intercept(e).put("data", data);
086            }
087        }
088        return new AddPrimitivesCommand(bufferCopy, toSelect, layer.getDataSet());
089    }
090
091    private static EnumMap<OsmPrimitiveType, Map<Long, Long>> generateNewPrimitives(PrimitiveTransferData pasteBuffer,
092            List<PrimitiveData> bufferCopy, List<PrimitiveData> toSelect) {
093        EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = new EnumMap<>(OsmPrimitiveType.class);
094        newIds.put(OsmPrimitiveType.NODE, new HashMap<Long, Long>());
095        newIds.put(OsmPrimitiveType.WAY, new HashMap<Long, Long>());
096        newIds.put(OsmPrimitiveType.RELATION, new HashMap<Long, Long>());
097
098        for (PrimitiveData data : pasteBuffer.getAll()) {
099            if (!data.isUsable()) {
100                continue;
101            }
102            PrimitiveData copy = data.makeCopy();
103            // don't know why this is reset, but we need it to not crash on copying incomplete nodes.
104            boolean wasIncomplete = copy.isIncomplete();
105            copy.clearOsmMetadata();
106            copy.setIncomplete(wasIncomplete);
107            newIds.get(data.getType()).put(data.getUniqueId(), copy.getUniqueId());
108
109            bufferCopy.add(copy);
110            if (pasteBuffer.getDirectlyAdded().contains(data)) {
111                toSelect.add(copy);
112            }
113        }
114        return newIds;
115    }
116
117    private static void updateMembers(EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds, PrimitiveData data) {
118        List<RelationMemberData> newMembers = new ArrayList<>();
119        for (RelationMemberData member : ((RelationData) data).getMembers()) {
120            OsmPrimitiveType memberType = member.getMemberType();
121            Long newId = newIds.get(memberType).get(member.getMemberId());
122            if (newId != null) {
123                newMembers.add(new RelationMemberData(member.getRole(), memberType, newId));
124            }
125        }
126        ((RelationData) data).setMembers(newMembers);
127    }
128
129    private static void updateNodes(Map<Long, Long> newNodeIds, PrimitiveData data) {
130        List<Long> newNodes = ((WayData) data).getNodeIds().stream()
131                .map(newNodeIds::get)
132                .filter(Objects::nonNull)
133                .collect(Collectors.toList());
134        ((WayData) data).setNodeIds(newNodes);
135    }
136
137    private static boolean confirmDeleteIncomplete() {
138        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Delete incomplete members?"),
139                tr("Paste without incomplete members"), tr("Cancel"));
140        ed.setButtonIcons("dialogs/relation/deletemembers", "cancel");
141        ed.setContent(tr(
142                "The copied data contains incomplete objects.  " + "When pasting the incomplete objects are removed.  "
143                        + "Do you want to paste the data without the incomplete objects?"));
144        ed.showDialog();
145        return ed.getValue() == 1;
146    }
147}