Ticket #7489: 7489_unified2.diff

File 7489_unified2.diff, 34.7 KB (added by joshdoe, 12 years ago)

fix NPE when coords are null

  • src/org/openstreetmap/josm/actions/MergeSelectionAction.java

    diff --git a/src/org/openstreetmap/josm/actions/MergeSelectionAction.java b/src/org/openstreetmap/josm/actions/MergeSelectionAction.java
    index 4c63a0b..c5c4888 100644
    a b import java.awt.event.ActionEvent;  
    88import java.awt.event.KeyEvent;
    99import java.util.Collection;
    1010import java.util.List;
     11import org.openstreetmap.josm.Main;
     12import org.openstreetmap.josm.command.MergeCommand;
    1113
    1214import org.openstreetmap.josm.data.osm.DataSet;
    1315import org.openstreetmap.josm.data.osm.OsmPrimitive;
    14 import org.openstreetmap.josm.data.osm.visitor.MergeSourceBuildingVisitor;
    1516import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    1617import org.openstreetmap.josm.gui.layer.Layer;
    1718import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    public class MergeSelectionAction extends AbstractMergeAction {  
    4445                return;
    4546            }
    4647        }
    47         MergeSourceBuildingVisitor builder = new MergeSourceBuildingVisitor(getEditLayer().data);
    48         ((OsmDataLayer)targetLayer).mergeFrom(builder.build());
     48       
     49        MergeCommand cmd = new MergeCommand((OsmDataLayer)targetLayer, getEditLayer().data, true);
     50        Main.main.undoRedo.add(cmd);
    4951    }
    5052
     53    @Override
    5154    public void actionPerformed(ActionEvent e) {
    5255        if (getEditLayer() == null || getEditLayer().data.getSelected().isEmpty())
    5356            return;
  • src/org/openstreetmap/josm/actions/UpdateSelectionAction.java

    diff --git a/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java b/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
    index 09156c7..19b5381 100644
    a b import java.util.Collections;  
    1313import javax.swing.JOptionPane;
    1414
    1515import org.openstreetmap.josm.Main;
     16import org.openstreetmap.josm.command.DownloadOsmCommand;
    1617import org.openstreetmap.josm.data.osm.DataSet;
    1718import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1819import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    public class UpdateSelectionAction extends JosmAction {  
    3940        reader.append(getCurrentDataSet(),id, type);
    4041        try {
    4142            DataSet ds = reader.parseOsm(NullProgressMonitor.INSTANCE);
    42             Main.map.mapView.getEditLayer().mergeFrom(ds);
     43            Main.main.undoRedo.add(new DownloadOsmCommand(tr("Update selected primitives"), Main.map.mapView.getEditLayer(), ds));
    4344        } catch(Exception e) {
    4445            ExceptionDialogUtil.explainException(e);
    4546        }
  • src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java

    diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java
    index 991b381..17879aa 100644
    a b import java.util.regex.Matcher;  
    1010import java.util.regex.Pattern;
    1111
    1212import org.openstreetmap.josm.Main;
     13import org.openstreetmap.josm.actions.AutoScaleAction;
     14import org.openstreetmap.josm.command.DownloadOsmCommand;
    1315import org.openstreetmap.josm.data.Bounds;
    1416import org.openstreetmap.josm.data.coor.LatLon;
    1517import org.openstreetmap.josm.data.osm.DataSet;
    public class DownloadOsmTask extends AbstractDownloadTask {  
    207209                if (targetLayer == null) {
    208210                    targetLayer = getFirstDataLayer();
    209211                }
    210                 targetLayer.mergeFrom(dataSet);
    211                 computeBboxAndCenterScale();
    212                 targetLayer.onPostDownloadFromServer();
     212                Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download primitives from bounding box"), targetLayer, dataSet));
     213                AutoScaleAction.zoomTo(dataSet.allPrimitives());
    213214            }
    214215        }
    215216       
  • new file src/org/openstreetmap/josm/command/DownloadOsmCommand.java

    diff --git a/src/org/openstreetmap/josm/command/DownloadOsmCommand.java b/src/org/openstreetmap/josm/command/DownloadOsmCommand.java
    new file mode 100644
    index 0000000..7836f70
    - +  
     1// License: GPL. Copyright 2012 by Josh Doe and others
     2package org.openstreetmap.josm.command;
     3
     4import java.util.ArrayList;
     5import java.util.Collection;
     6import javax.swing.Icon;
     7import org.openstreetmap.josm.data.osm.DataSet;
     8import org.openstreetmap.josm.data.osm.OsmPrimitive;
     9import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     10import static org.openstreetmap.josm.tools.I18n.marktr;
     11import static org.openstreetmap.josm.tools.I18n.tr;
     12import org.openstreetmap.josm.tools.ImageProvider;
     13
     14/**
     15 * A command that merges a downloaded dataset to a layer.
     16 *
     17 * @author joshdoe
     18 */
     19public class DownloadOsmCommand extends Command {
     20    private OsmDataLayer targetLayer;
     21    private DataSet sourceDataSet;
     22    private MergeCommand mergeCommand;
     23    private boolean requiresSaveToFile;
     24    private boolean requiresUploadToServer;
     25    private String commandSummary;
     26
     27    public DownloadOsmCommand(String commandSummary, OsmDataLayer targetLayer, DataSet sourceDataSet) {
     28        super(targetLayer);
     29        this.commandSummary = commandSummary;
     30        this.targetLayer = targetLayer;
     31        this.sourceDataSet = sourceDataSet;
     32        mergeCommand = new MergeCommand(targetLayer, sourceDataSet, false);
     33    }
     34
     35    @Override
     36    public boolean executeCommand() {
     37        if (!mergeCommand.executeCommand()) {
     38            return false;
     39        }
     40        requiresSaveToFile = targetLayer.requiresSaveToFile();
     41        requiresUploadToServer = targetLayer.requiresUploadToServer();
     42        targetLayer.setRequiresSaveToFile(true);
     43        targetLayer.setRequiresUploadToServer(sourceDataSet.isModified());
     44        return true;
     45    }
     46
     47    @Override
     48    public void undoCommand() {
     49        mergeCommand.undoCommand();
     50        boolean oldValue;
     51        oldValue = targetLayer.requiresSaveToFile();
     52        targetLayer.setRequiresSaveToFile(requiresSaveToFile);
     53        requiresSaveToFile = oldValue;
     54        oldValue = targetLayer.requiresUploadToServer();
     55        targetLayer.setRequiresUploadToServer(requiresUploadToServer);
     56        requiresUploadToServer = oldValue;
     57    }
     58
     59    @Override
     60    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
     61        throw new UnsupportedOperationException("Not supported yet.");
     62    }
     63
     64    @Override
     65    public String getDescriptionText() {
     66        return commandSummary;
     67    }
     68
     69    @Override
     70    public Icon getDescriptionIcon() {
     71        return ImageProvider.get("dialogs", "down");
     72    }
     73   
     74    @Override
     75    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
     76        return sourceDataSet.allPrimitives();
     77    }
     78   
     79    private class DownloadPseudoCommand extends PseudoCommand {
     80
     81        @Override
     82        public String getDescriptionText() {
     83            return tr(marktr("Download {0} nodes, {1} ways, {2} relations"),
     84                    sourceDataSet.getNodes().size(),
     85                    sourceDataSet.getWays().size(),
     86                    sourceDataSet.getRelations().size());
     87        }
     88
     89        @Override
     90        public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
     91            return sourceDataSet.allPrimitives();
     92        }
     93       
     94        @Override
     95        public Icon getDescriptionIcon() {
     96            return ImageProvider.get("dialogs", "down");
     97        }
     98       
     99    }
     100    @Override
     101    public Collection<PseudoCommand> getChildren() {
     102        ArrayList<PseudoCommand> children = new ArrayList<PseudoCommand>();
     103        children.add(new DownloadPseudoCommand());
     104        children.add(mergeCommand);
     105        return children;
     106    }
     107   
     108}
  • new file src/org/openstreetmap/josm/command/MergeCommand.java

    diff --git a/src/org/openstreetmap/josm/command/MergeCommand.java b/src/org/openstreetmap/josm/command/MergeCommand.java
    new file mode 100644
    index 0000000..c36ee77
    - +  
     1// License: GPL. Copyright 2012 by Josh Doe and others
     2package org.openstreetmap.josm.command;
     3
     4import java.awt.geom.Area;
     5import java.util.Collection;
     6import java.util.HashSet;
     7import javax.swing.Icon;
     8import javax.swing.JOptionPane;
     9import org.openstreetmap.josm.Main;
     10import org.openstreetmap.josm.data.conflict.Conflict;
     11import org.openstreetmap.josm.data.osm.*;
     12import org.openstreetmap.josm.data.osm.visitor.MergeSourceBuildingVisitor;
     13import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     14import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
     15import org.openstreetmap.josm.tools.CheckParameterUtil;
     16import static org.openstreetmap.josm.tools.I18n.marktr;
     17import static org.openstreetmap.josm.tools.I18n.tr;
     18import org.openstreetmap.josm.tools.ImageProvider;
     19
     20/**
     21 * A command that merges objects from one layer to another.
     22 *
     23 * @author joshdoe
     24 */
     25public class MergeCommand extends Command {
     26
     27    private DataSetMerger merger;
     28    private DataSet sourceDataSet;
     29    private DataSet targetDataSet;
     30    private OsmDataLayer targetLayer;
     31    private Collection<DataSource> addedDataSources;
     32    private String otherVersion;
     33    private Collection<Conflict> addedConflicts;
     34
     35    /**
     36     * Create command to merge all or only currently selected objects from
     37     * sourceDataSet to targetLayer.
     38     *
     39     * @param targetLayer
     40     * @param sourceDataSet
     41     * @param onlySelected true to only merge objects selected in the
     42     * sourceDataSet
     43     */
     44    public MergeCommand(OsmDataLayer targetLayer, DataSet sourceDataSet, boolean onlySelected) {
     45        this(targetLayer, sourceDataSet, onlySelected ? sourceDataSet.getSelected() : null);
     46    }
     47
     48    /**
     49     * Create command to merge the selection from the sourceDataSet to the
     50     * targetLayer.
     51     *
     52     * @param targetLayer
     53     * @param sourceDataSet
     54     * @param selection
     55     */
     56    public MergeCommand(OsmDataLayer targetLayer, DataSet sourceDataSet, Collection<OsmPrimitive> selection) {
     57        super(targetLayer);
     58        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
     59        CheckParameterUtil.ensureParameterNotNull(sourceDataSet, "sourceDataSet");
     60        this.targetLayer = targetLayer;
     61        this.targetDataSet = targetLayer.data;
     62
     63        // if selection present, create new dataset with just selected objects
     64        // and their "hull" (otherwise use entire dataset)
     65        if (selection != null && !selection.isEmpty()) {
     66            Collection<OsmPrimitive> origSelection = sourceDataSet.getSelected();
     67            sourceDataSet.setSelected(selection);
     68            MergeSourceBuildingVisitor builder = new MergeSourceBuildingVisitor(sourceDataSet);
     69            this.sourceDataSet = builder.build();
     70            sourceDataSet.setSelected(origSelection);
     71        } else {
     72            this.sourceDataSet = sourceDataSet;
     73        }
     74       
     75
     76        addedConflicts = new HashSet<Conflict>();
     77        addedDataSources = new HashSet<DataSource>();
     78    }
     79
     80    @Override
     81    public boolean executeCommand() {
     82        PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging data"));
     83        monitor.setCancelable(false);
     84        if (merger == null) {
     85            //first time command is executed
     86            merger = new DataSetMerger(targetDataSet, sourceDataSet);
     87            try {
     88                merger.merge(monitor);
     89            } catch (DataIntegrityProblemException e) {
     90                JOptionPane.showMessageDialog(
     91                        Main.parent,
     92                        e.getHtmlMessage() != null ? e.getHtmlMessage() : e.getMessage(),
     93                        tr("Error"),
     94                        JOptionPane.ERROR_MESSAGE);
     95                return false;
     96
     97            }
     98
     99            Area a = targetDataSet.getDataSourceArea();
     100
     101            // copy the merged layer's data source info;
     102            // only add source rectangles if they are not contained in the
     103            // layer already.
     104            for (DataSource src : sourceDataSet.dataSources) {
     105                if (a == null || !a.contains(src.bounds.asRect())) {
     106                    targetDataSet.dataSources.add(src);
     107                    addedDataSources.add(src);
     108                }
     109            }
     110
     111            otherVersion = targetDataSet.getVersion();
     112            // copy the merged layer's API version, downgrade if required
     113            if (targetDataSet.getVersion() == null) {
     114                targetDataSet.setVersion(sourceDataSet.getVersion());
     115            } else if ("0.5".equals(targetDataSet.getVersion()) ^ "0.5".equals(sourceDataSet.getVersion())) {
     116                System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
     117                targetDataSet.setVersion("0.5");
     118            }
     119
     120
     121            // FIXME: allow conflicts to be retrieved rather than added to layer?
     122            if (targetLayer != null) {
     123                for (Conflict<?> c : merger.getConflicts()) {
     124                    if (!targetLayer.getConflicts().hasConflict(c)) {
     125                        targetLayer.getConflicts().add(c);
     126                        addedConflicts.add(c);
     127                    }
     128                }
     129            }
     130        } else {
     131            // command is being "redone"
     132           
     133            merger.remerge();
     134            targetDataSet.dataSources.addAll(addedDataSources);
     135
     136            String version = otherVersion;
     137            otherVersion = targetDataSet.getVersion();
     138            targetDataSet.setVersion(version);
     139
     140            for (Conflict c : addedConflicts) {
     141                targetLayer.getConflicts().add(c);
     142            }
     143        }
     144       
     145        if (addedConflicts.size() > 0) {
     146            targetLayer.warnNumNewConflicts(addedConflicts.size());
     147        }
     148       
     149        // repaint to make sure new data is displayed properly.
     150        Main.map.mapView.repaint();
     151       
     152        monitor.close();
     153       
     154        return true;
     155    }
     156
     157    @Override
     158    public void undoCommand() {
     159        merger.unmerge();
     160
     161        // restore data source area
     162        targetDataSet.dataSources.removeAll(addedDataSources);
     163
     164        String version = otherVersion;
     165        otherVersion = targetDataSet.getVersion();
     166        targetDataSet.setVersion(version);
     167
     168        for (Conflict c : addedConflicts) {
     169            targetLayer.getConflicts().remove(c);
     170        }
     171
     172        Main.map.mapView.repaint();
     173    }
     174
     175    @Override
     176    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
     177        throw new UnsupportedOperationException("Not supported yet.");
     178    }
     179
     180    @Override
     181    public String getDescriptionText() {
     182        return tr(marktr("Merged objects: {0} added, {1} modified"),
     183                merger.getAddedObjects().size(),
     184                merger.getChangedObjectsMap().size());
     185    }
     186
     187    @Override
     188    public Icon getDescriptionIcon() {
     189        return ImageProvider.get("dialogs", "mergedown");
     190    }
     191   
     192    @Override
     193    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
     194        HashSet<OsmPrimitive> prims = new HashSet<OsmPrimitive>();
     195        prims.addAll(merger.getAddedObjects());
     196        prims.addAll(merger.getChangedObjectsMap().keySet());
     197        return prims;
     198    }
     199}
     200 No newline at end of file
  • src/org/openstreetmap/josm/data/osm/DataSetMerger.java

    diff --git a/src/org/openstreetmap/josm/data/osm/DataSetMerger.java b/src/org/openstreetmap/josm/data/osm/DataSetMerger.java
    index e66bda8..7c3d276 100644
    a b public class DataSetMerger {  
    4343     */
    4444    private final Set<PrimitiveId> objectsWithChildrenToMerge;
    4545    private final Set<OsmPrimitive> objectsToDelete;
     46   
     47    private final Map<OsmPrimitive, PrimitiveData> changedObjectsMap;
     48    private final Set<OsmPrimitive> addedObjects;
     49   
     50    private enum UndoState {
     51        INIT, MERGED, UNDONE, REDONE
     52    }
     53    private UndoState undoState;
    4654
    4755    /**
    4856     * constructor
    public class DataSetMerger {  
    6169        mergedMap = new HashMap<PrimitiveId, PrimitiveId>();
    6270        objectsWithChildrenToMerge = new HashSet<PrimitiveId>();
    6371        objectsToDelete = new HashSet<OsmPrimitive>();
     72        changedObjectsMap = new HashMap<OsmPrimitive, PrimitiveData>();
     73        addedObjects = new HashSet<OsmPrimitive>();
     74        undoState = UndoState.INIT;
    6475    }
    6576
    6677    /**
    public class DataSetMerger {  
    7788     * @param <P>  the type of the other primitive
    7889     * @param source  the other primitive
    7990     */
    80     protected void mergePrimitive(OsmPrimitive source, Collection<? extends OsmPrimitive> candidates) {
     91        protected void mergePrimitive(OsmPrimitive source, Collection<? extends OsmPrimitive> candidates) {
    8192        if (!source.isNew() ) {
    8293            // try to merge onto a matching primitive with the same
    8394            // defined id
    public class DataSetMerger {  
    107118                    target.setTimestamp(source.getTimestamp());
    108119                    target.setModified(source.isModified());
    109120                    objectsWithChildrenToMerge.add(source.getPrimitiveId());
     121                    changedObjectsMap.put(target, source.save());
    110122                    return;
    111123                }
    112124            }
    public class DataSetMerger {  
    126138        targetDataSet.addPrimitive(target);
    127139        mergedMap.put(source.getPrimitiveId(), target.getPrimitiveId());
    128140        objectsWithChildrenToMerge.add(source.getPrimitiveId());
     141        addedObjects.add(target);
    129142    }
    130143
    131144    protected OsmPrimitive getMergeTarget(OsmPrimitive mergeSource) throws IllegalStateException {
    public class DataSetMerger {  
    179192                List<OsmPrimitive> referrers = target.getReferrers();
    180193                if (referrers.isEmpty()) {
    181194                    target.setDeleted(true);
    182                     target.mergeFrom(source);
     195                    if (target.mergeFrom(source))
     196                        changedObjectsMap.put(target, source.save());
    183197                    it.remove();
    184198                    flag = true;
    185199                } else {
    public class DataSetMerger {  
    208222                    ((Relation) osm).setMembers(null);
    209223                }
    210224            }
    211             for (OsmPrimitive osm: objectsToDelete) {
    212                 osm.setDeleted(true);
    213                 osm.mergeFrom(sourceDataSet.getPrimitiveById(osm.getPrimitiveId()));
     225            for (OsmPrimitive target: objectsToDelete) {
     226                OsmPrimitive source = sourceDataSet.getPrimitiveById(target.getPrimitiveId());
     227                target.setDeleted(true);
     228                if (target.mergeFrom(source))
     229                    changedObjectsMap.put(target, source.save());
    214230            }
    215231        }
    216232    }
    public class DataSetMerger {  
    292308            // target is incomplete, source completes it
    293309            // => merge source into target
    294310            //
    295             target.mergeFrom(source);
     311            if (target.mergeFrom(source))
     312                changedObjectsMap.put(target, source.save());
    296313            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    297314        } else if (!target.isIncomplete() && source.isIncomplete()) {
    298315            // target is complete and source is incomplete
    public class DataSetMerger {  
    318335                if (targetDataSet.getPrimitiveById(referrer.getPrimitiveId()) == null) {
    319336                    conflicts.add(new Conflict<OsmPrimitive>(target, source, true));
    320337                    target.setDeleted(false);
     338                    changedObjectsMap.put(target, source.save());
    321339                    break;
    322340                }
    323341            }
    public class DataSetMerger {  
    329347        } else if (! target.isModified() && source.isModified()) {
    330348            // target not modified. We can assume that source is the most recent version.
    331349            // clone it into target.
    332             target.mergeFrom(source);
     350            if (target.mergeFrom(source))
     351                changedObjectsMap.put(target, source.save());
    333352            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    334353        } else if (! target.isModified() && !source.isModified() && target.getVersion() == source.getVersion()) {
    335354            // both not modified. Merge nevertheless.
    336355            // This helps when updating "empty" relations, see #4295
    337             target.mergeFrom(source);
     356            if (target.mergeFrom(source))
     357                changedObjectsMap.put(target, source.save());
    338358            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    339359        } else if (! target.isModified() && !source.isModified() && target.getVersion() < source.getVersion()) {
    340360            // my not modified but other is newer. clone other onto mine.
    341361            //
    342             target.mergeFrom(source);
     362            if (target.mergeFrom(source))
     363                changedObjectsMap.put(target, source.save());
    343364            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    344365        } else if (target.isModified() && ! source.isModified() && target.getVersion() == source.getVersion()) {
    345366            // target is same as source but target is modified
    346367            // => keep target and reset modified flag if target and source are semantically equal
    347368            if (target.hasEqualSemanticAttributes(source)) {
    348369                target.setModified(false);
     370                changedObjectsMap.put(target, source.save());
    349371            }
    350372        } else if (source.isDeleted() != target.isDeleted()) {
    351373            // target is modified and deleted state differs.
    public class DataSetMerger {  
    362384            // technical attributes like timestamp or user information. Semantic
    363385            // attributes should already be equal if we get here.
    364386            //
    365             target.mergeFrom(source);
     387            if (target.mergeFrom(source))
     388                changedObjectsMap.put(target, source.save());
    366389            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    367390        }
    368391        return true;
    public class DataSetMerger {  
    423446        if (progressMonitor != null) {
    424447            progressMonitor.finishTask();
    425448        }
     449       
     450        undoState = UndoState.MERGED;
    426451    }
    427452
    428453    /**
    public class DataSetMerger {  
    442467    public ConflictCollection getConflicts() {
    443468        return conflicts;
    444469    }
     470   
     471    /**
     472     * Undos the merge operation.
     473     */
     474    public void unmerge() {
     475        if (undoState != UndoState.MERGED && undoState != UndoState.REDONE) {
     476            throw new AssertionError();
     477        }
     478       
     479        targetDataSet.beginUpdate();
     480       
     481        for (PrimitiveId osm : addedObjects) {
     482            targetDataSet.removePrimitive(osm);
     483        }
     484       
     485        for (Map.Entry<OsmPrimitive, PrimitiveData> e : changedObjectsMap.entrySet()) {
     486            // restore previous state and save current state for opposite undo action
     487            PrimitiveData old = e.getKey().save();
     488            e.getKey().load(e.getValue());
     489            e.setValue(old);
     490        }
     491       
     492        targetDataSet.endUpdate();
     493        undoState = UndoState.UNDONE;
     494    }
     495   
     496    /**
     497     * Re-merge objects with dataset. Identical results as {@see #merge()}, but performed
     498     * after {@see #unmerge()} has been called.
     499     */
     500    public void remerge() {
     501        if (undoState != UndoState.UNDONE) {
     502            throw new AssertionError();
     503        }
     504       
     505        targetDataSet.beginUpdate();
     506       
     507        // add back objects in order that they can be referenced by other objects
     508        for (OsmPrimitive osm : OsmPrimitive.getFilteredList(addedObjects, Node.class)) {
     509            targetDataSet.addPrimitive(osm);
     510        }
     511        for (OsmPrimitive osm : OsmPrimitive.getFilteredList(addedObjects, Way.class)) {
     512            targetDataSet.addPrimitive(osm);
     513        }
     514        for (OsmPrimitive osm : OsmPrimitive.getFilteredList(addedObjects, Relation.class)) {
     515            targetDataSet.addPrimitive(osm);
     516        }
     517       
     518        for (Map.Entry<OsmPrimitive, PrimitiveData> e : changedObjectsMap.entrySet()) {
     519            // restore previous state and save current state for opposite undo action
     520            PrimitiveData old = e.getKey().save();
     521            e.getKey().load(e.getValue());
     522            e.setValue(old);
     523        }
     524       
     525        targetDataSet.endUpdate();
     526        undoState = UndoState.REDONE;
     527    }
     528   
     529    public Map<OsmPrimitive, PrimitiveData> getChangedObjectsMap() {
     530        return changedObjectsMap;
     531    }
     532   
     533    public Set<OsmPrimitive> getAddedObjects() {
     534        return addedObjects;
     535    }
    445536}
  • src/org/openstreetmap/josm/data/osm/Node.java

    diff --git a/src/org/openstreetmap/josm/data/osm/Node.java b/src/org/openstreetmap/josm/data/osm/Node.java
    index cefa750..2030035 100644
    a b public final class Node extends OsmPrimitive implements INode {  
    206206     * have an assigend OSM id, the IDs have to be the same.
    207207     *
    208208     * @param other the other primitive. Must not be null.
     209     * @return true if the semantic or technical attributes were changed
    209210     * @throws IllegalArgumentException thrown if other is null.
    210211     * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
    211212     * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId()
    212213     */
    213214    @Override
    214     public void mergeFrom(OsmPrimitive other) {
     215    public boolean mergeFrom(OsmPrimitive other) {
     216        boolean changed;
    215217        boolean locked = writeLock();
    216218        try {
    217             super.mergeFrom(other);
    218             if (!other.isIncomplete()) {
     219            changed = super.mergeFrom(other);
     220            if (!other.isIncomplete() && !((Node)other).getCoor().equals((getCoor()))) {
    219221                setCoor(((Node)other).getCoor());
     222                changed = true;
    220223            }
    221224        } finally {
    222225            writeUnlock(locked);
    223226        }
     227        return changed;
    224228    }
    225229
    226230    @Override public void load(PrimitiveData data) {
  • src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    diff --git a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
    index 2f06c76..a2e07b7 100644
    a b abstract public class OsmPrimitive extends AbstractPrimitive implements Comparab  
    926926     * Merges the technical and semantical attributes from <code>other</code> onto this.
    927927     *
    928928     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
    929      * have an assigend OSM id, the IDs have to be the same.
     929     * have an assigned OSM id, the IDs have to be the same.
    930930     *
    931931     * @param other the other primitive. Must not be null.
     932     * @return true if the semantic or technical attributes were changed
    932933     * @throws IllegalArgumentException thrown if other is null.
    933934     * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
    934935     * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
    935936     */
    936     public void mergeFrom(OsmPrimitive other) {
     937    public boolean mergeFrom(OsmPrimitive other) {
     938        if (hasEqualSemanticAttributes(other) && hasEqualTechnicalAttributes(other))
     939            return false;
     940
    937941        boolean locked = writeLock();
    938942        try {
    939943            CheckParameterUtil.ensureParameterNotNull(other, "other");
    abstract public class OsmPrimitive extends AbstractPrimitive implements Comparab  
    952956        } finally {
    953957            writeUnlock(locked);
    954958        }
     959        return true;
    955960    }
    956961
    957962    /**
  • src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java b/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java
    index 0c1ad81..9e68c3a 100644
    a b import java.util.Set;  
    1313import javax.swing.SwingUtilities;
    1414
    1515import org.openstreetmap.josm.Main;
     16import org.openstreetmap.josm.command.DownloadOsmCommand;
    1617import org.openstreetmap.josm.data.osm.DataSet;
    1718import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1819import org.openstreetmap.josm.data.osm.Relation;
    public class DownloadRelationMemberTask extends PleaseWaitRunnable {  
    135136            SwingUtilities.invokeLater(
    136137                    new Runnable() {
    137138                        public void run() {
    138                             curLayer.mergeFrom(dataSet);
    139                             curLayer.onPostDownloadFromServer();
     139                            Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download relation members"), curLayer, dataSet));
    140140                        }
    141141                    }
    142142            );
  • src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java b/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java
    index ec140ec..d524a4f 100644
    a b import java.util.Collection;  
    99import javax.swing.SwingUtilities;
    1010
    1111import org.openstreetmap.josm.Main;
     12import org.openstreetmap.josm.command.DownloadOsmCommand;
    1213import org.openstreetmap.josm.data.osm.DataSet;
    1314import org.openstreetmap.josm.data.osm.DataSetMerger;
    1415import org.openstreetmap.josm.data.osm.Relation;
    public class DownloadRelationTask extends PleaseWaitRunnable {  
    9899            SwingUtilities.invokeAndWait(
    99100                    new Runnable() {
    100101                        public void run() {
    101                             layer.mergeFrom(allDownloads);
    102                             layer.onPostDownloadFromServer();
     102                            Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download relation(s)"), layer, allDownloads));
    103103                            Main.map.repaint();
    104104                        }
    105105                    }
  • src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java

    diff --git a/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java b/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java
    index c1433d6..03fd341 100644
    a b import java.util.List;  
    1010import java.util.Set;
    1111
    1212import javax.swing.SwingUtilities;
     13import org.openstreetmap.josm.Main;
    1314
    1415import org.openstreetmap.josm.actions.AutoScaleAction;
     16import org.openstreetmap.josm.command.DownloadOsmCommand;
    1517import org.openstreetmap.josm.data.osm.DataSet;
    1618import org.openstreetmap.josm.data.osm.DataSetMerger;
    1719import org.openstreetmap.josm.data.osm.Node;
    public class DownloadPrimitivesTask extends PleaseWaitRunnable {  
    8385        }
    8486        Runnable r = new Runnable() {
    8587            public void run() {
    86                 layer.mergeFrom(ds);
     88                Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download primitives"), layer, ds));
    8789                AutoScaleAction.zoomTo(ds.allPrimitives());
    88                 layer.onPostDownloadFromServer();
    8990            }
    9091        };
    9192
  • src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java

    diff --git a/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java b/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java
    index 59cb47c..59f0bd6 100644
    a b import java.util.Collection;  
    1010import java.util.Collections;
    1111
    1212import javax.swing.SwingUtilities;
     13import org.openstreetmap.josm.Main;
     14import org.openstreetmap.josm.command.DownloadOsmCommand;
    1315
    1416import org.openstreetmap.josm.data.osm.DataSet;
    1517import org.openstreetmap.josm.data.osm.DataSetMerger;
    public class UpdatePrimitivesTask extends PleaseWaitRunnable {  
    8183        }
    8284        Runnable r = new Runnable() {
    8385            public void run() {
    84                 layer.mergeFrom(ds);
    85                 layer.onPostDownloadFromServer();
     86                Main.main.undoRedo.add(new DownloadOsmCommand(tr("Update primitives"), layer, ds));
    8687            }
    8788        };
    8889
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
    index 5a0f76f..db43ba5 100644
    a b public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis  
    9999
    100100    public List<TestError> validationErrors = new ArrayList<TestError>();
    101101
    102     protected void setRequiresSaveToFile(boolean newValue) {
     102    public void setRequiresSaveToFile(boolean newValue) {
    103103        boolean oldValue = requiresSaveToFile;
    104104        requiresSaveToFile = newValue;
    105105        if (oldValue != newValue) {
    public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis  
    107107        }
    108108    }
    109109
    110     protected void setRequiresUploadToServer(boolean newValue) {
     110    public void setRequiresUploadToServer(boolean newValue) {
    111111        boolean oldValue = requiresUploadToServer;
    112112        requiresUploadToServer = newValue;
    113113        if (oldValue != newValue) {
    public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis  
    299299    }
    300300
    301301    @Override public void mergeFrom(final Layer from) {
     302        // TODO: make undo-able
    302303        final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging layers"));
    303304        monitor.setCancelable(false);
    304305        if (from instanceof OsmDataLayer && ((OsmDataLayer)from).isUploadDiscouraged()) {
    public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis  
    314315     *
    315316     * @param from  the source data set
    316317     */
     318    @Deprecated
    317319    public void mergeFrom(final DataSet from) {
    318320        mergeFrom(from, null);
    319321    }
    public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis  
    324326     *
    325327     * @param from  the source data set
    326328     */
     329    @Deprecated
    327330    public void mergeFrom(final DataSet from, ProgressMonitor progressMonitor) {
    328331        final DataSetMerger visitor = new DataSetMerger(data,from);
    329332        try {
    public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis  
    375378     *
    376379     * @param numNewConflicts the number of detected conflicts
    377380     */
    378     protected void warnNumNewConflicts(int numNewConflicts) {
     381    public void warnNumNewConflicts(int numNewConflicts) {
    379382        if (numNewConflicts == 0) return;
    380383
    381384        String msg1 = trn(