001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.datatransfer.importers; 003 004import java.awt.datatransfer.UnsupportedFlavorException; 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.EnumMap; 010import java.util.HashMap; 011import java.util.List; 012import java.util.Map; 013 014import javax.swing.TransferHandler.TransferSupport; 015 016import org.openstreetmap.josm.Main; 017import org.openstreetmap.josm.command.ChangePropertyCommand; 018import org.openstreetmap.josm.command.Command; 019import org.openstreetmap.josm.data.osm.IPrimitive; 020import org.openstreetmap.josm.data.osm.Node; 021import org.openstreetmap.josm.data.osm.OsmPrimitive; 022import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 023import org.openstreetmap.josm.data.osm.Tag; 024import org.openstreetmap.josm.data.osm.TagCollection; 025import org.openstreetmap.josm.data.osm.TagMap; 026import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog; 027import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData; 028 029/** 030 * This class helps pasting tags from other primitives. It handles resolving conflicts. 031 * @author Michael Zangl 032 * @since 10737 033 */ 034public class PrimitiveTagTransferPaster extends AbstractTagPaster { 035 /** 036 * Create a new {@link PrimitiveTagTransferPaster} 037 */ 038 public PrimitiveTagTransferPaster() { 039 super(PrimitiveTagTransferData.FLAVOR); 040 } 041 042 @Override 043 public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection) 044 throws UnsupportedFlavorException, IOException { 045 Object o = support.getTransferable().getTransferData(df); 046 if (!(o instanceof PrimitiveTagTransferData)) 047 return false; 048 PrimitiveTagTransferData data = (PrimitiveTagTransferData) o; 049 050 TagPasteSupport tagPaster = new TagPasteSupport(data, selection); 051 List<Command> commands = new ArrayList<>(); 052 for (Tag tag : tagPaster.execute()) { 053 Map<String, String> tags = new HashMap<>(1); 054 tags.put(tag.getKey(), "".equals(tag.getValue()) ? null : tag.getValue()); 055 ChangePropertyCommand cmd = new ChangePropertyCommand(Main.main.getEditDataSet(), selection, tags); 056 if (cmd.getObjectsNumber() > 0) { 057 commands.add(cmd); 058 } 059 } 060 commitCommands(selection, commands); 061 return true; 062 } 063 064 @Override 065 protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException { 066 PrimitiveTagTransferData data = (PrimitiveTagTransferData) support.getTransferable().getTransferData(df); 067 068 TagPasteSupport tagPaster = new TagPasteSupport(data, Arrays.asList(new Node())); 069 return new TagMap(tagPaster.execute()); 070 } 071 072 private static class TagPasteSupport { 073 private final PrimitiveTagTransferData data; 074 private final Collection<? extends IPrimitive> selection; 075 private final List<Tag> tags = new ArrayList<>(); 076 077 /** 078 * Constructs a new {@code TagPasteSupport}. 079 * @param data source tags to paste 080 * @param selection target primitives 081 */ 082 TagPasteSupport(PrimitiveTagTransferData data, Collection<? extends IPrimitive> selection) { 083 super(); 084 this.data = data; 085 this.selection = selection; 086 } 087 088 /** 089 * Pastes the tags from a homogeneous source (the selection consisting 090 * of one type of {@link OsmPrimitive}s only). 091 * 092 * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives, 093 * regardless of their type, receive the same tags. 094 */ 095 protected void pasteFromHomogeneousSource() { 096 TagCollection tc = null; 097 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) { 098 TagCollection tc1 = data.getForPrimitives(type); 099 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}