Changeset 2443 in josm for trunk/src/org


Ignore:
Timestamp:
2009-11-13T10:24:58+01:00 (15 years ago)
Author:
Gubaer
Message:

Fixes problems with merging

Location:
trunk/src/org/openstreetmap/josm
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/DataSetMerger.java

    r2433 r2443  
    2626
    2727    /** the target dataset for merging */
    28     private final DataSet myDataSet;
     28    private final DataSet targetDataSet;
    2929    /** the source dataset where primitives are merged from */
    30     private final DataSet theirDataSet;
     30    private final DataSet sourceDataSet;
    3131
    3232    /**
     
    3838     * to relation members) after the first phase of merging
    3939     */
    40     private Set<Long> fixReferences;
     40    private Set<Long> childrenToMerge;
     41
     42    private Set<OsmPrimitive> deletedObjectsToUnlink;
    4143
    4244    /**
     
    4547     * The visitor will merge <code>theirDataSet</code> onto <code>myDataSet</code>
    4648     *
    47      * @param myDataSet  dataset with my primitives. Must not be null.
    48      * @param theirDataSet dataset with their primitives. Ignored, if null.
     49     * @param targetDataSet  dataset with my primitives. Must not be null.
     50     * @param sourceDataSet dataset with their primitives. Ignored, if null.
    4951     * @throws IllegalArgumentException thrown if myDataSet is null
    5052     */
    51     public DataSetMerger(DataSet myDataSet, DataSet theirDataSet) throws IllegalArgumentException {
    52         if (myDataSet == null)
    53             throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null"));
    54         this.myDataSet = myDataSet;
    55         this.theirDataSet = theirDataSet;
     53    public DataSetMerger(DataSet targetDataSet, DataSet sourceDataSet) throws IllegalArgumentException {
     54        if (targetDataSet == null)
     55            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "targetDataSet"));
     56        this.targetDataSet = targetDataSet;
     57        this.sourceDataSet = sourceDataSet;
    5658        conflicts = new ConflictCollection();
    5759        mergedMap = new HashMap<Long, Long>();
    58         fixReferences = new HashSet<Long>();
     60        childrenToMerge = new HashSet<Long>();
     61        deletedObjectsToUnlink = new HashSet<OsmPrimitive>();
    5962    }
    6063
     
    7174     *
    7275     * @param <P>  the type of the other primitive
    73      * @param other  the other primitive
    74      */
    75     protected <P extends OsmPrimitive> void mergePrimitive(P other) {
    76         if (!other.isNew() ) {
     76     * @param source  the other primitive
     77     */
     78    protected <P extends OsmPrimitive> void mergePrimitive(P source) {
     79        if (!source.isNew() ) {
    7780            // try to merge onto a matching primitive with the same
    7881            // defined id
    7982            //
    80             if (mergeById(other))
     83            if (mergeById(source))
    8184                return;
    82             if (!other.isVisible())
     85            if (!source.isVisible())
    8386                // ignore it
    8487                return;
     
    8891            //
    8992            Collection<? extends OsmPrimitive> candidates = null;
    90             switch(other.getType()) {
    91             case NODE: candidates = myDataSet.getNodes(); break;
    92             case WAY: candidates  =myDataSet.getWays(); break;
    93             case RELATION: candidates = myDataSet.getRelations(); break;
    94             }
    95             for (OsmPrimitive my : candidates) {
    96                 if (!my.isNew()) {
     93            switch(source.getType()) {
     94            case NODE: candidates = targetDataSet.getNodes(); break;
     95            case WAY: candidates  =targetDataSet.getWays(); break;
     96            case RELATION: candidates = targetDataSet.getRelations(); break;
     97            }
     98            for (OsmPrimitive target : candidates) {
     99                if (!target.isNew()) {
    97100                    continue;
    98101                }
    99                 if (my.hasEqualSemanticAttributes(other)) {
    100                     mergedMap.put(other.getUniqueId(), my.getUniqueId());
    101                     if (my.isDeleted() != other.isDeleted()) {
     102                if (target.hasEqualSemanticAttributes(source)) {
     103                    mergedMap.put(source.getUniqueId(), target.getUniqueId());
     104                    if (target.isDeleted() != source.isDeleted()) {
    102105                        // differences in deleted state have to be merged manually
    103106                        //
    104                         conflicts.add(my, other);
     107                        conflicts.add(target, source);
    105108                    } else {
    106109                        // copy the technical attributes from other
    107110                        // version
    108                         my.setVisible(other.isVisible());
    109                         my.setUser(other.getUser());
    110                         my.setTimestamp(other.getTimestamp());
    111                         my.setModified(other.isModified());
    112                         fixReferences.add(other.getUniqueId());
     111                        target.setVisible(source.isVisible());
     112                        target.setUser(source.getUser());
     113                        target.setTimestamp(source.getTimestamp());
     114                        target.setModified(source.isModified());
     115                        childrenToMerge.add(source.getUniqueId());
    113116                    }
    114117                    return;
     
    118121
    119122        // If we get here we didn't find a suitable primitive in
    120         // my dataset. Create a clone and add it to my dataset.
     123        // the target dataset. Create a clone and add it to the target dataset.
    121124        //
    122         OsmPrimitive my = null;
    123         switch(other.getType()) {
    124         case NODE: my = other.isNew() ? new Node() : new Node(other.getId()); break;
    125         case WAY: my = other.isNew() ? new Way() : new Way(other.getId()); break;
    126         case RELATION: my = other.isNew() ? new Relation() : new Relation(other.getId()); break;
    127         }
    128         my.mergeFrom(other);
    129         myDataSet.addPrimitive(my);
    130         mergedMap.put(other.getUniqueId(), my.getUniqueId());
    131         fixReferences.add(other.getUniqueId());
     125        OsmPrimitive target = null;
     126        switch(source.getType()) {
     127        case NODE: target = source.isNew() ? new Node() : new Node(source.getId()); break;
     128        case WAY: target = source.isNew() ? new Way() : new Way(source.getId()); break;
     129        case RELATION: target = source.isNew() ? new Relation() : new Relation(source.getId()); break;
     130        }
     131        target.mergeFrom(source);
     132        targetDataSet.addPrimitive(target);
     133        mergedMap.put(source.getUniqueId(), target.getUniqueId());
     134        childrenToMerge.add(source.getUniqueId());
    132135    }
    133136
     
    136139        if (targetId == null)
    137140            throw new RuntimeException(tr("Missing merge target for way with id {0}", mergeSource.getUniqueId()));
    138         return myDataSet.getPrimitiveById(targetId, mergeSource.getType());
     141        return targetDataSet.getPrimitiveById(targetId, mergeSource.getType());
    139142    }
    140143
     
    156159     */
    157160    public void fixReferences() {
    158         for (Way w : theirDataSet.getWays()) {
    159             if (!conflicts.hasConflictForTheir(w) && fixReferences.contains(w.getUniqueId())) {
     161        for (Way w : sourceDataSet.getWays()) {
     162            if (!conflicts.hasConflictForTheir(w) && childrenToMerge.contains(w.getUniqueId())) {
    160163                mergeNodeList(w);
    161164                fixIncomplete(w);
    162165            }
    163166        }
    164         for (Relation r : theirDataSet.getRelations()) {
    165             if (!conflicts.hasConflictForTheir(r) && fixReferences.contains(r.getUniqueId())) {
     167        for (Relation r : sourceDataSet.getRelations()) {
     168            if (!conflicts.hasConflictForTheir(r) && childrenToMerge.contains(r.getUniqueId())) {
    166169                mergeRelationMembers(r);
    167170            }
    168171        }
    169     }
    170 
    171     private void mergeNodeList(Way other) {
    172         Way myWay = (Way)getMergeTarget(other);
    173         if (myWay == null)
    174             throw new RuntimeException(tr("Missing merge target for way with id {0}", other.getUniqueId()));
    175 
    176         List<Node> myNodes = new LinkedList<Node>();
    177         for (Node otherNode : other.getNodes()) {
    178             Node myNode = (Node)getMergeTarget(otherNode);
    179             if (myNode != null) {
    180                 if (!myNode.isDeleted()) {
    181                     myNodes.add(myNode);
     172        for (OsmPrimitive source: deletedObjectsToUnlink) {
     173            OsmPrimitive target = getMergeTarget(source);
     174            if (target == null)
     175                throw new RuntimeException(tr("Missing merge target for object with id {0}", source.getUniqueId()));
     176            targetDataSet.unlinkReferencesToPrimitive(target);
     177        }
     178    }
     179
     180    /**
     181     * Merges the node list of a source way onto its target way.
     182     *
     183     * @param source the source way
     184     * @throws IllegalStateException thrown if no target way can be found for the source way
     185     * @throws IllegalStateException thrown if there isn't a target node for one of the nodes in the source way
     186     *
     187     */
     188    private void mergeNodeList(Way source) throws IllegalStateException {
     189        Way target = (Way)getMergeTarget(source);
     190        if (target == null)
     191            throw new IllegalStateException(tr("Missing merge target for way with id {0}", source.getUniqueId()));
     192
     193        List<Node> newNodes = new LinkedList<Node>();
     194        for (Node sourceNode : source.getNodes()) {
     195            Node targetNode = (Node)getMergeTarget(sourceNode);
     196            if (targetNode != null) {
     197                if (!targetNode.isDeleted() && targetNode.isVisible()) {
     198                    newNodes.add(targetNode);
     199                } else {
     200                    target.setModified(true);
    182201                }
    183202            } else
    184                 throw new RuntimeException(tr("Missing merge target for node with id {0}", otherNode.getUniqueId()));
    185         }
    186 
    187         // check whether the node list has changed. If so, set the modified flag on the way
    188         //
    189         if (myWay.getNodes().size() != myNodes.size()) {
    190             myWay.setModified(true);
    191         } else {
    192             for (int i=0; i< myWay.getNodesCount();i++) {
    193                 Node n1 = myWay.getNode(i);
    194                 Node n2 = myNodes.get(i);
    195                 if (n1.isNew() ^ n2.isNew()) {
    196                     myWay.setModified(true);
    197                     break;
    198                 } else if (n1.isNew() && n1 != n2) {
    199                     myWay.setModified(true);
    200                     break;
    201                 } else if (! n1.isNew() && n1.getId() != n2.getId()) {
    202                     myWay.setModified(true);
    203                     break;
    204                 }
    205             }
    206         }
    207         myWay.setNodes(myNodes);
    208     }
    209 
    210     private void mergeRelationMembers(Relation other) {
    211         Relation myRelation = (Relation) getMergeTarget(other);
    212         if (myRelation == null)
    213             throw new RuntimeException(tr("Missing merge target for relation with id {0}", other.getUniqueId()));
     203                throw new IllegalStateException(tr("Missing merge target for node with id {0}", sourceNode.getUniqueId()));
     204        }
     205        target.setNodes(newNodes);
     206    }
     207
     208
     209    /**
     210     * Merges the relation members of a source relation onto the corresponding target relation.
     211     * @param source the source relation
     212     * @throws IllegalStateException thrown if there is no corresponding target relation
     213     * @throws IllegalStateException thrown if there isn't a corresponding target object for one of the relation
     214     * members in source
     215     */
     216    private void mergeRelationMembers(Relation source) throws IllegalStateException {
     217        Relation target = (Relation) getMergeTarget(source);
     218        if (target == null)
     219            throw new IllegalStateException(tr("Missing merge target for relation with id {0}", source.getUniqueId()));
    214220        LinkedList<RelationMember> newMembers = new LinkedList<RelationMember>();
    215         for (RelationMember otherMember : other.getMembers()) {
    216             OsmPrimitive mergedMember = getMergeTarget(otherMember.getMember());
    217             if (mergedMember == null)
    218                 throw new RuntimeException(tr("Missing merge target of type {0} with id {1}", mergedMember.getType(), mergedMember.getUniqueId()));
    219             if (! mergedMember.isDeleted()) {
    220                 RelationMember newMember = new RelationMember(otherMember.getRole(), mergedMember);
     221        for (RelationMember sourceMember : source.getMembers()) {
     222            OsmPrimitive targetMember = getMergeTarget(sourceMember.getMember());
     223            if (targetMember == null)
     224                throw new IllegalStateException(tr("Missing merge target of type {0} with id {1}", targetMember.getType(), targetMember.getUniqueId()));
     225            if (! targetMember.isDeleted() && targetMember.isVisible()) {
     226                RelationMember newMember = new RelationMember(sourceMember.getRole(), targetMember);
    221227                newMembers.add(newMember);
    222             }
    223         }
    224 
    225         // check whether the list of relation members has changed
    226         //
    227         if (other.getMembersCount() != newMembers.size()) {
    228             myRelation.setModified(true);
    229         } else {
    230             for (int i=0; i<other.getMembersCount();i++) {
    231                 RelationMember rm1 = other.getMember(i);
    232                 RelationMember rm2 = newMembers.get(i);
    233                 if (!rm1.getRole().equals(rm2.getRole())) {
    234                     myRelation.setModified(true);
    235                     break;
    236                 } else if (rm1.getMember().isNew() ^ rm2.getMember().isNew()) {
    237                     myRelation.setModified(true);
    238                     break;
    239                 } else if (rm1.getMember().isNew() && rm1.getMember() != rm2.getMember()) {
    240                     myRelation.setModified(true);
    241                     break;
    242                 } else if (! rm1.getMember().isNew() && rm1.getMember().getId() != rm2.getMember().getId()) {
    243                     myRelation.setModified(true);
    244                     break;
    245                 }
    246             }
    247         }
    248         myRelation.setMembers(newMembers);
    249     }
    250 
    251     /**
    252      * Tries to merge a primitive <code>other</code> into an existing primitive with the same id.
    253      *
    254      * @param other  the other primitive which is to be merged onto a primitive in my primitives
    255      * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise
    256      */
    257     private <P extends OsmPrimitive> boolean mergeById(P other) {
    258         OsmPrimitive my = myDataSet.getPrimitiveById(other.getId(), other.getType());
     228            } else {
     229                target.setModified(true);
     230            }
     231        }
     232        target.setMembers(newMembers);
     233    }
     234
     235    /**
     236     * Tries to merge a primitive <code>source</code> into an existing primitive with the same id.
     237     *
     238     * @param source  the other primitive which is to be merged onto a primitive in my primitives
     239     * @return true, if this method was able to merge <code>source</code> into a target object; false, otherwise
     240     */
     241    private boolean mergeById(OsmPrimitive source) {
     242        OsmPrimitive target = targetDataSet.getPrimitiveById(source.getId(), source.getType());
    259243        // merge other into an existing primitive with the same id, if possible
    260244        //
    261         if (my == null)
     245        if (target == null)
    262246            return false;
    263         mergedMap.put(other.getUniqueId(), my.getUniqueId());
    264         if (my.getVersion() > other.getVersion())
    265             // my.version > other.version => keep my version
     247        // found a corresponding target, remember it
     248        mergedMap.put(source.getUniqueId(), target.getUniqueId());
     249
     250        if (target.getVersion() > source.getVersion())
     251            // target.version > source.version => keep target version
    266252            return true;
    267         if (! my.isVisible() && other.isVisible()) {
     253        if (! target.isVisible() && source.isVisible()) {
    268254            // should not happen
    269255            //
    270             logger.warning(tr("My primitive with id {0} and version {1} is visible although "
    271                     + "their primitive with lower version {2} is not visible. "
    272                     + "Can't deal with this inconsistency. Keeping my primitive. ",
    273                     Long.toString(my.getId()),Long.toString(my.getVersion()), Long.toString(other.getVersion())
     256            logger.warning(tr("Target object with id {0} and version {1} is visible although "
     257                    + "source object with lower version {2} is not visible. "
     258                    + "Can''t deal with this inconsistency. Keeping target object. ",
     259                    Long.toString(target.getId()),Long.toString(target.getVersion()), Long.toString(source.getVersion())
    274260            ));
    275         } else if (my.isVisible() && ! other.isVisible()) {
     261        } else if (target.isVisible() && ! source.isVisible()) {
    276262            // this is always a conflict because the user has to decide whether
    277             // he wants to create a clone of its local primitive or whether he
    278             // wants to purge my from the local dataset. He can't keep it unchanged
     263            // he wants to create a clone of its target primitive or whether he
     264            // wants to purge the target from the local dataset. He can't keep it unchanged
    279265            // because it was deleted on the server.
    280266            //
    281             conflicts.add(my,other);
    282         } else if (my.incomplete && !other.incomplete) {
    283             // my is incomplete, other completes it
    284             // => merge other onto my
    285             //
    286             my.mergeFrom(other);
    287             fixReferences.add(other.getUniqueId());
    288         } else if (!my.incomplete && other.incomplete) {
    289             // my is complete and the other is incomplete
    290             // => keep mine, we have more information already
    291             //
    292         } else if (my.incomplete && other.incomplete) {
    293             // my and other are incomplete. Doesn't matter which one to
    294             // take. We take mine.
    295             //
    296         } else if (my.isDeleted() && ! other.isDeleted() && my.getVersion() == other.getVersion()) {
    297             // same version, but my is deleted. Assume mine takes precedence
     267            conflicts.add(target,source);
     268        } else if (target.incomplete && !source.incomplete) {
     269            // target is incomplete, source completes it
     270            // => merge source into target
     271            //
     272            target.mergeFrom(source);
     273            childrenToMerge.add(source.getUniqueId());
     274        } else if (!target.incomplete && source.incomplete) {
     275            // target is complete and source is incomplete
     276            // => keep target, it has more information already
     277            //
     278        } else if (target.incomplete && source.incomplete) {
     279            // target and source are incomplete. Doesn't matter which one to
     280            // take. We take target.
     281            //
     282        } else if (target.isDeleted() && ! source.isDeleted() && target.getVersion() == source.getVersion()) {
     283            // same version, but target is deleted. Assume target takes precedence
    298284            // otherwise too many conflicts when refreshing from the server
    299         } else if (my.isDeleted() != other.isDeleted()) {
     285        } else if (target.isDeleted() != source.isDeleted()) {
    300286            // differences in deleted state have to be resolved manually
    301287            //
    302             conflicts.add(my,other);
    303         } else if (! my.isModified() && other.isModified()) {
    304             // my not modified. We can assume that other is the most recent version.
    305             // clone it onto my. But check first, whether other is deleted. if so,
    306             // make sure that my is not references anymore in myDataSet.
    307             //
    308             if (other.isDeleted()) {
    309                 myDataSet.unlinkReferencesToPrimitive(my);
    310             }
    311             my.mergeFrom(other);
    312             fixReferences.add(other.getUniqueId());
    313         } else if (! my.isModified() && !other.isModified() && my.getVersion() == other.getVersion()) {
     288            conflicts.add(target,source);
     289        } else if (! target.isModified() && source.isModified()) {
     290            // target not modified. We can assume that source is the most recent version.
     291            // clone it into target. But check first, whether source is deleted. if so,
     292            // make sure that target is not referenced anymore in myDataSet.
     293            //
     294            if (source.isDeleted()) {
     295                deletedObjectsToUnlink.add(source);
     296            }
     297            target.mergeFrom(source);
     298            childrenToMerge.add(source.getUniqueId());
     299        } else if (! target.isModified() && !source.isModified() && target.getVersion() == source.getVersion()) {
    314300            // both not modified. Keep mine
    315301            //
    316         } else if (! my.isModified() && !other.isModified() && my.getVersion() < other.getVersion()) {
     302        } else if (! target.isModified() && !source.isModified() && target.getVersion() < source.getVersion()) {
    317303            // my not modified but other is newer. clone other onto mine.
    318304            //
    319             my.mergeFrom(other);
    320             fixReferences.add(other.getUniqueId());
    321         } else if (my.isModified() && ! other.isModified() && my.getVersion() == other.getVersion()) {
     305            target.mergeFrom(source);
     306            childrenToMerge.add(source.getUniqueId());
     307        } else if (target.isModified() && ! source.isModified() && target.getVersion() == source.getVersion()) {
    322308            // my is same as other but mine is modified
    323309            // => keep mine
    324         } else if (! my.hasEqualSemanticAttributes(other)) {
     310        } else if (! target.hasEqualSemanticAttributes(source)) {
    325311            // my is modified and is not semantically equal with other. Can't automatically
    326312            // resolve the differences
    327313            // =>  create a conflict
    328             conflicts.add(my,other);
     314            conflicts.add(target,source);
    329315        } else {
    330316            // clone from other, but keep the modified flag. mergeFrom will mainly copy
     
    332318            // attributes should already be equal if we get here.
    333319            //
    334             my.mergeFrom(other);
    335             my.setModified(true);
    336             fixReferences.add(other.getUniqueId());
     320            target.mergeFrom(source);
     321            target.setModified(true);
     322            childrenToMerge.add(source.getUniqueId());
    337323        }
    338324        return true;
     
    346332     */
    347333    public void merge() {
    348         if (theirDataSet == null)
     334        if (sourceDataSet == null)
    349335            return;
    350         for (Node node: theirDataSet.getNodes()) {
     336        for (Node node: sourceDataSet.getNodes()) {
    351337            mergePrimitive(node);
    352338        }
    353         for (Way way: theirDataSet.getWays()) {
     339        for (Way way: sourceDataSet.getWays()) {
    354340            mergePrimitive(way);
    355341        }
    356         for (Relation relation: theirDataSet.getRelations()) {
     342        for (Relation relation: sourceDataSet.getRelations()) {
    357343            mergePrimitive(relation);
    358344        }
     
    365351     * @return
    366352     */
    367     public DataSet getMyDataSet() {
    368         return myDataSet;
     353    public DataSet getTargetDataSet() {
     354        return targetDataSet;
    369355    }
    370356
  • trunk/src/org/openstreetmap/josm/io/OsmServerBackreferenceReader.java

    r2433 r2443  
    269269                DataSetMerger visitor = new DataSetMerger(ret,ds);
    270270                visitor.merge();
    271                 ret = visitor.getMyDataSet();
     271                ret = visitor.getTargetDataSet();
    272272            }
    273273            DataSet ds = getReferringRelations(progressMonitor.createSubTaskMonitor(1, false));
    274274            DataSetMerger visitor = new DataSetMerger(ret,ds);
    275275            visitor.merge();
    276             ret = visitor.getMyDataSet();
     276            ret = visitor.getTargetDataSet();
    277277            readIncompletePrimitives(ret, progressMonitor.createSubTaskMonitor(1, false));
    278278            return ret;
Note: See TracChangeset for help on using the changeset viewer.