Ticket #7489: 7489_unified.diff

File 7489_unified.diff, 34.7 KB (added by joshdoe, 11 months ago)

git diff -U upstream/mirror origin/mergeundo > 7489_unified.diff

  • 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..b292609 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() && !getCoor().equals(((Node)other).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(