Changeset 2198 in josm for trunk/src


Ignore:
Timestamp:
2009-09-27T16:29:21+02:00 (15 years ago)
Author:
Gubaer
Message:

fixed #3249: Resolve conflicts between invisible and deleted primitives automatically

Location:
trunk/src/org/openstreetmap/josm
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/MergeSelectionAction.java

    r2017 r2198  
    5151        setEnabled(!getEditLayer().data.getSelected().isEmpty());
    5252    }
    53 
    54 
    55 
    5653}
  • trunk/src/org/openstreetmap/josm/actions/UploadAction.java

    r2189 r2198  
    1111import java.util.Collection;
    1212import java.util.Date;
     13import java.util.HashSet;
    1314import java.util.LinkedList;
    1415import java.util.logging.Logger;
     
    2930import org.openstreetmap.josm.data.osm.OsmPrimitive;
    3031import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     32import org.openstreetmap.josm.gui.DefaultNameFormatter;
    3133import org.openstreetmap.josm.gui.ExceptionDialogUtil;
    3234import org.openstreetmap.josm.gui.PleaseWaitRunnable;
     
    3739import org.openstreetmap.josm.io.OsmApiException;
    3840import org.openstreetmap.josm.io.OsmApiInitializationException;
     41import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
    3942import org.openstreetmap.josm.io.OsmChangesetCloseException;
    4043import org.openstreetmap.josm.io.OsmServerWriter;
     44import org.openstreetmap.josm.io.OsmTransferException;
    4145import org.openstreetmap.josm.tools.DateUtils;
    4246import org.openstreetmap.josm.tools.Shortcut;
     
    350354     * @see UpdateSelectionAction#handlePrimitiveGoneException(long)
    351355     */
    352     protected void handleGoneForKnownPrimitive(OsmPrimitiveType primitiveType, String id) {
     356    protected void handleGoneForKnownPrimitive(OsmPrimitiveType primitiveType, long id) {
    353357        UpdateSelectionAction act = new UpdateSelectionAction();
    354         act.handlePrimitiveGoneException(Long.parseLong(id),primitiveType);
     358        act.handlePrimitiveGoneException(id,primitiveType);
    355359    }
    356360
     
    363367     * @param e the exception
    364368     */
    365     protected void handleGone(OsmApiException e) {
    366         String pattern = "The (\\S+) with the id (\\d+) has already been deleted";
    367         Pattern p = Pattern.compile(pattern);
    368         Matcher m = p.matcher(e.getErrorHeader());
    369         if (m.matches()) {
    370             handleGoneForKnownPrimitive(OsmPrimitiveType.from(m.group(1)), m.group(2));
     369    protected void handleGone(OsmApiPrimitiveGoneException e) {
     370        if (e.isKnownPrimitive()) {
     371            handleGoneForKnownPrimitive(e.getPrimitiveType(), e.getPrimitiveId());
    371372        } else {
    372             logger.warning(tr("Error header \"{0}\" does not match expected pattern \"{1}\"",e.getErrorHeader(), pattern));
    373373            ExceptionDialogUtil.explainGoneForUnknownPrimitive(e);
    374374        }
     
    391391        if (e instanceof OsmChangesetCloseException) {
    392392            ExceptionDialogUtil.explainOsmChangesetCloseException((OsmChangesetCloseException)e);
     393            return;
     394        }
     395        if (e instanceof OsmApiPrimitiveGoneException) {
     396            handleGone((OsmApiPrimitiveGoneException)e);
    393397            return;
    394398        }
     
    406410            else if (ex.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) {
    407411                ExceptionDialogUtil.explainPreconditionFailed(ex);
    408                 return;
    409             }
    410             // Tried to delete an already deleted primitive? Let the user
    411             // decide whether and how to resolve this conflict.
    412             //
    413             else if (ex.getResponseCode() == HttpURLConnection.HTTP_GONE) {
    414                 handleGone(ex);
    415412                return;
    416413            }
     
    489486    }
    490487
    491     public UploadDiffTask createUploadTask(OsmDataLayer layer, Collection<OsmPrimitive> toUpload, Changeset changeset, boolean closeChangesetAfterUpload) {
    492         return new UploadDiffTask(layer, toUpload, changeset, closeChangesetAfterUpload);
     488    public UploadPrimitivesTask createUploadTask(OsmDataLayer layer, Collection<OsmPrimitive> toUpload, Changeset changeset, boolean closeChangesetAfterUpload) {
     489        return new UploadPrimitivesTask(layer, toUpload, changeset, closeChangesetAfterUpload);
    493490    }
    494491
     
    497494     *
    498495     */
    499     public class UploadDiffTask extends  PleaseWaitRunnable {
     496    public class UploadPrimitivesTask extends  PleaseWaitRunnable {
    500497        private boolean uploadCancelled = false;
    501498        private Exception lastException = null;
     
    505502        private Changeset changeset;
    506503        private boolean closeChangesetAfterUpload;
     504        private HashSet<OsmPrimitive> processedPrimitives;
    507505
    508506        /**
     
    513511         * @param closeChangesetAfterUpload true, if the changeset is to be closed after uploading
    514512         */
    515         private UploadDiffTask(OsmDataLayer layer, Collection <OsmPrimitive> toUpload, Changeset changeset, boolean closeChangesetAfterUpload) {
     513        private UploadPrimitivesTask(OsmDataLayer layer, Collection <OsmPrimitive> toUpload, Changeset changeset, boolean closeChangesetAfterUpload) {
    516514            super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);
    517515            this.toUpload = toUpload;
     
    519517            this.changeset = changeset;
    520518            this.closeChangesetAfterUpload = closeChangesetAfterUpload;
     519            this.processedPrimitives = new HashSet<OsmPrimitive>();
     520        }
     521
     522        protected OsmPrimitive getPrimitive(OsmPrimitiveType type, long id) {
     523            for (OsmPrimitive p: toUpload) {
     524                if (OsmPrimitiveType.from(p).equals(type) && p.getId() == id)
     525                    return p;
     526            }
     527            return null;
     528        }
     529
     530        /**
     531         * Retries to recover the upload operation from an exception which was thrown because
     532         * an uploaded primitive was already deleted on the server.
     533         *
     534         * @param e the exception throw by the API
     535         * @param monitor a progress monitor
     536         * @throws OsmTransferException  thrown if we can't recover from the exception
     537         */
     538        protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException{
     539            if (!e.isKnownPrimitive()) throw e;
     540            OsmPrimitive p = getPrimitive(e.getPrimitiveType(), e.getPrimitiveId());
     541            if (p == null) throw e;
     542            if (p.isDeleted()) {
     543                // we tried to delete an already deleted primitive.
     544                //
     545                System.out.println(tr("Warning: primitive ''{0}'' is already deleted on the server. Skipping this primitive and retrying to upload.", p.getDisplayName(DefaultNameFormatter.getInstance())));
     546                processedPrimitives.addAll(writer.getProcessedPrimitives());
     547                processedPrimitives.add(p);
     548                toUpload.removeAll(processedPrimitives);
     549                return;
     550            }
     551            // exception was thrown because we tried to *update* an already deleted
     552            // primitive. We can't resolve this automatically. Re-throw exception,
     553            // a conflict is going to be created later.
     554            throw e;
    521555        }
    522556
     
    524558            writer = new OsmServerWriter();
    525559            try {
    526                 ProgressMonitor monitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
    527                 writer.uploadOsm(layer.data.version, toUpload, changeset,closeChangesetAfterUpload, monitor);
    528             } catch (Exception sxe) {
     560                //
     561                while(true) {
     562                    try {
     563                        ProgressMonitor monitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
     564                        writer.uploadOsm(layer.data.version, toUpload, changeset, monitor);
     565                        processedPrimitives.addAll(writer.getProcessedPrimitives());
     566                        // if we get here we've successfully uploaded the data. We
     567                        // can exit the loop.
     568                        //
     569                        break;
     570                    } catch(OsmApiPrimitiveGoneException e) {
     571                        // try to recover from the 410 Gone
     572                        recoverFromGoneOnServer(e, getProgressMonitor());
     573                    }
     574                }
     575                // if required close the changeset
     576                //
     577                if (closeChangesetAfterUpload) {
     578                    if (changeset != null && changeset.getId() > 0) {
     579                        OsmApi.getOsmApi().closeChangeset(changeset, progressMonitor.createSubTaskMonitor(0,false));
     580                    }
     581                }
     582            } catch (Exception e) {
    529583                if (uploadCancelled) {
    530                     System.out.println("Ignoring exception caught because upload is cancelled. Exception is: " + sxe.toString());
     584                    System.out.println("Ignoring exception caught because upload is cancelled. Exception is: " + e.toString());
    531585                    return;
    532586                }
    533                 lastException = sxe;
     587                lastException = e;
    534588            }
    535589        }
     
    539593                return;
    540594
    541             // we always clean the data, even in case of errors. It's possible the data was
     595            // we always clean up the data, even in case of errors. It's possible the data was
    542596            // partially uploaded
    543597            //
    544             layer.cleanupAfterUpload(writer.getProcessedPrimitives());
     598            layer.cleanupAfterUpload(processedPrimitives);
    545599            DataSet.fireSelectionChanged(layer.data.getSelected());
    546600            layer.fireDataChange();
    547601            if (lastException != null) {
    548602                handleFailedUpload(lastException);
    549             } else {
    550                 // run post upload action on the layer
    551                 //
    552                 layer.onPostUploadToServer();
    553                 // refresh changeset dialog with the updated changeset
    554                 //
    555                 UploadDialog.getUploadDialog().setOrUpdateChangeset(changeset);
    556             }
     603            }
     604            layer.onPostUploadToServer();
     605            UploadDialog.getUploadDialog().setOrUpdateChangeset(changeset);
    557606        }
    558607
     
    563612            }
    564613        }
    565 
    566         public boolean isSuccessful() {
    567             return !isCancelled() && !isFailed();
    568         }
    569 
    570         public boolean isCancelled() {
    571             return uploadCancelled;
    572         }
    573 
    574         public boolean isFailed() {
    575             return lastException != null;
    576         }
    577614    }
    578615}
  • trunk/src/org/openstreetmap/josm/actions/upload/RelationUploadOrderHook.java

    r2168 r2198  
    3333public class RelationUploadOrderHook implements UploadHook {
    3434
    35     /** the data to be analysed */
     35    /** the data to be analyzed */
    3636    private APIDataSet data;
    3737
  • trunk/src/org/openstreetmap/josm/command/ConflictResolveCommand.java

    r2163 r2198  
    1212
    1313/**
    14  * This is the common basse class for {@see Command}s which manipulate {@see Conflict}s in
     14 * This is the common base class for {@see Command}s which manipulate {@see Conflict}s in
    1515 * addition to {@see OsmPrimitive}s.
    1616 *
    1717 * A ConflictResolverCommand can remember a collection of conflicts it resolves. Upon undoing
    18  * it reconstitutes these conflicts.
     18 * it reconstitutes them.
    1919 *
    2020 */
     
    3030    }
    3131
     32    public ConflictResolveCommand(OsmDataLayer layer) {
     33        super(layer);
     34        resolvedConflicts = new ConflictCollection();
     35    }
     36
    3237    /**
    3338     * remembers a conflict in the internal list of remembered conflicts
     
    3540     * @param c the remembered conflict
    3641     */
    37     protected void rememberConflict(Conflict c) {
     42    protected void rememberConflict(Conflict<?> c) {
    3843        if (! resolvedConflicts.hasConflictForMy(c.getMy())) {
    3944            resolvedConflicts.add(c);
     
    4853    protected void reconstituteConflicts() {
    4954        OsmDataLayer editLayer = getLayer();
    50         for(Conflict c : resolvedConflicts) {
     55        for(Conflict<?> c : resolvedConflicts) {
    5156            if (!editLayer.getConflicts().hasConflictForMy(c.getMy())) {
    5257                editLayer.getConflicts().add(c);
     
    6065
    6166        if (! Main.map.mapView.hasLayer(getLayer())) {
    62             logger.warning(tr("Can't undo command ''{0}'' because layer ''{1}'' is not present any more",
     67            logger.warning(tr("Can''t undo command ''{0}'' because layer ''{1}'' is not present any more",
    6368                    this.toString(),
    6469                    getLayer().toString()
     
    7075        reconstituteConflicts();
    7176    }
    72 
    73 
    74 
    7577}
  • trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java

    r2163 r2198  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.util.ArrayList;
    76import java.util.Collection;
     7import java.util.Collections;
     8import java.util.HashSet;
    89import java.util.List;
     10import java.util.Set;
    911import java.util.logging.Logger;
    1012
     
    1517import org.openstreetmap.josm.Main;
    1618import org.openstreetmap.josm.data.conflict.ConflictCollection;
    17 import org.openstreetmap.josm.data.osm.DataSet;
     19import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
    1820import org.openstreetmap.josm.data.osm.Node;
    1921import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2022import org.openstreetmap.josm.data.osm.Relation;
    21 import org.openstreetmap.josm.data.osm.RelationMember;
    2223import org.openstreetmap.josm.data.osm.Way;
     24import org.openstreetmap.josm.gui.DefaultNameFormatter;
     25import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2326import org.openstreetmap.josm.tools.ImageProvider;
    2427
     
    3841    static private final Logger logger = Logger.getLogger(PurgePrimitivesCommand.class.getName());
    3942
    40     /**
    41      * Represents a pair of {@see OsmPrimitive} where the parent referrs to
    42      * the child, either because a {@see Way} includes a {@see Node} or
    43      * because a {@see Relation} refers to any other {@see OsmPrimitive}
    44      * via a relation member.
    45      *
    46      */
    47     static class OsmParentChildPair {
    48         private OsmPrimitive parent;
    49         private OsmPrimitive child;
    50 
    51 
    52         public OsmParentChildPair(OsmPrimitive parent, OsmPrimitive child) {
    53             this.parent = parent;
    54             this.child = child;
    55         }
    56 
    57         public OsmPrimitive getParent() {
    58             return parent;
    59         }
    60 
    61         public OsmPrimitive getChild() {
    62             return child;
    63         }
    64 
    65         @Override
    66         public int hashCode() {
    67             final int prime = 31;
    68             int result = 1;
    69             result = prime * result + ((child == null) ? 0 : child.hashCode());
    70             result = prime * result + ((parent == null) ? 0 : parent.hashCode());
    71             return result;
    72         }
    73 
    74         @Override
    75         public boolean equals(Object obj) {
    76             if (this == obj)
    77                 return true;
    78             if (obj == null)
    79                 return false;
    80             if (getClass() != obj.getClass())
    81                 return false;
    82             OsmParentChildPair other = (OsmParentChildPair) obj;
    83             if (child == null) {
    84                 if (other.child != null)
    85                     return false;
    86             } else if (child != other.child)
    87                 return false;
    88             if (parent == null) {
    89                 if (other.parent != null)
    90                     return false;
    91             } else if (parent != other.parent)
    92                 return false;
    93             return true;
    94         }
    95     }
    96 
    97     /**
    98      * creates a list of all {@see OsmParentChildPair}s for a given {@see OsmPrimitive}
    99      * as child and given set of parents. We don't use {@see CollectBackReferencesVisitor}
    100      * because it seems quite inefficient.
    101      *
    102      * @param parents  the set of potential parents
    103      * @param child the child
    104      * @return the list of {@see OsmParentChildPair}
    105      */
    106     protected List<OsmParentChildPair> getParentChildPairs(List<OsmPrimitive> parents, OsmPrimitive child) {
    107         ArrayList<OsmParentChildPair> pairs = new ArrayList<OsmParentChildPair>();
    108         for (OsmPrimitive parent : parents) {
    109             if (parent instanceof Way) {
    110                 Way w = (Way)parent;
    111                 for (OsmPrimitive node : w.getNodes()) {
    112                     if (node == child) {
    113                         OsmParentChildPair pair = new OsmParentChildPair(parent, node);
    114                         if (! pairs.contains(pair)) {
    115                             pairs.add(pair);
    116                         }
    117                     }
    118                 }
    119             } else if (parent instanceof Relation) {
    120                 Relation r = (Relation)parent;
    121                 for (RelationMember member : r.getMembers()) {
    122                     if (member.getMember() == child) {
    123                         OsmParentChildPair pair = new OsmParentChildPair(parent, member.getMember());
    124                         if (! pairs.contains(pair)) {
    125                             pairs.add(pair);
    126                         }
    127                     }
    128                 }
    129             }
    130         }
    131         return pairs;
    132     }
    133 
    134     /** the primitive to purge */
    135     private OsmPrimitive primitive;
     43    /** the primitives to purge */
     44    private Collection<OsmPrimitive> toPurge;
    13645
    13746    /** the set of primitives to purge as consequence of purging
    13847     * {@see #primitive}, including {@see #primitive}
    13948     */
    140     private ArrayList<OsmPrimitive> purgedPrimitives;
    141 
    142     /** the set of {@see OsmParentChildPair}. We keep a reference
    143      * to this set for the {@see #fillModifiedData(Collection, Collection, Collection)} operation
    144      */
    145     private ArrayList<OsmParentChildPair> pairs;
    146 
     49    private Set<OsmPrimitive> purgedPrimitives;
     50
     51    private Set<OsmPrimitive> origVersionsOfTouchedPrimitives;
     52
     53    /**
     54     * the data structure with child->parent references
     55     */
     56    private BackreferencedDataSet backreferenceDataSet;
     57
     58    protected void init(Collection<OsmPrimitive> toPurge) {
     59        this.toPurge = toPurge;
     60        this.purgedPrimitives = new HashSet<OsmPrimitive>();
     61        this.origVersionsOfTouchedPrimitives = new HashSet<OsmPrimitive>();
     62    }
    14763
    14864    /**
    14965     * constructor
    150      * @param node  the node to undelete
     66     * @param primitive the primitive to purge
     67     *
    15168     */
    15269    public PurgePrimitivesCommand(OsmPrimitive primitive) {
    153         this.primitive = primitive;
    154         purgedPrimitives = new ArrayList<OsmPrimitive>();
    155         pairs = new ArrayList<OsmParentChildPair>();
    156     }
    157 
    158     @Override
    159     public MutableTreeNode description() {
     70        init(Collections.singleton(primitive));
     71    }
     72
     73    /**
     74     * constructor
     75     * @param layer the OSM data layer
     76     * @param primitive the primitive to purge
     77     *
     78     */
     79    public PurgePrimitivesCommand(OsmDataLayer layer, OsmPrimitive primitive) {
     80        super(layer);
     81        init(Collections.singleton(primitive));
     82    }
     83
     84    /**
     85     * constructor
     86     * @param layer the OSM data layer
     87     * @param primitives the primitives to purge
     88     *
     89     */
     90    public PurgePrimitivesCommand(OsmDataLayer layer, Collection<OsmPrimitive> primitives) {
     91        super(layer);
     92        init(primitives);
     93    }
     94
     95    /**
     96     * Replies a collection with the purged primitives
     97     *
     98     * @return a collection with the purged primitives
     99     */
     100    public Collection<OsmPrimitive> getPurgedPrimitives() {
     101        return purgedPrimitives;
     102    }
     103
     104    protected MutableTreeNode getDescription(OsmPrimitive primitive) {
    160105        return new DefaultMutableTreeNode(
    161106                new JLabel(
    162                         tr("Purging 1 primitive"),
     107                        tr("Purged object ''{0}''", primitive.getDisplayName(DefaultNameFormatter.getInstance())),
    163108                        ImageProvider.get("data", "object"),
    164109                        JLabel.HORIZONTAL
     
    167112    }
    168113
    169     /**
    170      * Purges an {@see OsmPrimitive} <code>toPurge</code> from a {@see DataSet}.
     114    protected MutableTreeNode getDescription(Collection<OsmPrimitive> primitives) {
     115
     116        DefaultMutableTreeNode root = new DefaultMutableTreeNode(
     117                tr("Purged {0} objects", primitives.size())
     118        );
     119        for (OsmPrimitive p : primitives) {
     120            root.add(getDescription(p));
     121        }
     122        return root;
     123    }
     124
     125    @Override
     126    public MutableTreeNode description() {
     127        if (purgedPrimitives.size() == 1)
     128            return getDescription(purgedPrimitives.iterator().next());
     129        else
     130            return getDescription(purgedPrimitives);
     131    }
     132
     133    /**
     134     * Purges an {@see OsmPrimitive} <code>child</code> from a {@see DataSet}.
    171135     *
    172      * @param toPurge the primitive to purge
    173      * @param ds  the dataset to purge from
     136     * @param child the primitive to purge
    174137     * @param hive the hive of {@see OsmPrimitive}s we remember other {@see OsmPrimitive}
    175      * we have to purge because we purge <code>toPurge</code>.
     138     * we have to purge because we purge <code>child</code>.
    176139     *
    177140     */
    178     protected void purge(OsmPrimitive toPurge, DataSet ds, ArrayList<OsmPrimitive> hive) {
    179         ArrayList<OsmPrimitive> parents = new ArrayList<OsmPrimitive>();
    180         parents.addAll(getLayer().data.ways);
    181         parents.addAll(getLayer().data.relations);
    182         List<OsmParentChildPair> pairs = getParentChildPairs(parents, primitive);
    183         hive.remove(toPurge);
    184         for (OsmParentChildPair pair: pairs) {
    185             if (pair.getParent() instanceof Way) {
    186                 Way w = (Way)pair.getParent();
    187                 System.out.println(tr("removing reference from way {0}",w.getId()));
     141    protected void removeReferecesToPrimitive(OsmPrimitive child, Set<OsmPrimitive> hive) {
     142        hive.remove(child);
     143        for (OsmPrimitive parent: this.backreferenceDataSet.getParents(child)) {
     144            if (toPurge.contains(parent))
     145                // parent itself is to be purged. This method is going to be
     146                // invoked for parent later
     147                return;
     148            if (parent instanceof Way) {
     149                Way w = (Way)parent;
     150                if (!origVersionsOfTouchedPrimitives.contains(w)) {
     151                    origVersionsOfTouchedPrimitives.add(w);
     152                }
    188153                List<Node> wayNodes = w.getNodes();
    189                 wayNodes.remove(primitive);
     154                wayNodes.remove(child);
    190155                w.setNodes(wayNodes);
    191                 // if a way ends up with less than two node we
     156                // if a way ends up with less than two nodes we
    192157                // remember it on the "hive"
    193158                //
     
    195160                    System.out.println(tr("Warning: Purging way {0} because number of nodes dropped below 2. Current is {1}",
    196161                            w.getId(),w.getNodesCount()));
    197                     if (!hive.contains(w)) {
    198                         hive.add(w);
    199                     }
     162                    hive.add(w);
    200163                }
    201             } else if (pair.getParent() instanceof Relation) {
    202                 Relation r = (Relation)pair.getParent();
    203                 System.out.println(tr("removing reference from relation {0}",r.getId()));
    204                 r.removeMembersFor(primitive);
     164            } else if (parent instanceof Relation) {
     165                Relation r = (Relation)parent;
     166                if (!origVersionsOfTouchedPrimitives.contains(r)) {
     167                    origVersionsOfTouchedPrimitives.add(r);
     168                }
     169                System.out.println(tr("Removing reference from relation {0}",r.getId()));
     170                r.removeMembersFor(child);
     171            } else {
     172                // should not happen. parent can't be a node
    205173            }
    206174        }
     
    209177    @Override
    210178    public boolean executeCommand() {
    211         ArrayList<OsmPrimitive> hive = new ArrayList<OsmPrimitive>();
     179        if (backreferenceDataSet == null) {
     180            backreferenceDataSet = new BackreferencedDataSet(getLayer().data);
     181            backreferenceDataSet.build();
     182        }
     183        HashSet<OsmPrimitive> hive = new HashSet<OsmPrimitive>();
    212184
    213185        // iteratively purge the primitive and all primitives
    214         // which violate invariants after they loose a reference to
     186        // which violate invariants after they lose a reference to
    215187        // the primitive (i.e. ways which end up with less than two
    216188        // nodes)
    217         hive.add(primitive);
     189        hive.addAll(toPurge);
    218190        while(! hive.isEmpty()) {
    219             OsmPrimitive toPurge = hive.get(0);
    220             purge(toPurge, getLayer().data, hive);
    221             if (toPurge instanceof Node) {
    222                 getLayer().data.nodes.remove(toPurge);
    223             } else if (primitive instanceof Way) {
    224                 getLayer().data.ways.remove(toPurge);
    225             } else if (primitive instanceof Relation) {
    226                 getLayer().data.relations.remove(toPurge);
     191            OsmPrimitive p = hive.iterator().next();
     192            removeReferecesToPrimitive(p, hive);
     193            getLayer().data.removePrimitive(p);
     194            purgedPrimitives.add(p);
     195            ConflictCollection conflicts = getLayer().getConflicts();
     196            if (conflicts.hasConflictForMy(p)) {
     197                rememberConflict(conflicts.getConflictForMy(p));
     198                conflicts.remove(p);
    227199            }
    228             purgedPrimitives.add(toPurge);
    229             ConflictCollection conflicts = getLayer().getConflicts();
    230             if (conflicts.hasConflictForMy(toPurge)) {
    231                 rememberConflict(conflicts.getConflictForMy(toPurge));
    232                 conflicts.remove(toPurge);
    233             }
    234         }
     200        }
     201        // we don't need this any more
     202        backreferenceDataSet = null;
    235203        return super.executeCommand();
    236204    }
     
    239207    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
    240208            Collection<OsmPrimitive> added) {
    241         for (OsmParentChildPair pair : pairs) {
    242             modified.add(pair.getParent());
    243         }
    244         // we don't need pairs anymore
    245         pairs = null;
     209        modified.addAll(origVersionsOfTouchedPrimitives);
    246210    }
    247211
     
    249213    public void undoCommand() {
    250214        if (! Main.map.mapView.hasLayer(getLayer())) {
    251             logger.warning(tr("Can't undo command ''{0}'' because layer ''{1}'' is not present any more",
     215            logger.warning(tr("Can''t undo command ''{0}'' because layer ''{1}'' is not present any more",
    252216                    this.toString(),
    253217                    getLayer().toString()
     
    263227        }
    264228        reconstituteConflicts();
    265         // will restore the former references to the purged nodes
    266         //
     229
     230        // will restore the primitives referring to one
     231        // of the purged primitives
    267232        super.undoCommand();
    268233    }
     234
     235    /**
     236     * Use to inject a backreference data set used when the command
     237     * is executed.
     238     *
     239     * @param ds the backreference data set
     240     */
     241    public void setBackreferenceDataSet(BackreferencedDataSet ds) {
     242        this.backreferenceDataSet = ds;
     243    }
    269244}
  • trunk/src/org/openstreetmap/josm/data/conflict/Conflict.java

    r1911 r2198  
    66/**
    77 * Represents a conflict between two {@see OsmPrimitive}s. It is represented as
    8  * a pair if {@see OsmPrimitive} where one element of the pair has the role <em>my</em>
     8 * a pair of {@see OsmPrimitive}s where one element of the pair has the role <em>my</em>
    99 * and the other has the role <em>their</em>.
    1010 * <ul>
  • trunk/src/org/openstreetmap/josm/gui/io/UploadLayerTask.java

    r2181 r2198  
    55
    66import java.util.Collection;
     7import java.util.HashSet;
    78
     9import org.openstreetmap.josm.actions.upload.CyclicUploadDependencyException;
    810import org.openstreetmap.josm.data.APIDataSet;
    911import org.openstreetmap.josm.data.osm.Changeset;
    1012import org.openstreetmap.josm.data.osm.DataSet;
    1113import org.openstreetmap.josm.data.osm.OsmPrimitive;
     14import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     15import org.openstreetmap.josm.gui.DefaultNameFormatter;
    1216import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1317import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    1418import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    15 import org.openstreetmap.josm.io.ChangesetProcessingType;
     19import org.openstreetmap.josm.io.OsmApi;
     20import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
    1621import org.openstreetmap.josm.io.OsmServerWriter;
     22import org.openstreetmap.josm.io.OsmTransferException;
    1723
    1824/**
     
    3743    private Changeset changeset;
    3844    private boolean closeChangesetAfterUpload;
     45    private Collection<OsmPrimitive> toUpload;
     46    private HashSet<OsmPrimitive> processedPrimitives;
    3947
    4048    /**
     
    4250     * @param layer the layer. Must not be null.
    4351     * @param monitor  a progress monitor. If monitor is null, uses {@see NullProgressMonitor#INSTANCE}
    44      * @param changeset the changeset to be used if <code>changesetProcessingType</code> indicates that a new
    45      *   changeset is to be used
     52     * @param changeset the changeset to be used
    4653     * @param closeChangesetAfterUpload true, if the changeset should be closed after the upload
    4754     * @throws IllegalArgumentException thrown, if layer is null
     
    5764        this.changeset = changeset;
    5865        this.closeChangesetAfterUpload = closeChangesetAfterUpload;
     66        processedPrimitives = new HashSet<OsmPrimitive>();
     67    }
     68
     69    protected OsmPrimitive getPrimitive(OsmPrimitiveType type, long id) {
     70        for (OsmPrimitive p: toUpload) {
     71            if (OsmPrimitiveType.from(p).equals(type) && p.getId() == id)
     72                return p;
     73        }
     74        return null;
     75    }
     76
     77    /**
     78     * Retries to recover the upload operation from an exception which was thrown because
     79     * an uploaded primitive was already deleted on the server.
     80     *
     81     * @param e the exception throw by the API
     82     * @param monitor a progress monitor
     83     * @throws OsmTransferException  thrown if we can't recover from the exception
     84     */
     85    protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException{
     86        if (!e.isKnownPrimitive()) throw e;
     87        OsmPrimitive p = getPrimitive(e.getPrimitiveType(), e.getPrimitiveId());
     88        if (p == null) throw e;
     89        if (p.isDeleted()) {
     90            // we tried to delete an already deleted primitive.
     91            //
     92            System.out.println(tr("Warning: primitive ''{0}'' is already deleted on the server. Skipping this primitive and retrying to upload.", p.getDisplayName(DefaultNameFormatter.getInstance())));
     93            processedPrimitives.addAll(writer.getProcessedPrimitives());
     94            processedPrimitives.add(p);
     95            toUpload.removeAll(processedPrimitives);
     96            return;
     97        }
     98        // exception was thrown because we tried to *update* an already deleted
     99        // primitive. We can't resolve this automatically. Re-throw exception,
     100        // a conflict is going to be created later.
     101        throw e;
    59102    }
    60103
     
    62105    public void run() {
    63106        monitor.subTask(tr("Preparing primitives to upload ..."));
    64         Collection<OsmPrimitive> toUpload = new APIDataSet(layer.data).getPrimitives();
     107        APIDataSet ds = new APIDataSet(layer.data);
     108        try {
     109            ds.adjustRelationUploadOrder();
     110        } catch(CyclicUploadDependencyException e) {
     111            setLastException(e);
     112            return;
     113        }
     114        toUpload = ds.getPrimitives();
    65115        if (toUpload.isEmpty())
    66116            return;
    67117        writer = new OsmServerWriter();
    68118        try {
    69             ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
    70             if (isCancelled()) return;
    71             writer.uploadOsm(layer.data.version, toUpload, changeset, closeChangesetAfterUpload, m);
     119            while(true) {
     120                try {
     121                    ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
     122                    if (isCancelled()) return;
     123                    writer.uploadOsm(layer.data.version, toUpload, changeset, m);
     124                    processedPrimitives.addAll(writer.getProcessedPrimitives());
     125                    break;
     126                } catch(OsmApiPrimitiveGoneException e) {
     127                    recoverFromGoneOnServer(e, monitor);
     128                }
     129            }
     130            if (closeChangesetAfterUpload) {
     131                if (changeset != null && changeset.getId() > 0) {
     132                    OsmApi.getOsmApi().closeChangeset(changeset, monitor.createSubTaskMonitor(0, false));
     133                }
     134            }
    72135        } catch (Exception sxe) {
    73136            if (isCancelled()) {
     
    80143        if (isCancelled())
    81144            return;
    82         layer.cleanupAfterUpload(writer.getProcessedPrimitives());
     145        layer.cleanupAfterUpload(processedPrimitives);
    83146        DataSet.fireSelectionChanged(layer.data.getSelected());
    84147        layer.fireDataChange();
    85148        layer.onPostUploadToServer();
     149
     150        // don't process exceptions remembered with setLastException().
     151        // Caller is supposed to deal with them.
    86152    }
    87153
    88154    @Override
    89155    public void cancel() {
    90         // make sure the the softCancel operation is serialized with
    91         // blocks which can be interrupted.
    92156        setCancelled(true);
    93157        if (writer != null) {
  • trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    r2120 r2198  
    3838import org.openstreetmap.josm.Main;
    3939import org.openstreetmap.josm.actions.RenameLayerAction;
     40import org.openstreetmap.josm.command.PurgePrimitivesCommand;
    4041import org.openstreetmap.josm.data.conflict.Conflict;
    4142import org.openstreetmap.josm.data.conflict.ConflictCollection;
     
    4546import org.openstreetmap.josm.data.gpx.GpxTrack;
    4647import org.openstreetmap.josm.data.gpx.WayPoint;
     48import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
    4749import org.openstreetmap.josm.data.osm.DataSet;
    4850import org.openstreetmap.josm.data.osm.DataSource;
     
    285287        if (data.version == null) {
    286288            data.version = from.version;
    287         } else {
    288             if ("0.5".equals(data.version) ^ "0.5".equals(from.version)) {
    289                 System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
    290                 data.version = "0.5";
    291             }
     289        } else if ("0.5".equals(data.version) ^ "0.5".equals(from.version)) {
     290            System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
     291            data.version = "0.5";
     292        }
     293
     294        int numNewConflicts = 0;
     295        for (Conflict<?> c : visitor.getConflicts()) {
     296            if (!conflicts.hasConflict(c)) {
     297                numNewConflicts++;
     298                conflicts.add(c);
     299            }
     300        }
     301        PurgePrimitivesCommand cmd = buildPurgeCommand();
     302        if (cmd != null) {
     303            Main.main.undoRedo.add(cmd);
    292304        }
    293305        fireDataChange();
    294306        // repaint to make sure new data is displayed properly.
    295307        Main.map.mapView.repaint();
    296 
    297         int numNewConflicts = 0;
    298         for (Conflict c : visitor.getConflicts()) {
    299             if (!conflicts.hasConflict(c)) {
    300                 numNewConflicts++;
    301                 conflicts.add(c);
    302             }
    303         }
     308        warnNumNewConflicts(
     309                numNewConflicts,
     310                cmd == null ? 0 : cmd.getPurgedPrimitives().size()
     311        );
     312    }
     313
     314    /**
     315     * Warns the user about the number of detected conflicts
     316     *
     317     * @param numNewConflicts the number of detected conflicts
     318     * @param numPurgedPrimitives the number of automatically purged objects
     319     */
     320    protected void warnNumNewConflicts(int numNewConflicts, int numPurgedPrimitives) {
     321        if (numNewConflicts == 0 && numPurgedPrimitives == 0) return;
     322
     323        String msg1 = trn(
     324                "There was {0} conflict detected.",
     325                "There were {0} conflicts detected.",
     326                numNewConflicts,
     327                numNewConflicts
     328        );
     329        String msg2 = trn(
     330                "{0} object has been purged from the local dataset because it is deleted on the server.",
     331                "{0} objects have been purged from the local dataset because they are deleted on the server.",
     332                numPurgedPrimitives,
     333                numPurgedPrimitives
     334        );
     335        StringBuffer sb = new StringBuffer();
     336        sb.append("<html>").append(msg1);
     337        if (numPurgedPrimitives > 0) {
     338            sb.append("<br>").append(msg2);
     339        }
     340        sb.append("</html>");
    304341        if (numNewConflicts > 0) {
    305342            JOptionPane.showMessageDialog(
    306343                    Main.parent,
    307                     tr("There were {0} conflicts during import.", numNewConflicts),
    308                     tr("Warning"),
     344                    sb.toString(),
     345                    tr("Conflicts detected"),
    309346                    JOptionPane.WARNING_MESSAGE
    310347            );
    311348        }
     349    }
     350
     351    /**
     352     * Builds the purge command for primitives which can be purged automatically
     353     * from the local dataset because they've been deleted on the
     354     * server.
     355     *
     356     * @return the purge command. <code>null</code> if no primitives have to
     357     * be purged
     358     */
     359    protected PurgePrimitivesCommand buildPurgeCommand() {
     360        BackreferencedDataSet ds = new BackreferencedDataSet(data);
     361        ds.build();
     362        ArrayList<OsmPrimitive> toPurge = new ArrayList<OsmPrimitive>();
     363        conflictLoop: for (Conflict<?> c: conflicts) {
     364            if (c.getMy().isDeleted() && !c.getTheir().isVisible()) {
     365                // Local and server version of the primitive are deleted. We
     366                // can purge it from the local dataset.
     367                //
     368                toPurge.add(c.getMy());
     369            } else if (!c.getMy().isModified() && ! c.getTheir().isVisible()) {
     370                // We purge deleted *ways* and *relations* automatically if they are
     371                // deleted on the server and if they aren't modified in the local
     372                // dataset.
     373                //
     374                if (c.getMy() instanceof Way || c.getMy() instanceof Relation) {
     375                    toPurge.add(c.getMy());
     376                    continue conflictLoop;
     377                }
     378                // We only purge nodes if they aren't part of a modified way.
     379                // Otherwise the number of nodes of a modified way could drop
     380                // below 2 and we would lose the modified data when the way
     381                // gets purged.
     382                //
     383                for (OsmPrimitive parent: ds.getParents(c.getMy())) {
     384                    if (parent.isModified() && parent instanceof Way) {
     385                        continue conflictLoop;
     386                    }
     387                }
     388                toPurge.add(c.getMy());
     389            }
     390        }
     391        if (toPurge.isEmpty()) return null;
     392        PurgePrimitivesCommand cmd = new PurgePrimitivesCommand(this, toPurge);
     393        cmd.setBackreferenceDataSet(ds);
     394        return cmd;
    312395    }
    313396
     
    580663     */
    581664    public void onPostUploadToServer() {
    582         setRequiresUploadToServer(false);
     665        setRequiresUploadToServer(data.isModified());
    583666        // keep requiresSaveToDisk unchanged
    584667    }
  • trunk/src/org/openstreetmap/josm/io/OsmApi.java

    r2181 r2198  
    398398     *
    399399     * @param list the list of changed OSM Primitives
     400     * @param  monitor the progress monitor
    400401     * @return list of processed primitives
    401402     * @throws OsmTransferException if something is wrong
    402403     */
    403     public Collection<OsmPrimitive> uploadDiff(Collection<OsmPrimitive> list, ProgressMonitor progressMonitor) throws OsmTransferException {
     404    public Collection<OsmPrimitive> uploadDiff(Collection<OsmPrimitive> list, ProgressMonitor monitor) throws OsmTransferException {
    404405        try {
    405             progressMonitor.beginTask("", list.size() * 2);
     406            monitor.beginTask("", list.size() * 2);
    406407            if (changeset == null)
    407408                throw new OsmTransferException(tr("No changeset present for diff upload."));
    408409
    409             initialize(progressMonitor);
     410            initialize(monitor);
    410411            final ArrayList<OsmPrimitive> processed = new ArrayList<OsmPrimitive>();
    411412
    412413            CreateOsmChangeVisitor duv = new CreateOsmChangeVisitor(changeset, OsmApi.this);
    413414
    414             progressMonitor.subTask(tr("Preparing..."));
     415            monitor.subTask(tr("Preparing..."));
    415416            for (OsmPrimitive osm : list) {
    416417                osm.visit(duv);
    417                 progressMonitor.worked(1);
     418                monitor.worked(1);
    418419            }
    419             progressMonitor.indeterminateSubTask(tr("Uploading..."));
     420            monitor.indeterminateSubTask(tr("Uploading..."));
    420421
    421422            String diff = duv.getDocument();
    422             String diffresult = sendRequest("POST", "changeset/" + changeset.getId() + "/upload", diff,progressMonitor);
     423            String diffresult = sendRequest("POST", "changeset/" + changeset.getId() + "/upload", diff,monitor);
    423424            DiffResultReader.parseDiffResult(diffresult, list, processed, duv.getNewIdMap(),
    424                     progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
     425                    monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
    425426            return processed;
    426427        } catch(OsmTransferException e) {
     
    429430            throw new OsmTransferException(e);
    430431        } finally {
    431             progressMonitor.finishTask();
    432         }
    433     }
    434 
    435 
     432            monitor.finishTask();
     433        }
     434    }
    436435
    437436    private void sleepAndListen(int retry, ProgressMonitor monitor) throws OsmTransferCancelledException {
     
    548547                activeConnection.disconnect();
    549548
    550                 if (retCode != 200)
    551                     throw new OsmApiException(
    552                             retCode,
    553                             errorHeader == null? null : errorHeader.trim(),
    554                                     responseBody == null ? null : responseBody.toString().trim()
    555                     );
    556 
     549                errorHeader = errorHeader == null? null : errorHeader.trim();
     550                String errorBody = responseBody == null ? null : responseBody.toString().trim();
     551                switch(retCode) {
     552                    case HttpURLConnection.HTTP_OK:
     553                        break; // do nothing
     554                    case HttpURLConnection.HTTP_GONE:
     555                        throw new OsmApiPrimitiveGoneException(errorHeader, errorBody);
     556                    default:
     557                        throw new OsmApiException(retCode, errorHeader, errorBody);
     558
     559                }
    557560                return responseBody.toString();
    558561            } catch (UnknownHostException e) {
  • trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java

    r2181 r2198  
    1313import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1414import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     15import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    1516import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    1617
     
    111112     */
    112113    protected void uploadChangesAsDiffUpload(Collection<OsmPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException {
    113         // upload everything in one changeset
    114         //
    115114        try {
    116115            progressMonitor.beginTask(tr("Starting to upload in one request ..."));
     
    130129     * @param apiVersion version of the data set
    131130     * @param primitives list of objects to send
    132      */
    133     public void uploadOsm(String apiVersion, Collection<OsmPrimitive> primitives, Changeset changeset, boolean closeChangesetAfterUpload, ProgressMonitor progressMonitor) throws OsmTransferException {
     131     * @param changeset the changeset the data is uploaded to. Must not be null.
     132     * @param monitor the progress monitor. If null, assumes {@see NullProgressMonitor#INSTANCE}
     133     * @throws IllegalArgumentException thrown if changeset is null
     134     * @throws OsmTransferException thrown if something goes wrong
     135     */
     136    public void uploadOsm(String apiVersion, Collection<OsmPrimitive> primitives, Changeset changeset, ProgressMonitor monitor) throws OsmTransferException {
     137        if (changeset == null)
     138            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "changeset"));
    134139        processed = new LinkedList<OsmPrimitive>();
    135         progressMonitor.beginTask(tr("Uploading data ..."));
    136         api.initialize(progressMonitor);
     140        monitor = monitor == null ? NullProgressMonitor.INSTANCE : monitor;
     141        monitor.beginTask(tr("Uploading data ..."));
    137142        try {
     143            api.initialize(monitor);
    138144            // check whether we can use diff upload
    139145            //
    140             boolean casUseDiffUploads = api.hasSupportForDiffUploads();
     146            boolean canUseDiffUpload = api.hasSupportForDiffUploads();
    141147            if (apiVersion == null) {
    142148                System.out.println(tr("WARNING: no API version defined for data to upload. Falling back to version 0.6"));
     
    144150            }
    145151            boolean useDiffUpload = Main.pref.getBoolean("osm-server.atomic-upload", apiVersion.compareTo("0.6")>=0);
    146             if (useDiffUpload && ! casUseDiffUploads) {
     152            if (useDiffUpload && ! canUseDiffUpload) {
    147153                System.out.println(tr("WARNING: preference ''{0}'' or API version ''{1}'' of dataset requires to use diff uploads, but API is not able to handle them. Ignoring diff upload.", "osm-server.atomic-upload", apiVersion));
    148154                useDiffUpload = false;
    149155            }
    150             if (changeset == null) {
    151                 changeset = new Changeset();
    152             }
    153156            if (changeset.getId() == 0) {
    154                 api.openChangeset(changeset,progressMonitor.createSubTaskMonitor(0, false));
     157                api.openChangeset(changeset,monitor.createSubTaskMonitor(0, false));
    155158            } else {
    156                 api.updateChangeset(changeset,progressMonitor.createSubTaskMonitor(0, false));
     159                api.updateChangeset(changeset,monitor.createSubTaskMonitor(0, false));
    157160            }
    158161            api.setChangeset(changeset);
    159162            if (useDiffUpload) {
    160                 uploadChangesAsDiffUpload(primitives,progressMonitor.createSubTaskMonitor(0,false));
     163                uploadChangesAsDiffUpload(primitives,monitor.createSubTaskMonitor(0,false));
    161164            } else {
    162                 uploadChangesIndividually(primitives,progressMonitor.createSubTaskMonitor(0,false));
     165                uploadChangesIndividually(primitives,monitor.createSubTaskMonitor(0,false));
    163166            }
    164167        } catch(OsmTransferException e) {
     
    167170            throw new OsmTransferException(e);
    168171        } finally {
    169             try {
    170                 if (closeChangesetAfterUpload && api.getChangeset() != null && api.getChangeset().getId() > 0) {
    171                     api.closeChangeset(changeset,progressMonitor.createSubTaskMonitor(0, false));
    172                     api.setChangeset(null);
    173                 }
    174             } catch (Exception ee) {
    175                 OsmChangesetCloseException closeException = new OsmChangesetCloseException(ee);
    176                 closeException.setChangeset(api.getChangeset());
    177                 throw closeException;
    178             } finally {
    179                 progressMonitor.finishTask();
    180             }
     172            monitor.finishTask();
     173            api.setChangeset(null);
    181174        }
    182175    }
Note: See TracChangeset for help on using the changeset viewer.