Ticket #12900: patch-fix-12900.patch

File patch-fix-12900.patch, 45.6 KB (added by michael2402, 10 years ago)
  • src/org/openstreetmap/josm/actions/PasteTagsAction.java

    diff --git a/src/org/openstreetmap/josm/actions/PasteTagsAction.java b/src/org/openstreetmap/josm/actions/PasteTagsAction.java
    index d111ab8..a62cadd 100644
    a b package org.openstreetmap.josm.actions;  
    33
    44import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    55import static org.openstreetmap.josm.tools.I18n.tr;
    6 import static org.openstreetmap.josm.tools.I18n.trn;
    76
    87import java.awt.event.ActionEvent;
    98import java.awt.event.KeyEvent;
    10 import java.util.ArrayList;
    119import java.util.Collection;
    12 import java.util.EnumMap;
    13 import java.util.List;
    14 import java.util.Map;
    15 import java.util.Map.Entry;
    1610
    17 import org.openstreetmap.josm.Main;
    18 import org.openstreetmap.josm.command.ChangePropertyCommand;
    19 import org.openstreetmap.josm.command.Command;
    20 import org.openstreetmap.josm.command.SequenceCommand;
    2111import org.openstreetmap.josm.data.osm.DataSet;
    2212import org.openstreetmap.josm.data.osm.OsmPrimitive;
    23 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    24 import org.openstreetmap.josm.data.osm.PrimitiveData;
    25 import org.openstreetmap.josm.data.osm.Tag;
    26 import org.openstreetmap.josm.data.osm.TagCollection;
    27 import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
    2813import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    29 import org.openstreetmap.josm.tools.I18n;
    3014import org.openstreetmap.josm.tools.Shortcut;
    31 import org.openstreetmap.josm.tools.TextTagParser;
    3215
    3316/**
    3417 * Action, to paste all tags from one primitive to another.
    public final class PasteTagsAction extends JosmAction {  
    5437        putValue("help", help);
    5538    }
    5639
    57     /**
    58      * Used to update the tags.
    59      */
    60     public static class TagPaster {
    61 
    62         private final Collection<PrimitiveData> source;
    63         private final Collection<OsmPrimitive> target;
    64         private final List<Tag> tags = new ArrayList<>();
    65 
    66         /**
    67          * Constructs a new {@code TagPaster}.
    68          * @param source source primitives
    69          * @param target target primitives
    70          */
    71         public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
    72             this.source = source;
    73             this.target = target;
    74         }
    75 
    76         /**
    77          * Determines if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
    78          * {@link OsmPrimitive}s of exactly one type
    79          * @return true if the source for tag pasting is heterogeneous
    80          */
    81         protected boolean isHeterogeneousSource() {
    82             int count = 0;
    83             count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? (count + 1) : count;
    84             count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? (count + 1) : count;
    85             count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? (count + 1) : count;
    86             return count > 1;
    87         }
    88 
    89         /**
    90          * Replies all primitives of type <code>type</code> in the current selection.
    91          *
    92          * @param type  the type
    93          * @return all primitives of type <code>type</code> in the current selection.
    94          */
    95         protected Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
    96             return PrimitiveData.getFilteredList(source, type);
    97         }
    98 
    99         /**
    100          * Replies the collection of tags for all primitives of type <code>type</code> in the current
    101          * selection
    102          *
    103          * @param type  the type
    104          * @return the collection of tags for all primitives of type <code>type</code> in the current
    105          * selection
    106          */
    107         protected TagCollection getSourceTagsByType(OsmPrimitiveType type) {
    108             return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
    109         }
    110 
    111         /**
    112          * Replies true if there is at least one tag in the current selection for primitives of
    113          * type <code>type</code>
    114          *
    115          * @param type the type
    116          * @return true if there is at least one tag in the current selection for primitives of
    117          * type <code>type</code>
    118          */
    119         protected boolean hasSourceTagsByType(OsmPrimitiveType type) {
    120             return !getSourceTagsByType(type).isEmpty();
    121         }
    122 
    123         protected void buildTags(TagCollection tc) {
    124             for (String key : tc.getKeys()) {
    125                 tags.add(new Tag(key, tc.getValues(key).iterator().next()));
    126             }
    127         }
    128 
    129         protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
    130             Map<OsmPrimitiveType, Integer> ret = new EnumMap<>(OsmPrimitiveType.class);
    131             for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
    132                 if (!getSourceTagsByType(type).isEmpty()) {
    133                     ret.put(type, getSourcePrimitivesByType(type).size());
    134                 }
    135             }
    136             return ret;
    137         }
    138 
    139         protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
    140             Map<OsmPrimitiveType, Integer> ret = new EnumMap<>(OsmPrimitiveType.class);
    141             for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
    142                 int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
    143                 if (count > 0) {
    144                     ret.put(type, count);
    145                 }
    146             }
    147             return ret;
    148         }
    149 
    150         /**
    151          * Pastes the tags from a homogeneous source (the selection consisting
    152          * of one type of {@link OsmPrimitive}s only).
    153          *
    154          * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
    155          * regardless of their type, receive the same tags.
    156          */
    157         protected void pasteFromHomogeneousSource() {
    158             TagCollection tc = null;
    159             for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
    160                 TagCollection tc1 = getSourceTagsByType(type);
    161                 if (!tc1.isEmpty()) {
    162                     tc = tc1;
    163                 }
    164             }
    165             if (tc == null)
    166                 // no tags found to paste. Abort.
    167                 return;
    168 
    169             if (!tc.isApplicableToPrimitive()) {
    170                 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
    171                 dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
    172                 dialog.setVisible(true);
    173                 if (dialog.isCanceled())
    174                     return;
    175                 buildTags(dialog.getResolution());
    176             } else {
    177                 // no conflicts in the source tags to resolve. Just apply the tags to the target primitives
    178                 buildTags(tc);
    179             }
    180         }
    181 
    182         /**
    183          * Replies true if there is at least one primitive of type <code>type</code>
    184          * is in the target collection
    185          *
    186          * @param type  the type to look for
    187          * @return true if there is at least one primitive of type <code>type</code> in the collection
    188          * <code>selection</code>
    189          */
    190         protected boolean hasTargetPrimitives(Class<? extends OsmPrimitive> type) {
    191             return !OsmPrimitive.getFilteredList(target, type).isEmpty();
    192         }
    193 
    194         /**
    195          * Replies true if this a heterogeneous source can be pasted without conflict to targets
    196          *
    197          * @return true if this a heterogeneous source can be pasted without conflicts to targets
    198          */
    199         protected boolean canPasteFromHeterogeneousSourceWithoutConflict() {
    200             for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
    201                 if (hasTargetPrimitives(type.getOsmClass())) {
    202                     TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
    203                     if (!tc.isEmpty() && !tc.isApplicableToPrimitive())
    204                         return false;
    205                 }
    206             }
    207             return true;
    208         }
    209 
    210         /**
    211          * Pastes the tags in the current selection of the paste buffer to a set of target primitives.
    212          */
    213         protected void pasteFromHeterogeneousSource() {
    214             if (canPasteFromHeterogeneousSourceWithoutConflict()) {
    215                 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
    216                     if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
    217                         buildTags(getSourceTagsByType(type));
    218                     }
    219                 }
    220             } else {
    221                 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
    222                 dialog.populate(
    223                         getSourceTagsByType(OsmPrimitiveType.NODE),
    224                         getSourceTagsByType(OsmPrimitiveType.WAY),
    225                         getSourceTagsByType(OsmPrimitiveType.RELATION),
    226                         getSourceStatistics(),
    227                         getTargetStatistics()
    228                 );
    229                 dialog.setVisible(true);
    230                 if (dialog.isCanceled())
    231                     return;
    232                 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
    233                     if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
    234                         buildTags(dialog.getResolution(type));
    235                     }
    236                 }
    237             }
    238         }
    239 
    240         /**
    241          * Performs the paste operation.
    242          * @return list of tags
    243          */
    244         public List<Tag> execute() {
    245             tags.clear();
    246             if (isHeterogeneousSource()) {
    247                 pasteFromHeterogeneousSource();
    248             } else {
    249                 pasteFromHomogeneousSource();
    250             }
    251             return tags;
    252         }
    253 
    254     }
    255 
    25640    @Override
    25741    public void actionPerformed(ActionEvent e) {
    25842        Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
    public final class PasteTagsAction extends JosmAction {  
    26347        transferHandler.pasteTags(selection);
    26448    }
    26549
    266     /**
    267      * Paste tags from arbitrary text, not using JOSM buffer
    268      * @param selection selected primitives
    269      * @param text text containing tags
    270      * @return true if action was successful
    271      * @see TextTagParser#readTagsFromText
    272      */
    273     public static boolean pasteTagsFromText(Collection<OsmPrimitive> selection, String text) {
    274         Map<String, String> tags = TextTagParser.readTagsFromText(text);
    275         if (tags == null || tags.isEmpty()) {
    276             TextTagParser.showBadBufferMessage(help);
    277             return false;
    278         }
    279         if (!TextTagParser.validateTags(tags)) return false;
    280 
    281         List<Command> commands = new ArrayList<>(tags.size());
    282         for (Entry<String, String> entry: tags.entrySet()) {
    283             String v = entry.getValue();
    284             commands.add(new ChangePropertyCommand(selection, entry.getKey(), "".equals(v) ? null : v));
    285         }
    286         commitCommands(selection, commands);
    287         return !commands.isEmpty();
    288     }
    289 
    290     /**
    291      * Create and execute SequenceCommand with descriptive title
    292      * @param selection selected primitives
    293      * @param commands the commands to perform in a sequential command
    294      */
    295     private static void commitCommands(Collection<OsmPrimitive> selection, List<Command> commands) {
    296         if (!commands.isEmpty()) {
    297             String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
    298             String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
    299             @I18n.QuirkyPluralString
    300             final String title = title1 + ' ' + title2;
    301             Main.main.undoRedo.add(
    302                     new SequenceCommand(
    303                             title,
    304                             commands
    305                     ));
    306         }
    307     }
    308 
    30950    @Override
    31051    protected void updateEnabledState() {
    31152        DataSet ds = getLayerManager().getEditDataSet();
  • src/org/openstreetmap/josm/data/osm/Tag.java

    diff --git a/src/org/openstreetmap/josm/data/osm/Tag.java b/src/org/openstreetmap/josm/data/osm/Tag.java
    index 826d8e8..f02cff5 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm;
    33
     4import java.io.Serializable;
    45import java.util.Collection;
    56import java.util.Collections;
    67import java.util.Map;
    import org.openstreetmap.josm.tools.Utils;  
    1617 * It implements the {@link Tagged} interface. However, since instances of this class are immutable,
    1718 * the modifying methods throw an {@link UnsupportedOperationException}.
    1819 */
    19 public class Tag implements Tagged, Entry<String, String> {
     20public class Tag implements Tagged, Entry<String, String>, Serializable {
     21
     22    private static final long serialVersionUID = 1;
    2023
    2124    private final String key;
    2225    private final String value;
  • src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java b/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java
    index af392dc..f85f934 100644
    a b public class PasteTagsConflictResolverDialog extends JDialog implements Property  
    289289            setVisible(false);
    290290        }
    291291
    292         protected void updateEnabledState() {
     292        void updateEnabledState() {
    293293            if (mode == null) {
    294294                setEnabled(false);
    295295            } else if (mode.equals(Mode.RESOLVING_ONE_TAGCOLLECTION_ONLY)) {
    296296                setEnabled(allPrimitivesResolver.getModel().isResolvedCompletely());
    297297            } else {
    298                 boolean enabled = true;
    299                 for (TagConflictResolver val: resolvers.values()) {
    300                     enabled &= val.getModel().isResolvedCompletely();
    301                 }
    302                 setEnabled(enabled);
     298                setEnabled(resolvers.values().stream().allMatch(val -> val.getModel().isResolvedCompletely()));
    303299            }
    304300        }
    305301
    public class PasteTagsConflictResolverDialog extends JDialog implements Property  
    345341                TagConflictResolver resolver = (TagConflictResolver) tpResolvers.getComponentAt(i);
    346342                if (model == resolver.getModel()) {
    347343                    tpResolvers.setIconAt(i,
    348                             (Boolean) evt.getNewValue() ? iconResolved : iconUnresolved
     344                            (Integer) evt.getNewValue() == 0 ? iconResolved : iconUnresolved
    349345
    350346                    );
    351347                }
  • src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java

    diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java b/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java
    index a085c4f..a347d4c 100644
    a b public class TagConflictResolverModel extends DefaultTableModel {  
    2121
    2222    private transient TagCollection tags;
    2323    private List<String> displayedKeys;
    24     private Set<String> keysWithConflicts;
     24    private Set<String> keysWithConflicts = new HashSet<>();
    2525    private transient Map<String, MultiValueResolutionDecision> decisions;
    2626    private int numConflicts;
    2727    private final PropertyChangeSupport support;
    public class TagConflictResolverModel extends DefaultTableModel {  
    5353    }
    5454
    5555    protected void refreshNumConflicts() {
    56         int count = 0;
    57         for (MultiValueResolutionDecision d : decisions.values()) {
    58             if (!d.isDecided()) {
    59                 count++;
    60             }
    61         }
    62         setNumConflicts(count);
     56        setNumConflicts((int) decisions.values().stream().filter(d -> !d.isDecided()).count());
    6357    }
    6458
    6559    protected void sort() {
    public class TagConflictResolverModel extends DefaultTableModel {  
    121115        CheckParameterUtil.ensureParameterNotNull(tags, "tags");
    122116        this.tags = tags;
    123117        displayedKeys = new ArrayList<>();
    124         this.keysWithConflicts = keysWithConflicts == null ? new HashSet<>() : keysWithConflicts;
     118        if (keysWithConflicts != null) {
     119            this.keysWithConflicts.addAll(keysWithConflicts);
     120        }
    125121        decisions = new HashMap<>();
    126122        rebuild();
    127123    }
    public class TagConflictResolverModel extends DefaultTableModel {  
    182178     * @return true if each {@link MultiValueResolutionDecision} is decided; false otherwise
    183179     */
    184180    public boolean isResolvedCompletely() {
    185         return numConflicts == 0 && keysWithConflicts != null && keysWithConflicts.isEmpty();
     181        return numConflicts == 0;
    186182    }
    187183
     184    /**
     185     * Gets the number of reamining conflicts.
     186     * @return The number
     187     */
    188188    public int getNumConflicts() {
    189189        return numConflicts;
    190190    }
    191191
     192    /**
     193     * Gets the number of decisions the user can take
     194     * @return The number of decisions
     195     */
    192196    public int getNumDecisions() {
    193197        return decisions == null ? 0 : decisions.size();
    194198    }
  • src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java b/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java
    index 5076f86..5001c48 100644
    a b import org.openstreetmap.josm.data.osm.OsmPrimitive;  
    1616import org.openstreetmap.josm.gui.datatransfer.importers.AbstractOsmDataPaster;
    1717import org.openstreetmap.josm.gui.datatransfer.importers.FilePaster;
    1818import org.openstreetmap.josm.gui.datatransfer.importers.PrimitiveDataPaster;
     19import org.openstreetmap.josm.gui.datatransfer.importers.PrimitiveTagTransferPaster;
    1920import org.openstreetmap.josm.gui.datatransfer.importers.TagTransferPaster;
    2021import org.openstreetmap.josm.gui.datatransfer.importers.TextTagPaster;
    2122import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    public class OsmTransferHandler extends TransferHandler {  
    2930
    3031    private static final Collection<AbstractOsmDataPaster> SUPPORTED = Arrays.asList(
    3132            new FilePaster(), new PrimitiveDataPaster(),
     33            new PrimitiveTagTransferPaster(),
    3234            new TagTransferPaster(), new TextTagPaster());
    3335
    3436    @Override
  • src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java b/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java
    index 140276e..8d6c9a0 100644
    a b import java.util.List;  
    1111import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    1212import org.openstreetmap.josm.data.osm.PrimitiveData;
    1313import org.openstreetmap.josm.gui.datatransfer.data.OsmLayerTransferData;
     14import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData;
    1415import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    1516import org.openstreetmap.josm.gui.datatransfer.data.TagTransferData;
    1617import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    public class PrimitiveTransferable implements Transferable {  
    7475            return getStringData();
    7576        } else if (PrimitiveTransferData.DATA_FLAVOR.equals(flavor)) {
    7677            return primitives;
     78        } else if (PrimitiveTagTransferData.FLAVOR.equals(flavor)) {
     79            return new PrimitiveTagTransferData(primitives);
    7780        } else if (TagTransferData.FLAVOR.equals(flavor)) {
    7881            return new TagTransferData(primitives.getDirectlyAdded());
    7982        } else if (sourceLayer != null && OsmLayerTransferData.FLAVORS.contains(flavor)) {
  • new file src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTagTransferData.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTagTransferData.java b/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTagTransferData.java
    new file mode 100644
    index 0000000..37ef416
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.data;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.io.Serializable;
     6import java.util.Collection;
     7import java.util.Collections;
     8import java.util.EnumMap;
     9import java.util.Map;
     10
     11import org.openstreetmap.josm.data.osm.OsmPrimitive;
     12import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     13import org.openstreetmap.josm.data.osm.PrimitiveData;
     14import org.openstreetmap.josm.data.osm.TagCollection;
     15
     16/**
     17 * This is a variant of {@link TagTransferData} that holds tags that were copied from a collection of primitives.
     18 * @author Michael Zangl
     19 * @since xxx
     20 */
     21public class PrimitiveTagTransferData implements Serializable {
     22
     23    private static final long serialVersionUID = 1;
     24
     25    /**
     26     * This is a data flavor added
     27     */
     28    public static final DataFlavor FLAVOR = new DataFlavor(TagTransferData.class, "OSM Primitive Tags");
     29
     30    private final EnumMap<OsmPrimitiveType, TagCollection> tags = new EnumMap<>(OsmPrimitiveType.class);
     31    private final EnumMap<OsmPrimitiveType, Integer> counts = new EnumMap<>(OsmPrimitiveType.class);
     32
     33    /**
     34     * Create a new {@link PrimitiveTagTransferData}
     35     * @param source The primitives to initialize this object with.
     36     */
     37    public PrimitiveTagTransferData(Collection<? extends PrimitiveData> source) {
     38        for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
     39            tags.put(type, new TagCollection());
     40        }
     41
     42        for (PrimitiveData primitive : source) {
     43            tags.get(primitive.getType()).add(TagCollection.from(primitive));
     44            counts.merge(primitive.getType(), 1, (a, b) -> a + b);
     45        }
     46    }
     47
     48    /**
     49     * Create a new {@link PrimitiveTagTransferData}
     50     * @param data The primitives to initialize this object with.
     51     */
     52    public PrimitiveTagTransferData(PrimitiveTransferData data) {
     53        this(data.getDirectlyAdded());
     54    }
     55
     56    /**
     57     * Determines if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
     58     * {@link OsmPrimitive}s of exactly one type
     59     * @return true if the source for tag pasting is heterogeneous
     60     */
     61    public boolean isHeterogeneousSource() {
     62        return counts.size() > 1;
     63    }
     64
     65    /**
     66     * Gets the tags used for this primitive type.
     67     * @param type The primitive type
     68     * @return The tags as collection. Empty if no such type was copied
     69     */
     70    public TagCollection getForPrimitives(OsmPrimitiveType type) {
     71        return tags.get(type);
     72    }
     73
     74    /**
     75     * Gets the number of source primitives for the given type.
     76     * @param type The type
     77     * @return The number of source primitives of that type
     78     */
     79    public int getSourcePrimitiveCount(OsmPrimitiveType type) {
     80        return counts.getOrDefault(type, 0);
     81    }
     82
     83    /**
     84     * Gets the statistics of the source primitive counts. May contain no entries for unused types.
     85     * @return The statistics as map
     86     */
     87    public Map<OsmPrimitiveType, Integer> getStatistics() {
     88        return Collections.unmodifiableMap(counts);
     89    }
     90}
  • src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java b/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java
    index 52040d2..6bc5e58 100644
    a b import org.openstreetmap.josm.data.osm.Tagged;  
    1313/**
    1414 * This is a special transfer type that only transfers tag data.
    1515 * <p>
    16  * It currently contains all tags contained in the selection that was copied.
     16 * It contains all tags contained in the selection that was copied. For conflicting tags, any of the values may be used.
    1717 * @author Michael Zangl
    1818 * @since 10604
    1919 */
  • src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java
    index 12f4a96..8c5da7f 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.datatransfer.importers;
    33
     4import static org.openstreetmap.josm.tools.I18n.trn;
     5
    46import java.awt.datatransfer.DataFlavor;
    57import java.awt.datatransfer.UnsupportedFlavorException;
    68import java.io.IOException;
    79import java.util.Collection;
     10import java.util.Collections;
     11import java.util.List;
    812import java.util.Map;
    913
    1014import javax.swing.TransferHandler.TransferSupport;
    1115
    1216import org.openstreetmap.josm.Main;
    1317import org.openstreetmap.josm.command.ChangePropertyCommand;
     18import org.openstreetmap.josm.command.Command;
     19import org.openstreetmap.josm.command.SequenceCommand;
    1420import org.openstreetmap.josm.data.coor.EastNorth;
    1521import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1622import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     23import org.openstreetmap.josm.tools.I18n;
    1724
    1825/**
    1926 * This transfer support allows us to transfer tags to the selected primitives
    public abstract class AbstractTagPaster extends AbstractOsmDataPaster {  
    4148    public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection)
    4249            throws UnsupportedFlavorException, IOException {
    4350        ChangePropertyCommand command = new ChangePropertyCommand(selection, getTags(support));
    44         Main.main.undoRedo.add(command);
     51        commitCommands(selection, Collections.singletonList(command));
    4552        return true;
    4653    }
    4754
    4855    /**
     56     * Create and execute SequenceCommand with descriptive title
     57     * @param selection selected primitives
     58     * @param commands the commands to perform in a sequential command
     59     */
     60    protected static void commitCommands(Collection<? extends OsmPrimitive> selection, List<Command> commands) {
     61        if (!commands.isEmpty()) {
     62            String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
     63            String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
     64            @I18n.QuirkyPluralString
     65            final String title = title1 + ' ' + title2;
     66            Main.main.undoRedo.add(
     67                    new SequenceCommand(
     68                            title,
     69                            commands
     70                    ));
     71        }
     72    }
     73    /**
    4974     * Gets the tags that should be pasted.
    5075     * @param support The TransferSupport to get the tags from.
    5176     * @return The tags
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveTagTransferPaster.java

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

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java
    index 5b63ec7..404cc32 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.datatransfer.importers;
    33
     4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
     5
    46import java.awt.datatransfer.DataFlavor;
    57import java.awt.datatransfer.UnsupportedFlavorException;
    68import java.io.IOException;
    import org.openstreetmap.josm.tools.TextTagParser;  
    1719 * @since 10604
    1820 */
    1921public final class TextTagPaster extends AbstractTagPaster {
     22    private static final String help = ht("/Action/PasteTags");
    2023
    2124    /**
    2225     * Create a new {@link TextTagPaster}
    public final class TextTagPaster extends AbstractTagPaster {  
    2831    @Override
    2932    public boolean supports(TransferSupport support) {
    3033        try {
    31             return super.supports(support) && getTags(support) != null;
     34            return super.supports(support) && containsValidTags(support);
    3235        } catch (UnsupportedFlavorException | IOException e) {
    3336            Main.warn(e);
    3437            return false;
    3538        }
    3639    }
    3740
     41    private boolean containsValidTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
     42        Map<String, String> tags = getTagsImpl(support);
     43        return tags != null && !tags.isEmpty();
     44    }
     45
    3846    @Override
    3947    protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
     48        Map<String, String> tags = getTagsImpl(support);
     49        if (tags == null || tags.isEmpty()) {
     50            TextTagParser.showBadBufferMessage(help);
     51            throw new IOException("Invalid tags to paste.");
     52        }
     53        if (!TextTagParser.validateTags(tags)) {
     54            throw new IOException("Tags to paste are not valid.");
     55        }
     56        return tags;
     57    }
     58
     59    private Map<String, String> getTagsImpl(TransferSupport support) throws UnsupportedFlavorException, IOException {
    4060        return TextTagParser.readTagsFromText((String) support.getTransferable().getTransferData(df));
    4161    }
    4262}
  • deleted file test/unit/org/openstreetmap/josm/actions/PasteTagsActionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/actions/PasteTagsActionTest.java b/test/unit/org/openstreetmap/josm/actions/PasteTagsActionTest.java
    deleted file mode 100644
    index 0763efa..0000000
    + -  
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.actions;
    3 
    4 import static org.junit.Assert.assertFalse;
    5 import static org.junit.Assert.assertTrue;
    6 
    7 import java.util.Arrays;
    8 
    9 import org.junit.BeforeClass;
    10 import org.junit.Test;
    11 import org.openstreetmap.josm.JOSMFixture;
    12 import org.openstreetmap.josm.actions.PasteTagsAction.TagPaster;
    13 import org.openstreetmap.josm.data.osm.NodeData;
    14 import org.openstreetmap.josm.data.osm.PrimitiveData;
    15 import org.openstreetmap.josm.data.osm.RelationData;
    16 import org.openstreetmap.josm.data.osm.WayData;
    17 
    18 /**
    19  * Unit tests for class {@link PasteTagsAction}.
    20  */
    21 public class PasteTagsActionTest {
    22 
    23     /**
    24      * Setup test.
    25      */
    26     @BeforeClass
    27     public static void setUpBeforeClass() {
    28         JOSMFixture.createUnitTestFixture().init();
    29     }
    30 
    31     private static boolean isHeterogeneousSource(PrimitiveData... t) {
    32         return new TagPaster(Arrays.asList(t), null).isHeterogeneousSource();
    33     }
    34 
    35     /**
    36      * Unit test of {@link TagPaster#isHeterogeneousSource}.
    37      */
    38     @Test
    39     public void testTagPasterIsHeterogeneousSource() {
    40         // 0 item
    41         assertFalse(isHeterogeneousSource());
    42         // 1 item
    43         assertFalse(isHeterogeneousSource(new NodeData()));
    44         assertFalse(isHeterogeneousSource(new WayData()));
    45         assertFalse(isHeterogeneousSource(new RelationData()));
    46         // 2 items of same type
    47         assertFalse(isHeterogeneousSource(new NodeData(), new NodeData()));
    48         assertFalse(isHeterogeneousSource(new WayData(), new WayData()));
    49         assertFalse(isHeterogeneousSource(new RelationData(), new RelationData()));
    50         // 2 items of different type
    51         assertTrue(isHeterogeneousSource(new NodeData(), new WayData()));
    52         assertTrue(isHeterogeneousSource(new NodeData(), new RelationData()));
    53         assertTrue(isHeterogeneousSource(new WayData(), new RelationData()));
    54     }
    55 }
  • new file test/unit/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTagTransferDataTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTagTransferDataTest.java b/test/unit/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTagTransferDataTest.java
    new file mode 100644
    index 0000000..8f0e10b
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.data;
     3
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertFalse;
     6import static org.junit.Assert.assertTrue;
     7
     8import java.util.Arrays;
     9import java.util.Map;
     10
     11import org.junit.Rule;
     12import org.junit.Test;
     13import org.openstreetmap.josm.data.osm.Node;
     14import org.openstreetmap.josm.data.osm.NodeData;
     15import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     16import org.openstreetmap.josm.data.osm.PrimitiveData;
     17import org.openstreetmap.josm.data.osm.RelationData;
     18import org.openstreetmap.josm.data.osm.TagCollection;
     19import org.openstreetmap.josm.data.osm.WayData;
     20import org.openstreetmap.josm.testutils.JOSMTestRules;
     21
     22import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     23
     24/**
     25 * Test {@link PrimitiveTagTransferData}
     26 * @author Michael Zangl
     27 * @since xxx
     28 */
     29public class PrimitiveTagTransferDataTest {
     30    /**
     31     * Prefs only required because of the dependencies of OSM primitives.
     32     */
     33    @Rule
     34    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     35    public JOSMTestRules test = new JOSMTestRules().preferences();
     36
     37
     38    private static boolean isHeterogeneousSource(PrimitiveData... t) {
     39        return new PrimitiveTagTransferData(Arrays.asList(t)).isHeterogeneousSource();
     40    }
     41
     42    /**
     43     * Test method for {@link org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData#PrimitiveTagTransferData(org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData)}.
     44     */
     45    @Test
     46    public void testPrimitiveTagTransferDataPrimitiveTransferData() {
     47        PrimitiveTagTransferData data = new PrimitiveTagTransferData(PrimitiveTransferData.getData(Arrays.asList(new Node(), new Node())));
     48        assertEquals(2, data.getSourcePrimitiveCount(OsmPrimitiveType.NODE));
     49        assertEquals(0, data.getSourcePrimitiveCount(OsmPrimitiveType.WAY));
     50        assertEquals(0, data.getSourcePrimitiveCount(OsmPrimitiveType.RELATION));
     51    }
     52
     53    /**
     54     * Test method for {@link org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData#isHeterogeneousSource()}.
     55     */
     56    @Test
     57    public void testIsHeterogeneousSource() {
     58        // 0 item
     59        assertFalse(isHeterogeneousSource());
     60        // 1 item
     61        assertFalse(isHeterogeneousSource(new NodeData()));
     62        assertFalse(isHeterogeneousSource(new WayData()));
     63        assertFalse(isHeterogeneousSource(new RelationData()));
     64        // 2 items of same type
     65        assertFalse(isHeterogeneousSource(new NodeData(), new NodeData()));
     66        assertFalse(isHeterogeneousSource(new WayData(), new WayData()));
     67        assertFalse(isHeterogeneousSource(new RelationData(), new RelationData()));
     68        // 2 items of different type
     69        assertTrue(isHeterogeneousSource(new NodeData(), new WayData()));
     70        assertTrue(isHeterogeneousSource(new NodeData(), new RelationData()));
     71        assertTrue(isHeterogeneousSource(new WayData(), new RelationData()));
     72    }
     73
     74    /**
     75     * Test method for {@link org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData#getForPrimitives(org.openstreetmap.josm.data.osm.OsmPrimitiveType)}.
     76     */
     77    @Test
     78    public void testGetForPrimitives() {
     79        PrimitiveTagTransferData data = createTestData();
     80        TagCollection forNode = data.getForPrimitives(OsmPrimitiveType.NODE);
     81        assertEquals(1, forNode.getKeys().size());
     82        assertEquals(2, forNode.getValues("k").size());
     83        TagCollection forWay = data.getForPrimitives(OsmPrimitiveType.WAY);
     84        assertEquals(1, forWay.getKeys().size());
     85        assertEquals(1, forWay.getValues("x").size());
     86        TagCollection forRelation = data.getForPrimitives(OsmPrimitiveType.RELATION);
     87        assertEquals(0, forRelation.getKeys().size());
     88    }
     89
     90    private PrimitiveTagTransferData createTestData() {
     91        NodeData nd1 = new NodeData();
     92        nd1.put("k", "v");
     93        NodeData nd2 = new NodeData();
     94        nd2.put("k", "v2");
     95        WayData way = new WayData();
     96        way.put("x", "v");
     97        return new PrimitiveTagTransferData(Arrays.asList(nd1, nd2, way));
     98    }
     99
     100    /**
     101     * Test method for {@link org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData#getSourcePrimitiveCount(org.openstreetmap.josm.data.osm.OsmPrimitiveType)}.
     102     */
     103    @Test
     104    public void testGetSourcePrimitiveCount() {
     105        PrimitiveTagTransferData data = createTestData();
     106        assertEquals(2, data.getSourcePrimitiveCount(OsmPrimitiveType.NODE));
     107        assertEquals(1, data.getSourcePrimitiveCount(OsmPrimitiveType.WAY));
     108        assertEquals(0, data.getSourcePrimitiveCount(OsmPrimitiveType.RELATION));
     109    }
     110
     111    /**
     112     * Test method for {@link org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTagTransferData#getStatistics()}.
     113     */
     114    @Test
     115    public void testGetStatistics() {
     116        PrimitiveTagTransferData data = createTestData();
     117        Map<OsmPrimitiveType, Integer> stats = data.getStatistics();
     118        assertEquals(2, (int) stats.get(OsmPrimitiveType.NODE));
     119        assertEquals(1, (int) stats.get(OsmPrimitiveType.WAY));
     120        assertEquals(null, stats.get(OsmPrimitiveType.RELATION));
     121    }
     122
     123}