Changeset 2417 in josm for trunk


Ignore:
Timestamp:
2009-11-09T01:03:08+01:00 (15 years ago)
Author:
Gubaer
Message:

Had to update MergeVisitor as a consequence of the note in r2406 and the rework in the OSM data primitives.
Unit tests are OK but I expect a few problems in the next days.

Location:
trunk
Files:
5 edited

Legend:

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

    r2410 r2417  
    106106    }
    107107
     108    /**
     109     * Merges the technical and semantical attributes from <code>other</code> onto this.
     110     *
     111     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
     112     * have an assigend OSM id, the IDs have to be the same.
     113     *
     114     * @param other the other primitive. Must not be null.
     115     * @throws IllegalArgumentException thrown if other is null.
     116     * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
     117     * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId()
     118     */
     119    @Override
     120    public void mergeFrom(OsmPrimitive other) {
     121        super.mergeFrom(other);
     122        if (!other.incomplete) {
     123            setCoor(new LatLon(((Node)other).coor));
     124        }
     125    }
     126
    108127    @Override public void load(PrimitiveData data) {
    109128        super.load(data);
  • trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    r2415 r2417  
    789789        clearCached();
    790790        clearErrors();
     791    }
     792
     793    /**
     794     * Merges the technical and semantical attributes from <code>other</code> onto this.
     795     *
     796     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
     797     * have an assigend OSM id, the IDs have to be the same.
     798     *
     799     * @param other the other primitive. Must not be null.
     800     * @throws IllegalArgumentException thrown if other is null.
     801     * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
     802     * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId()
     803     */
     804    public void mergeFrom(OsmPrimitive other) {
     805        if (other == null)
     806            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "other"));
     807        if (other.isNew() ^ isNew())
     808            throw new DataIntegrityProblemException(tr("Can''t merge because either of the participating primitives is new and the other is not"));
     809        if (! other.isNew() && other.getId() != id)
     810            throw new DataIntegrityProblemException(tr("Can''t merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
     811        keys = other.keys == null ? null : new HashMap<String, String>(other.keys);
     812        timestamp = other.timestamp;
     813        version = other.version;
     814        incomplete = other.incomplete;
     815        flags = other.flags;
     816        user= other.user;
    791817    }
    792818
  • trunk/src/org/openstreetmap/josm/data/osm/Way.java

    r2410 r2417  
    229229            return false;
    230230        Way w = (Way)other;
    231         return Arrays.equals(nodes, w.nodes);
     231        if (getNodesCount() != w.getNodesCount()) return false;
     232        for (int i=0;i<getNodesCount();i++) {
     233            if (! getNode(i).hasEqualSemanticAttributes(w.getNode(i)))
     234                return false;
     235        }
     236        return true;
    232237    }
    233238
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java

    r2406 r2417  
    2626    private static Logger logger = Logger.getLogger(MergeVisitor.class.getName());
    2727
    28     /**
    29      * Map from primitives in the database to visited primitives. (Attention: The other way
    30      * round than merged)
    31      */
     28    /** the collection of conflicts created during merging */
    3229    private ConflictCollection conflicts;
    3330
    34 
     31    /** the target dataset for merging */
    3532    private final DataSet myDataSet;
     33    /** the source dataset where primitives are merged from */
    3634    private final DataSet theirDataSet;
    3735
    38     private final HashMap<Long, Node> nodeshash = new HashMap<Long, Node>();
    39     private final HashMap<Long, Way> wayshash = new HashMap<Long, Way>();
    40     private final HashMap<Long, Relation> relshash = new HashMap<Long, Relation>();
    41 
    42     /**
    43      * A list of all primitives that got replaced with other primitives.
    44      * Key is the primitives in the other's dataset and the value is the one that is now
    45      * in ds.nodes instead.
    46      */
    47     private Map<OsmPrimitive, OsmPrimitive> merged;
     36    /**
     37     * A map of all primitives that got replaced with other primitives.
     38     * Key is the primitive id in their dataset, the value is the id in my dataset
     39     */
     40    private Map<Long, Long> merged;
    4841
    4942    /**
     
    5851        this.myDataSet = myDataSet;
    5952        this.theirDataSet = theirDataSet;
    60 
    61         for (Node n : myDataSet.getNodes()) {
    62             if (!n.isNew()) {
    63                 nodeshash.put(n.getId(), n);
    64             }
    65         }
    66         for (Way w : myDataSet.getWays()) {
    67             if (!w.isNew()) {
    68                 wayshash.put(w.getId(), w);
    69             }
    70         }
    71         for (Relation r : myDataSet.getRelations()) {
    72             if (!r.isNew()) {
    73                 relshash.put(r.getId(), r);
    74             }
    75         }
    7653        conflicts = new ConflictCollection();
    77         merged = new HashMap<OsmPrimitive, OsmPrimitive>();
     54        merged = new HashMap<Long, Long>();
    7855    }
    7956
     
    9168     * @param <P>  the type of the other primitive
    9269     * @param other  the other primitive
    93      * @param myPrimitives the collection of my relevant primitives (i.e. only my
    94      *    primitives of the same type)
    95      * @param otherPrimitives  the collection of the other primitives
    96      * @param primitivesWithDefinedIds the collection of my primitives with an
    97      *   assigned id (i.e. id != 0)
    98      */
    99     protected <P extends OsmPrimitive> void mergePrimitive(P other,
    100             DataSet myDataset, Collection<P> myPrimitives, HashMap<Long, P> primitivesWithDefinedIds) {
    101 
     70     */
     71    protected <P extends OsmPrimitive> void mergePrimitive(P other) {
    10272        if (!other.isNew() ) {
    10373            // try to merge onto a matching primitive with the same
    10474            // defined id
    10575            //
    106             if (mergeById(primitivesWithDefinedIds, other))
     76            if (mergeById(other))
    10777                return;
    10878        } else {
     
    11080            // yet but which is equal in its semantic attributes
    11181            //
    112             for (P my : myPrimitives) {
     82            Collection<? extends OsmPrimitive> candidates = null;
     83            switch(other.getType()) {
     84            case NODE: candidates = myDataSet.getNodes(); break;
     85            case WAY: candidates  =myDataSet.getWays(); break;
     86            case RELATION: candidates = myDataSet.getRelations(); break;
     87            }
     88            for (OsmPrimitive my : candidates) {
    11389                if (!my.isNew()) {
    11490                    continue;
     
    11995                        //
    12096                        conflicts.add(my, other);
     97                        merged.put(other.getUniqueId(), my.getUniqueId());
    12198                    } else {
    12299                        // copy the technical attributes from other
     
    126103                        my.setTimestamp(other.getTimestamp());
    127104                        my.setModified(other.isModified());
    128                         merged.put(other, my);
     105                        merged.put(other.getUniqueId(), my.getUniqueId());
    129106                    }
    130107                    return;
     
    133110        }
    134111        // If we get here we didn't find a suitable primitive in
    135         // my dataset. Just add other to my dataset.
     112        // my dataset. Create a clone and add it to my dataset.
    136113        //
    137 
    138         //TODO primitive must be only in one dataset at one time, so remove it from theirDataset. This obviously
    139         // breaks theirDataset and we can only hope that nobody will try to use theirDataset after merging is finished
    140         theirDataSet.removePrimitive(other);
    141         myDataset.addPrimitive(other);
     114        OsmPrimitive my = null;
     115        switch(other.getType()) {
     116        case NODE: my = other.isNew() ? new Node() : new Node(other.getId()); break;
     117        case WAY: my = other.isNew() ? new Way() : new Way(other.getId()); break;
     118        case RELATION: my = other.isNew() ? new Relation() : new Relation(other.getId()); break;
     119        }
     120        my.mergeFrom(other);
     121        myDataSet.addPrimitive(my);
     122        merged.put(other.getUniqueId(), my.getUniqueId());
    142123    }
    143124
    144125    public void visit(Node other) {
    145         mergePrimitive(other, myDataSet, myDataSet.getNodes(), nodeshash);
     126        mergePrimitive(other);
    146127    }
    147128
    148129    public void visit(Way other) {
    149         fixWay(other);
    150         mergePrimitive(other, myDataSet, myDataSet.getWays(), wayshash);
     130        mergePrimitive(other);
    151131    }
    152132
    153133    public void visit(Relation other) {
    154         fixRelation(other);
    155         mergePrimitive(other, myDataSet, myDataSet.getRelations(), relshash);
    156     }
    157 
    158     protected void fixIncomplete(Way w) {
    159         if (!w.incomplete)return;
    160         if (w.incomplete && w.getNodesCount() == 0) return;
    161         for (Node n: w.getNodes()) {
     134        mergePrimitive(other);
     135    }
     136
     137    protected OsmPrimitive getMergeTarget(OsmPrimitive mergeSource) {
     138        Long targetId = merged.get(mergeSource.getUniqueId());
     139        if (targetId == null)
     140            throw new RuntimeException("no merge target for merge primitive " + mergeSource.getUniqueId() + " of type " + mergeSource.getType());
     141        return myDataSet.getPrimitiveById(targetId, mergeSource.getType());
     142    }
     143
     144    protected void fixIncomplete(Way other) {
     145        Way myWay = (Way)getMergeTarget(other);
     146        if (myWay == null)
     147            throw new RuntimeException(tr("Missing merge target for way with id {0}", other.getUniqueId()));
     148        if (!myWay.incomplete)return;
     149        if (myWay.incomplete && other.getNodesCount() == 0) return;
     150        for (Node n: myWay.getNodes()) {
    162151            if (n.incomplete) return;
    163152        }
    164         w.incomplete = false;
     153        myWay.incomplete = false;
    165154    }
    166155
     
    170159     */
    171160    public void fixReferences() {
    172         for (Way w : myDataSet.getWays()) {
    173             fixWay(w);
    174             fixIncomplete(w);
    175         }
    176         for (Relation r : myDataSet.getRelations()) {
    177             fixRelation(r);
    178         }
    179         for (OsmPrimitive osm : conflicts.getMyConflictParties())
    180             if (osm instanceof Way) {
    181                 fixWay((Way) osm);
    182             } else if (osm instanceof Relation) {
    183                 fixRelation((Relation) osm);
    184             }
    185     }
    186 
    187 
    188     private void fixWay(Way w) {
    189         boolean replacedSomething = false;
    190         List<Node> newNodes = new LinkedList<Node>();
    191         for (Node myNode : w.getNodes()) {
    192             Node mergedNode = (Node) merged.get(myNode);
    193             if (mergedNode != null) {
    194                 if (!mergedNode.isDeleted()) {
    195                     newNodes.add(mergedNode);
    196                 } else {
    197                     // we've removed a node from a way during merging.
    198                     // Flag the way as being modified.
    199                     //
    200                     w.setModified(true);
    201                 }
    202                 replacedSomething =  true;
    203             } else {
    204                 newNodes.add(myNode);
    205             }
    206         }
    207         if (replacedSomething) {
    208             w.setNodes(newNodes);
    209         }
    210     }
    211 
    212     private void fixRelation(Relation r) {
    213         boolean replacedSomething = false;
     161        for (Way w : theirDataSet.getWays()) {
     162            if (!conflicts.hasConflictForTheir(w)) {
     163                mergeNodeList(w);
     164                fixIncomplete(w);
     165            }
     166        }
     167        for (Relation r : theirDataSet.getRelations()) {
     168            if (!conflicts.hasConflictForTheir(r)) {
     169                mergeRelationMembers(r);
     170            }
     171        }
     172    }
     173
     174    private void mergeNodeList(Way other) {
     175        Way myWay = (Way)getMergeTarget(other);
     176        if (myWay == null)
     177            throw new RuntimeException(tr("Missing merge target for way with id {0}", other.getUniqueId()));
     178
     179        List<Node> myNodes = new LinkedList<Node>();
     180        for (Node otherNode : other.getNodes()) {
     181            Node myNode = (Node)getMergeTarget(otherNode);
     182            if (myNode != null) {
     183                if (!myNode.isDeleted()) {
     184                    myNodes.add(myNode);
     185                }
     186            } else
     187                throw new RuntimeException(tr("Missing merge target for node with id {0}", otherNode.getUniqueId()));
     188        }
     189
     190        // check whether the node list has changed. If so, set the modified flag on the way
     191        //
     192        if (myWay.getNodes().size() != myNodes.size()) {
     193            myWay.setModified(true);
     194        } else {
     195            for (int i=0; i< myWay.getNodesCount();i++) {
     196                Node n1 = myWay.getNode(i);
     197                Node n2 = myNodes.get(i);
     198                if (n1.isNew() ^ n2.isNew()) {
     199                    myWay.setModified(true);
     200                    break;
     201                } else if (n1.isNew() && n1 != n2) {
     202                    myWay.setModified(true);
     203                    break;
     204                } else if (! n1.isNew() && n1.getId() != n2.getId()) {
     205                    myWay.setModified(true);
     206                    break;
     207                }
     208            }
     209        }
     210        myWay.setNodes(myNodes);
     211    }
     212
     213    private void mergeRelationMembers(Relation other) {
     214        Relation myRelation = (Relation) getMergeTarget(other);
     215        if (myRelation == null)
     216            throw new RuntimeException(tr("Missing merge target for relation with id {0}", other.getUniqueId()));
    214217        LinkedList<RelationMember> newMembers = new LinkedList<RelationMember>();
    215         for (RelationMember myMember : r.getMembers()) {
    216             OsmPrimitive mergedMember = merged.get(myMember.getMember());
    217             if (mergedMember == null) {
    218                 newMembers.add(myMember);
    219             } else {
    220                 if (! mergedMember.isDeleted()) {
    221                     RelationMember newMember = new RelationMember(myMember.getRole(), mergedMember);
    222                     newMembers.add(newMember);
    223                 }
    224                 replacedSomething = true;
    225             }
    226         }
    227         if (replacedSomething) {
    228             r.setMembers(newMembers);
    229         }
     218        for (RelationMember otherMember : other.getMembers()) {
     219            OsmPrimitive mergedMember = getMergeTarget(otherMember.getMember());
     220            if (mergedMember == null)
     221                throw new RuntimeException(tr("Missing merge target of type {0} with id {1}", mergedMember.getType(), mergedMember.getUniqueId()));
     222            if (! mergedMember.isDeleted()) {
     223                RelationMember newMember = new RelationMember(otherMember.getRole(), mergedMember);
     224                newMembers.add(newMember);
     225            }
     226        }
     227
     228        // check whether the list of relation members has changed
     229        //
     230        if (other.getMembersCount() != newMembers.size()) {
     231            myRelation.setModified(true);
     232        } else {
     233            for (int i=0; i<other.getMembersCount();i++) {
     234                RelationMember rm1 = other.getMember(i);
     235                RelationMember rm2 = newMembers.get(i);
     236                if (!rm1.getRole().equals(rm2.getRole())) {
     237                    myRelation.setModified(true);
     238                    break;
     239                } else if (rm1.getMember().isNew() ^ rm2.getMember().isNew()) {
     240                    myRelation.setModified(true);
     241                    break;
     242                } else if (rm1.getMember().isNew() && rm1.getMember() != rm2.getMember()) {
     243                    myRelation.setModified(true);
     244                    break;
     245                } else if (! rm1.getMember().isNew() && rm1.getMember().getId() != rm2.getMember().getId()) {
     246                    myRelation.setModified(true);
     247                    break;
     248                }
     249            }
     250        }
     251        myRelation.setMembers(newMembers);
    230252    }
    231253
     
    233255     * Tries to merge a primitive <code>other</code> into an existing primitive with the same id.
    234256     *
    235      * @param myPrimitivesWithDefinedIds the map of primitives (potential merge targets) with an id <> 0, for faster lookup
    236      *    by id. Key is the id, value the primitive with the given value. myPrimitives.valueSet() is a
    237      *    subset of primitives.
    238257     * @param other  the other primitive which is to be merged onto a primitive in my primitives
    239258     * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise
    240259     */
    241     private <P extends OsmPrimitive> boolean mergeById(HashMap<Long, P> myPrimitivesWithDefinedIds, P other) {
    242 
     260    private <P extends OsmPrimitive> boolean mergeById(P other) {
     261        OsmPrimitive my = myDataSet.getPrimitiveById(other.getId(), other.getType());
    243262        // merge other into an existing primitive with the same id, if possible
    244263        //
    245         if (myPrimitivesWithDefinedIds.containsKey(other.getId())) {
    246             P my = myPrimitivesWithDefinedIds.get(other.getId());
     264        if (my != null) {
    247265            if (my.getVersion() <= other.getVersion()) {
    248266                if (! my.isVisible() && other.isVisible()) {
     
    254272                            Long.toString(my.getId()),Long.toString(my.getVersion()), Long.toString(other.getVersion())
    255273                    ));
    256                     merged.put(other, my);
     274                    merged.put(other.getUniqueId(), my.getUniqueId());
    257275                } else if (my.isVisible() && ! other.isVisible()) {
    258276                    // this is always a conflict because the user has to decide whether
     
    262280                    //
    263281                    conflicts.add(my,other);
     282                    merged.put(other.getUniqueId(), my.getUniqueId());
    264283                } else if (my.incomplete && !other.incomplete) {
    265284                    // my is incomplete, other completes it
    266285                    // => merge other onto my
    267286                    //
    268                     my.incomplete = false;
    269                     my.cloneFrom(other);
    270                     merged.put(other, my);
     287                    my.mergeFrom(other);
     288                    merged.put(other.getUniqueId(), my.getUniqueId());
    271289                } else if (!my.incomplete && other.incomplete) {
    272290                    // my is complete and the other is incomplete
    273291                    // => keep mine, we have more information already
    274292                    //
    275                     merged.put(other, my);
     293                    merged.put(other.getUniqueId(), my.getUniqueId());
    276294                } else if (my.incomplete && other.incomplete) {
    277295                    // my and other are incomplete. Doesn't matter which one to
    278296                    // take. We take mine.
    279297                    //
    280                     merged.put(other, my);
     298                    merged.put(other.getUniqueId(), my.getUniqueId());
    281299                } else if (my.isDeleted() && ! other.isDeleted() && my.getVersion() == other.getVersion()) {
    282300                    // same version, but my is deleted. Assume mine takes precedence
    283301                    // otherwise too many conflicts when refreshing from the server
    284                     merged.put(other, my);
     302                    merged.put(other.getUniqueId(), my.getUniqueId());
    285303                } else if (my.isDeleted() != other.isDeleted()) {
    286304                    // differences in deleted state have to be resolved manually
    287305                    //
    288306                    conflicts.add(my,other);
     307                    merged.put(other.getUniqueId(), my.getUniqueId());
    289308                } else if (! my.isModified() && other.isModified()) {
    290309                    // my not modified. We can assume that other is the most recent version.
     
    295314                        myDataSet.unlinkReferencesToPrimitive(my);
    296315                    }
    297                     my.cloneFrom(other);
    298                     merged.put(other, my);
     316                    my.mergeFrom(other);
     317                    merged.put(other.getUniqueId(), my.getUniqueId());
    299318                } else if (! my.isModified() && !other.isModified() && my.getVersion() == other.getVersion()) {
    300319                    // both not modified. Keep mine
    301320                    //
    302                     merged.put(other,my);
     321                    merged.put(other.getUniqueId(),my.getUniqueId());
    303322                } else if (! my.isModified() && !other.isModified() && my.getVersion() < other.getVersion()) {
    304323                    // my not modified but other is newer. clone other onto mine.
    305324                    //
    306                     my.cloneFrom(other);
    307                     merged.put(other,my);
     325                    my.mergeFrom(other);
     326                    merged.put(other.getUniqueId(),my.getUniqueId());
    308327                } else if (my.isModified() && ! other.isModified() && my.getVersion() == other.getVersion()) {
    309328                    // my is same as other but mine is modified
    310329                    // => keep mine
    311                     merged.put(other, my);
     330                    merged.put(other.getUniqueId(), my.getUniqueId());
    312331                } else if (! my.hasEqualSemanticAttributes(other)) {
    313332                    // my is modified and is not semantically equal with other. Can't automatically
     
    315334                    // =>  create a conflict
    316335                    conflicts.add(my,other);
     336                    merged.put(other.getUniqueId(), my.getUniqueId());
    317337                } else {
    318338                    // clone from other, but keep the modified flag. Clone will mainly copy
     
    320340                    // attributes should already be equal if we get here.
    321341                    //
    322                     my.cloneFrom(other);
     342                    my.mergeFrom(other);
    323343                    my.setModified(true);
    324                     merged.put(other, my);
     344                    merged.put(other.getUniqueId(), my.getUniqueId());
    325345                }
    326346            } else {
    327347                // my.version > other.version => keep my version
    328                 merged.put(other, my);
     348                merged.put(other.getUniqueId(), my.getUniqueId());
    329349            }
    330350            return true;
     
    341361     */
    342362    public void merge() {
    343         for (final OsmPrimitive primitive : theirDataSet.allPrimitives()) {
    344             primitive.visit(this);
     363        for (Node node: theirDataSet.getNodes()) {
     364            node.visit(this);
     365        }
     366        for (Way way: theirDataSet.getWays()) {
     367            way.visit(this);
     368        }
     369        for (Relation relation: theirDataSet.getRelations()) {
     370            relation.visit(this);
    345371        }
    346372        fixReferences();
     
    356382    }
    357383
    358 
    359384    /**
    360385     * replies the map of conflicts
  • trunk/test/unit/org/openstreetmap/josm/data/osm/visitor/MergeVisitorTest.java

    r2396 r2417  
    553553        their.addPrimitive(n5);
    554554
    555         their.addPrimitive(n5);
    556 
    557555        Node n4 = new Node(new LatLon(2,2));
    558556        n4.setOsmId(2,2);
     
    586584     * their way has a higher version and different nodes. My way is modified.
    587585     *
    588      * => merge it onto my way not possbile, conflict
     586     * => merge onto my way not possible, create a conflict
    589587     *
    590588     */
     
    621619        Node n5 = new Node(new LatLon(1,1));
    622620        n5.setOsmId(4,1);
    623         their.addPrimitive(n5);
    624 
    625621        their.addPrimitive(n5);
    626622
     
    788784        theirWay.addNode(n3);
    789785        theirWay.addNode(n4);
    790         theirWay.setUser(User.createOsmUser(1111, "their"));
     786        User user = User.createOsmUser(1111, "their");
     787        theirWay.setUser(user);
    791788        theirWay.setTimestamp(new Date());
    792789        their.addPrimitive(theirWay);
     
    797794        assertEquals(0,visitor.getConflicts().size());
    798795        assertEquals("their", myWay.getUser().getName());
    799         assertEquals(1111, myWay.getUser().getId());
    800796        assertEquals(1111, myWay.getUser().getId());
    801797        assertEquals(theirWay.getTimestamp(), myWay.getTimestamp());
Note: See TracChangeset for help on using the changeset viewer.