Ticket #7489: add_merge_command.patch

File add_merge_command.patch, 17.1 KB (added by joshdoe, 12 years ago)

adds MergeCommand and used with MergeSelectionAction

  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    372372     *
    373373     * @param numNewConflicts the number of detected conflicts
    374374     */
    375     protected void warnNumNewConflicts(int numNewConflicts) {
     375    public void warnNumNewConflicts(int numNewConflicts) {
    376376        if (numNewConflicts == 0) return;
    377377
    378378        String msg1 = trn(
  • src/org/openstreetmap/josm/actions/MergeSelectionAction.java

     
    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;
     
    3637        Layer targetLayer = askTargetLayer(targetLayers);
    3738        if (targetLayer == null)
    3839            return;
    39         MergeSourceBuildingVisitor builder = new MergeSourceBuildingVisitor(getEditLayer().data);
    40         ((OsmDataLayer)targetLayer).mergeFrom(builder.build());
     40       
     41        MergeCommand cmd = new MergeCommand((OsmDataLayer)targetLayer, getEditLayer().data, true);
     42        Main.main.undoRedo.add(cmd);
    4143    }
    4244
     45    @Override
    4346    public void actionPerformed(ActionEvent e) {
    4447        if (getEditLayer() == null || getEditLayer().data.getSelected().isEmpty())
    4548            return;
  • src/org/openstreetmap/josm/data/osm/DataSetMerger.java

     
    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
     
    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    /**
     
    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
     
    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            }
     
    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 {
     
    180193                if (referrers.isEmpty()) {
    181194                    target.setDeleted(true);
    182195                    target.mergeFrom(source);
     196                    changedObjectsMap.put(target, source.save());
    183197                    it.remove();
    184198                    flag = true;
    185199                } else {
     
    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                target.mergeFrom(source);
     229                changedObjectsMap.put(target, source.save());
    214230            }
    215231        }
    216232    }
     
    293309            // => merge source into target
    294310            //
    295311            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
     
    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            }
     
    330348            // target not modified. We can assume that source is the most recent version.
    331349            // clone it into target.
    332350            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
    337356            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            //
    342362            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.
     
    363385            // attributes should already be equal if we get here.
    364386            //
    365387            target.mergeFrom(source);
     388            changedObjectsMap.put(target, source.save());
    366389            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    367390        }
    368391        return true;
     
    423446        if (progressMonitor != null) {
    424447            progressMonitor.finishTask();
    425448        }
     449       
     450        undoState = UndoState.MERGED;
    426451    }
    427452
    428453    /**
     
    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    public void remerge() {
     497        if (undoState != UndoState.UNDONE) {
     498            throw new AssertionError();
     499        }
     500       
     501        targetDataSet.beginUpdate();
     502       
     503        for (OsmPrimitive osm : addedObjects) {
     504            targetDataSet.addPrimitive(osm);
     505        }
     506       
     507        for (Map.Entry<OsmPrimitive, PrimitiveData> e : changedObjectsMap.entrySet()) {
     508            // restore previous state and save current state for opposite undo action
     509            PrimitiveData old = e.getKey().save();
     510            e.getKey().load(e.getValue());
     511            e.setValue(old);
     512        }
     513       
     514        targetDataSet.endUpdate();
     515        undoState = UndoState.REDONE;
     516    }
     517   
     518    public Map<OsmPrimitive, PrimitiveData> getChangedObjectsMap() {
     519        return changedObjectsMap;
     520    }
     521   
     522    public Set<OsmPrimitive> getAddedObjects() {
     523        return addedObjects;
     524    }
    445525}
  • src/org/openstreetmap/josm/command/MergeCommand.java

     
     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        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
     58        CheckParameterUtil.ensureParameterNotNull(sourceDataSet, "sourceDataSet");
     59        this.targetLayer = targetLayer;
     60        this.targetDataSet = targetLayer.data;
     61
     62        // if selection present, create new dataset with just selected objects
     63        // and their "hull" (otherwise use entire dataset)
     64        if (selection != null && !selection.isEmpty()) {
     65            Collection<OsmPrimitive> origSelection = sourceDataSet.getSelected();
     66            sourceDataSet.setSelected(selection);
     67            MergeSourceBuildingVisitor builder = new MergeSourceBuildingVisitor(sourceDataSet);
     68            this.sourceDataSet = builder.build();
     69            sourceDataSet.setSelected(origSelection);
     70        }
     71
     72        addedConflicts = new HashSet<Conflict>();
     73        addedDataSources = new HashSet<DataSource>();
     74    }
     75
     76    @Override
     77    public boolean executeCommand() {
     78        PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging data"));
     79        monitor.setCancelable(false);
     80        if (merger == null) {
     81            //first time command is executed
     82            merger = new DataSetMerger(targetDataSet, sourceDataSet);
     83            try {
     84                merger.merge(monitor);
     85            } catch (DataIntegrityProblemException e) {
     86                JOptionPane.showMessageDialog(
     87                        Main.parent,
     88                        e.getHtmlMessage() != null ? e.getHtmlMessage() : e.getMessage(),
     89                        tr("Error"),
     90                        JOptionPane.ERROR_MESSAGE);
     91                return false;
     92
     93            }
     94
     95            Area a = targetDataSet.getDataSourceArea();
     96
     97            // copy the merged layer's data source info;
     98            // only add source rectangles if they are not contained in the
     99            // layer already.
     100            for (DataSource src : sourceDataSet.dataSources) {
     101                if (a == null || !a.contains(src.bounds.asRect())) {
     102                    targetDataSet.dataSources.add(src);
     103                    addedDataSources.add(src);
     104                }
     105            }
     106
     107            otherVersion = targetDataSet.getVersion();
     108            // copy the merged layer's API version, downgrade if required
     109            if (targetDataSet.getVersion() == null) {
     110                targetDataSet.setVersion(sourceDataSet.getVersion());
     111            } else if ("0.5".equals(targetDataSet.getVersion()) ^ "0.5".equals(sourceDataSet.getVersion())) {
     112                System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
     113                targetDataSet.setVersion("0.5");
     114            }
     115
     116
     117            // FIXME: allow conflicts to be retrieved rather than added to layer?
     118            if (targetLayer != null) {
     119                for (Conflict<?> c : merger.getConflicts()) {
     120                    if (!targetLayer.getConflicts().hasConflict(c)) {
     121                        targetLayer.getConflicts().add(c);
     122                        addedConflicts.add(c);
     123                    }
     124                }
     125            }
     126        } else {
     127            // command is being "redone"
     128           
     129            merger.remerge();
     130            targetDataSet.dataSources.addAll(addedDataSources);
     131
     132            String version = otherVersion;
     133            otherVersion = targetDataSet.getVersion();
     134            targetDataSet.setVersion(version);
     135
     136            for (Conflict c : addedConflicts) {
     137                targetLayer.getConflicts().add(c);
     138            }
     139        }
     140       
     141        if (addedConflicts.size() > 0) {
     142            targetLayer.warnNumNewConflicts(addedConflicts.size());
     143        }
     144       
     145        // repaint to make sure new data is displayed properly.
     146        Main.map.mapView.repaint();
     147       
     148        monitor.close();
     149       
     150        return true;
     151    }
     152
     153    @Override
     154    public void undoCommand() {
     155        merger.unmerge();
     156
     157        // restore data source area
     158        targetDataSet.dataSources.removeAll(addedDataSources);
     159
     160        String version = otherVersion;
     161        otherVersion = targetDataSet.getVersion();
     162        targetDataSet.setVersion(version);
     163
     164        for (Conflict c : addedConflicts) {
     165            targetLayer.getConflicts().remove(c);
     166        }
     167
     168        Main.map.mapView.repaint();
     169    }
     170
     171    @Override
     172    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
     173        throw new UnsupportedOperationException("Not supported yet.");
     174    }
     175
     176    @Override
     177    public String getDescriptionText() {
     178        return tr(marktr("Merge objects, {0} added, {1} modified"),
     179                merger.getAddedObjects().size(),
     180                merger.getChangedObjectsMap().size());
     181    }
     182
     183    @Override
     184    public Icon getDescriptionIcon() {
     185        return ImageProvider.get("dialogs", "mergedown");
     186    }
     187}
     188 No newline at end of file