source: josm/trunk/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveTagTransferPaster.java @ 12920

Last change on this file since 12920 was 12920, checked in by Don-vip, 3 months ago

fix #15387 - IAE occurs when pasting tags into relation editor using "Paste tags from buffer" (regression from #13036)

  • Property svn:eol-style set to native
File size: 8.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.datatransfer.importers;
3
4import java.awt.datatransfer.UnsupportedFlavorException;
5import java.io.IOException;
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.EnumMap;
10import java.util.HashMap;
11import java.util.List;
12import java.util.Map;
13
14import javax.swing.TransferHandler.TransferSupport;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.command.ChangePropertyCommand;
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.data.osm.IPrimitive;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
23import org.openstreetmap.josm.data.osm.Tag;
24import org.openstreetmap.josm.data.osm.TagCollection;
25import org.openstreetmap.josm.data.osm.TagMap;
26import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
27import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData;
28
29/**
30 * This class helps pasting tags from other primitives. It handles resolving conflicts.
31 * @author Michael Zangl
32 * @since 10737
33 */
34public class PrimitiveTagTransferPaster extends AbstractTagPaster {
35    /**
36     * Create a new {@link PrimitiveTagTransferPaster}
37     */
38    public PrimitiveTagTransferPaster() {
39        super(PrimitiveTagTransferData.FLAVOR);
40    }
41
42    @Override
43    public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection)
44            throws UnsupportedFlavorException, IOException {
45        Object o = support.getTransferable().getTransferData(df);
46        if (!(o instanceof PrimitiveTagTransferData))
47            return false;
48        PrimitiveTagTransferData data = (PrimitiveTagTransferData) o;
49
50        TagPasteSupport tagPaster = new TagPasteSupport(data, selection);
51        List<Command> commands = new ArrayList<>();
52        for (Tag tag : tagPaster.execute()) {
53            Map<String, String> tags = new HashMap<>(1);
54            tags.put(tag.getKey(), "".equals(tag.getValue()) ? null : tag.getValue());
55            ChangePropertyCommand cmd = new ChangePropertyCommand(Main.main.getEditDataSet(), selection, tags);
56            if (cmd.getObjectsNumber() > 0) {
57                commands.add(cmd);
58            }
59        }
60        commitCommands(selection, commands);
61        return true;
62    }
63
64    @Override
65    protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
66        PrimitiveTagTransferData data = (PrimitiveTagTransferData) support.getTransferable().getTransferData(df);
67
68        TagPasteSupport tagPaster = new TagPasteSupport(data, Arrays.asList(new Node()));
69        return new TagMap(tagPaster.execute());
70    }
71
72    private static class TagPasteSupport {
73        private final PrimitiveTagTransferData data;
74        private final Collection<? extends IPrimitive> selection;
75        private final List<Tag> tags = new ArrayList<>();
76
77        /**
78         * Constructs a new {@code TagPasteSupport}.
79         * @param data source tags to paste
80         * @param selection target primitives
81         */
82        TagPasteSupport(PrimitiveTagTransferData data, Collection<? extends IPrimitive> selection) {
83            super();
84            this.data = data;
85            this.selection = selection;
86        }
87
88        /**
89         * Pastes the tags from a homogeneous source (the selection consisting
90         * of one type of {@link OsmPrimitive}s only).
91         *
92         * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
93         * regardless of their type, receive the same tags.
94         */
95        protected void pasteFromHomogeneousSource() {
96            TagCollection tc = null;
97            for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
98                TagCollection tc1 = data.getForPrimitives(type);
99                if (!tc1.isEmpty()) {
100                    tc = tc1;
101                }
102            }
103            if (tc == null)
104                // no tags found to paste. Abort.
105                return;
106
107            if (!tc.isApplicableToPrimitive()) {
108                PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
109                dialog.populate(tc, data.getStatistics(), getTargetStatistics());
110                dialog.setVisible(true);
111                if (dialog.isCanceled())
112                    return;
113                buildTags(dialog.getResolution());
114            } else {
115                // no conflicts in the source tags to resolve. Just apply the tags to the target primitives
116                buildTags(tc);
117            }
118        }
119
120        /**
121         * Replies true if this a heterogeneous source can be pasted without conflict to targets
122         *
123         * @return true if this a heterogeneous source can be pasted without conflicts to targets
124         */
125        protected boolean canPasteFromHeterogeneousSourceWithoutConflict() {
126            for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
127                if (hasTargetPrimitives(type)) {
128                    TagCollection tc = data.getForPrimitives(type);
129                    if (!tc.isEmpty() && !tc.isApplicableToPrimitive())
130                        return false;
131                }
132            }
133            return true;
134        }
135
136        /**
137         * Pastes the tags in the current selection of the paste buffer to a set of target primitives.
138         */
139        protected void pasteFromHeterogeneousSource() {
140            if (canPasteFromHeterogeneousSourceWithoutConflict()) {
141                for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
142                    if (!data.getForPrimitives(type).isEmpty() && hasTargetPrimitives(type)) {
143                        buildTags(data.getForPrimitives(type));
144                    }
145                }
146            } else {
147                PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
148                dialog.populate(
149                        data.getForPrimitives(OsmPrimitiveType.NODE),
150                        data.getForPrimitives(OsmPrimitiveType.WAY),
151                        data.getForPrimitives(OsmPrimitiveType.RELATION),
152                        data.getStatistics(),
153                        getTargetStatistics()
154                );
155                dialog.setVisible(true);
156                if (dialog.isCanceled())
157                    return;
158                for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
159                    if (!data.getForPrimitives(type).isEmpty() && hasTargetPrimitives(type)) {
160                        buildTags(dialog.getResolution(type));
161                    }
162                }
163            }
164        }
165
166        protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
167            Map<OsmPrimitiveType, Integer> ret = new EnumMap<>(OsmPrimitiveType.class);
168            for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
169                int count = (int) selection.stream().filter(p -> type == p.getType()).count();
170                if (count > 0) {
171                    ret.put(type, count);
172                }
173            }
174            return ret;
175        }
176
177        /**
178         * Replies true if there is at least one primitive of type <code>type</code>
179         * is in the target collection
180         *
181         * @param type  the type to look for
182         * @return true if there is at least one primitive of type <code>type</code> in the collection
183         * <code>selection</code>
184         */
185        protected boolean hasTargetPrimitives(OsmPrimitiveType type) {
186            return selection.stream().anyMatch(p -> type == p.getType());
187        }
188
189        protected void buildTags(TagCollection tc) {
190            for (String key : tc.getKeys()) {
191                tags.add(new Tag(key, tc.getValues(key).iterator().next()));
192            }
193        }
194
195        /**
196         * Performs the paste operation.
197         * @return list of tags
198         */
199        public List<Tag> execute() {
200            tags.clear();
201            if (data.isHeterogeneousSource()) {
202                pasteFromHeterogeneousSource();
203            } else {
204                pasteFromHomogeneousSource();
205            }
206            return tags;
207        }
208
209        @Override
210        public String toString() {
211            return "PasteSupport [data=" + data + ", selection=" + selection + ']';
212        }
213    }
214}
Note: See TracBrowser for help on using the repository browser.