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