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.List; 011import java.util.Map; 012 013import javax.swing.TransferHandler.TransferSupport; 014 015import org.openstreetmap.josm.Main; 016import org.openstreetmap.josm.command.ChangePropertyCommand; 017import org.openstreetmap.josm.command.Command; 018import org.openstreetmap.josm.data.osm.IPrimitive; 019import org.openstreetmap.josm.data.osm.Node; 020import org.openstreetmap.josm.data.osm.OsmPrimitive; 021import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 022import org.openstreetmap.josm.data.osm.Tag; 023import org.openstreetmap.josm.data.osm.TagCollection; 024import org.openstreetmap.josm.data.osm.TagMap; 025import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog; 026import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData; 027 028/** 029 * This class helps pasting tags form other primitives. It handles resolving conflicts. 030 * @author Michael Zangl 031 * @since 10737 032 */ 033public class PrimitiveTagTransferPaster extends AbstractTagPaster { 034 /** 035 * Create a new {@link PrimitiveTagTransferPaster} 036 */ 037 public PrimitiveTagTransferPaster() { 038 super(PrimitiveTagTransferData.FLAVOR); 039 } 040 041 @Override 042 public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection) 043 throws UnsupportedFlavorException, IOException { 044 PrimitiveTagTransferData data = (PrimitiveTagTransferData) support.getTransferable().getTransferData(df); 045 046 TagPasteSupport tagPaster = new TagPasteSupport(data, selection); 047 List<Command> commands = new ArrayList<>(); 048 for (Tag tag : tagPaster.execute()) { 049 commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue()) ? null : tag.getValue())); 050 } 051 commitCommands(selection, commands); 052 return true; 053 } 054 055 @Override 056 protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException { 057 PrimitiveTagTransferData data = (PrimitiveTagTransferData) support.getTransferable().getTransferData(df); 058 059 TagPasteSupport tagPaster = new TagPasteSupport(data, Arrays.asList(new Node())); 060 return new TagMap(tagPaster.execute()); 061 } 062 063 private static class TagPasteSupport { 064 private final PrimitiveTagTransferData data; 065 private final Collection<? extends IPrimitive> selection; 066 private final List<Tag> tags = new ArrayList<>(); 067 068 /** 069 * Constructs a new {@code TagPasteSupport}. 070 * @param data source tags to paste 071 * @param selection target primitives 072 */ 073 TagPasteSupport(PrimitiveTagTransferData data, Collection<? extends IPrimitive> selection) { 074 super(); 075 this.data = data; 076 this.selection = selection; 077 } 078 079 /** 080 * Pastes the tags from a homogeneous source (the selection consisting 081 * of one type of {@link OsmPrimitive}s only). 082 * 083 * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives, 084 * regardless of their type, receive the same tags. 085 */ 086 protected void pasteFromHomogeneousSource() { 087 TagCollection tc = null; 088 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) { 089 TagCollection tc1 = data.getForPrimitives(type); 090 if (!tc1.isEmpty()) { 091 tc = tc1; 092 } 093 } 094 if (tc == null) 095 // no tags found to paste. Abort. 096 return; 097 098 if (!tc.isApplicableToPrimitive()) { 099 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent); 100 dialog.populate(tc, data.getStatistics(), getTargetStatistics()); 101 dialog.setVisible(true); 102 if (dialog.isCanceled()) 103 return; 104 buildTags(dialog.getResolution()); 105 } else { 106 // no conflicts in the source tags to resolve. Just apply the tags to the target primitives 107 buildTags(tc); 108 } 109 } 110 111 /** 112 * Replies true if this a heterogeneous source can be pasted without conflict to targets 113 * 114 * @return true if this a heterogeneous source can be pasted without conflicts to targets 115 */ 116 protected boolean canPasteFromHeterogeneousSourceWithoutConflict() { 117 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) { 118 if (hasTargetPrimitives(type)) { 119 TagCollection tc = data.getForPrimitives(type); 120 if (!tc.isEmpty() && !tc.isApplicableToPrimitive()) 121 return false; 122 } 123 } 124 return true; 125 } 126 127 /** 128 * Pastes the tags in the current selection of the paste buffer to a set of target primitives. 129 */ 130 protected void pasteFromHeterogeneousSource() { 131 if (canPasteFromHeterogeneousSourceWithoutConflict()) { 132 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) { 133 if (!data.getForPrimitives(type).isEmpty() && hasTargetPrimitives(type)) { 134 buildTags(data.getForPrimitives(type)); 135 } 136 } 137 } else { 138 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent); 139 dialog.populate( 140 data.getForPrimitives(OsmPrimitiveType.NODE), 141 data.getForPrimitives(OsmPrimitiveType.WAY), 142 data.getForPrimitives(OsmPrimitiveType.RELATION), 143 data.getStatistics(), 144 getTargetStatistics() 145 ); 146 dialog.setVisible(true); 147 if (dialog.isCanceled()) 148 return; 149 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) { 150 if (!data.getForPrimitives(type).isEmpty() && hasTargetPrimitives(type)) { 151 buildTags(dialog.getResolution(type)); 152 } 153 } 154 } 155 } 156 157 protected Map<OsmPrimitiveType, Integer> getTargetStatistics() { 158 Map<OsmPrimitiveType, Integer> ret = new EnumMap<>(OsmPrimitiveType.class); 159 for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) { 160 int count = (int) selection.stream().filter(p -> type == p.getType()).count(); 161 if (count > 0) { 162 ret.put(type, count); 163 } 164 } 165 return ret; 166 } 167 168 /** 169 * Replies true if there is at least one primitive of type <code>type</code> 170 * is in the target collection 171 * 172 * @param type the type to look for 173 * @return true if there is at least one primitive of type <code>type</code> in the collection 174 * <code>selection</code> 175 */ 176 protected boolean hasTargetPrimitives(OsmPrimitiveType type) { 177 return selection.stream().anyMatch(p -> type == p.getType()); 178 } 179 180 protected void buildTags(TagCollection tc) { 181 for (String key : tc.getKeys()) { 182 tags.add(new Tag(key, tc.getValues(key).iterator().next())); 183 } 184 } 185 186 /** 187 * Performs the paste operation. 188 * @return list of tags 189 */ 190 public List<Tag> execute() { 191 tags.clear(); 192 if (data.isHeterogeneousSource()) { 193 pasteFromHeterogeneousSource(); 194 } else { 195 pasteFromHomogeneousSource(); 196 } 197 return tags; 198 } 199 200 @Override 201 public String toString() { 202 return "PasteSupport [data=" + data + ", selection=" + selection + ']'; 203 } 204 } 205}