Changeset 1690 in josm


Ignore:
Timestamp:
2009-06-23T22:03:37+02:00 (15 years ago)
Author:
Gubaer
Message:

new: MultiFetchServerObjectReader using APIs Multi Fetch method
update: now uses Multi Fetch to check for deleted primitives on the server
update: now uses Multi Fetch to update the selected primitives with the state from the server
fixed: cleaned up merging in MergeVisitor
new: conflict resolution dialog; now resolves conflicts due to different visibilities
new: replacement for realEqual() on OsmPrimitive and derived classes; realEqual now @deprecated
fixed: cleaning up OsmReader
fixed: progress indication in OsmApi

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

Legend:

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

    r1670 r1690  
    66import java.awt.event.ActionEvent;
    77import java.awt.event.KeyEvent;
    8 import java.io.IOException;
    9 import java.net.HttpURLConnection;
    10 import java.util.ArrayList;
    118import java.util.Collection;
     9import java.util.HashSet;
     10import java.util.Set;
    1211
    1312import javax.swing.JOptionPane;
     
    1514import org.openstreetmap.josm.Main;
    1615import org.openstreetmap.josm.command.PurgePrimitivesCommand;
    17 import org.openstreetmap.josm.command.UndeletePrimitivesCommand;
    1816import org.openstreetmap.josm.data.osm.DataSet;
    19 import org.openstreetmap.josm.data.osm.Node;
    2017import org.openstreetmap.josm.data.osm.OsmPrimitive;
    21 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    22 import org.openstreetmap.josm.data.osm.Relation;
    23 import org.openstreetmap.josm.data.osm.RelationMember;
    24 import org.openstreetmap.josm.data.osm.Way;
    25 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    26 import org.openstreetmap.josm.io.OsmApi;
    27 import org.openstreetmap.josm.io.OsmApiException;
    28 import org.openstreetmap.josm.io.OsmServerObjectReader;
    29 import org.openstreetmap.josm.io.OsmTransferException;
     18import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
    3019import org.openstreetmap.josm.tools.Shortcut;
    31 import org.xml.sax.SAXException;
    3220
    3321/**
     
    4129
    4230    /**
    43      * Undelete a node which is already deleted on the server. The API
    44      * doesn't offer a call for "undeleting" a node. We therefore create
    45      * a clone of the node which we flag as new. On the next upload the
    46      * server will assign the node a new id.
    47      *
    48      * @param node the node to undelete
    49      */
    50     protected void  undeleteNode(Node node) {
    51         UndeletePrimitivesCommand cmd = new UndeletePrimitivesCommand(node);
    52         Main.main.undoRedo.add(cmd);
    53     }
    54 
    55     /**
    56      * Undelete a way which is already deleted on the server.
    57      *
    58      * This method also checks whether there are additional nodes referred to by
    59      * this way which are deleted on the server too.
    60      *
    61      * @param way the way to undelete
    62      * @see #undeleteNode(Node)
    63      */
    64     protected void undeleteWay(final Way way) {
    65         class NodeGoneChecker extends PleaseWaitRunnable {
    66 
    67             UndeletePrimitivesCommand cmd = null;
    68 
    69             public NodeGoneChecker() {
    70                 super(tr("Undeleting Way..."), false);
    71             }
    72 
    73             @Override
    74             protected void cancel() {
    75                 OsmApi.getOsmApi().cancel();
    76             }
    77 
    78             @Override
    79             protected void finish() {
    80                 if (cmd != null) {
    81                     Main.main.undoRedo.add(cmd);
    82                 }
    83             }
    84 
    85             /**
    86              * replies the subset of the node list which already
    87              * have an assigned id
    88              *
    89              * @param way  the way
    90              * @return the node list
    91              */
    92             protected ArrayList<Node> getCandidateNodes(Way way) {
    93                 ArrayList<Node> candidates = new ArrayList<Node>();
    94                 for (Node n : way.nodes) {
    95                     if (n.id > 0 && ! candidates.contains(n)) {
    96                         candidates.add(n);
    97                     }
    98                 }
    99                 return candidates;
    100             }
    101 
    102             /**
    103              * checks whether a specific node is deleted on the server
    104              *
    105              * @param n the node
    106              * @return true, if the node is deleted; false, otherwise
    107              * @throws OsmTransferException thrown, if an error occurs while communicating with the API
    108              */
    109             protected boolean isGone(Node n) throws OsmTransferException {
    110                 OsmServerObjectReader reader = new OsmServerObjectReader(n.id, OsmPrimitiveType.from(n), true);
    111                 try {
    112                     reader.parseOsm();
    113                 } catch(OsmApiException e) {
    114                     if (e.getResponseCode() == HttpURLConnection.HTTP_GONE)
    115                         return true;
    116                     throw e;
    117                 } catch(OsmTransferException e) {
    118                     throw e;
    119                 }
    120                 return false;
    121             }
    122 
    123             /**
    124              * displays a confirmation message. The user has to confirm that additional dependent
    125              * nodes should be undeleted too.
    126              *
    127              * @param way  the way
    128              * @param dependent a list of dependent nodes which have to be undelete too
    129              * @return true, if the user confirms; false, otherwise
    130              */
    131             protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) {
    132                 String [] options = {
    133                         tr("Yes, undelete them too"),
    134                         tr("No, cancel operation")
    135                 };
    136                 int ret = JOptionPane.showOptionDialog(
    137                         Main.parent,
    138                         tr("<html>There are {0} additional nodes used by way {1}<br>"
    139                                 + "which are deleted on the server.<br>"
    140                                 + "<br>"
    141                                 + "Do you want to undelete these nodes too?</html>",
    142                                 Long.toString(dependent.size()), Long.toString(way.id)),
    143                                 tr("Undelete additional nodes?"),
    144                                 JOptionPane.YES_NO_OPTION,
    145                                 JOptionPane.QUESTION_MESSAGE,
    146                                 null,
    147                                 options,
    148                                 options[0]
    149                 );
    150 
    151                 switch(ret) {
    152                 case JOptionPane.CLOSED_OPTION: return false;
    153                 case JOptionPane.YES_OPTION: return true;
    154                 case JOptionPane.NO_OPTION: return false;
    155                 }
    156                 return false;
    157 
    158             }
    159 
    160             @Override
    161             protected void realRun() throws SAXException, IOException, OsmTransferException {
    162                 ArrayList<Node> candidate = getCandidateNodes(way);
    163                 ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
    164                 Main.pleaseWaitDlg.progress.setMinimum(0);
    165                 Main.pleaseWaitDlg.progress.setMaximum(candidate.size());
    166 
    167                 for (int i=0; i<candidate.size();i++) {
    168                     Node n = candidate.get(i);
    169                     Main.pleaseWaitDlg.progress.setValue(i);
    170                     Main.pleaseWaitDlg.currentAction.setText(tr("Checking whether node {0} is gone ...", n.id));
    171                     if (isGone(n)) {
    172                         toDelete.add(n);
    173                     }
    174                 }
    175                 if (toDelete.size() > 0) {
    176                     if (!confirmUndeleteDependentPrimitives(way, toDelete))
    177                         return;
    178                 }
    179 
    180                 toDelete.add(way);
    181                 cmd = new UndeletePrimitivesCommand(toDelete);
    182             }
    183         }
    184 
    185         Main.worker.submit(new NodeGoneChecker());
    186     }
    187 
    188     /**
    189      * Undelete a relation which is already deleted on the server.
    190      *
    191      * This method  checks whether there are additional primitives referred to by
    192      * this relation which are already deleted on the server.
    193      *
    194      * @param r the relation
    195      * @see #undeleteNode(Node)
    196      */
    197     protected void undeleteRelation(final Relation r) {
    198         class RelationMemberGoneChecker extends PleaseWaitRunnable {
    199 
    200             UndeletePrimitivesCommand cmd = null;
    201 
    202             public RelationMemberGoneChecker() {
    203                 super(tr("Undeleting relation..."), false);
    204             }
    205 
    206             @Override
    207             protected void cancel() {
    208                 OsmApi.getOsmApi().cancel();
    209             }
    210 
    211             @Override
    212             protected void finish() {
    213                 if (cmd != null) {
    214                     Main.main.undoRedo.add(cmd);
    215                 }
    216             }
    217 
    218             protected ArrayList<OsmPrimitive> getCandidateRelationMembers(Relation r) {
    219                 ArrayList<OsmPrimitive> candidates = new ArrayList<OsmPrimitive>();
    220                 for (RelationMember m : r.members) {
    221                     if (m.member.id > 0 && !candidates.contains(m.member)) {
    222                         candidates.add(m.member);
    223                     }
    224                 }
    225                 return candidates;
    226             }
    227 
    228             protected boolean isGone(OsmPrimitive primitive) throws OsmTransferException {
    229                 OsmServerObjectReader reader = new OsmServerObjectReader(
    230                         primitive.id,
    231                         OsmPrimitiveType.from(primitive),
    232                         true);
    233                 try {
    234                     reader.parseOsm();
    235                 } catch(OsmApiException e) {
    236                     if (e.getResponseCode() == HttpURLConnection.HTTP_GONE)
    237                         return true;
    238                     throw e;
    239                 } catch(OsmTransferException e) {
    240                     throw e;
    241                 }
    242                 return false;
    243             }
    244 
    245             protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) {
    246                 String [] options = {
    247                         tr("Yes, undelete them too"),
    248                         tr("No, cancel operation")
    249                 };
    250                 int ret = JOptionPane.showOptionDialog(
    251                         Main.parent,
    252                         tr("<html>There are {0} additional primitives referred to by relation {1}<br>"
    253                                 + "which are deleted on the server.<br>"
    254                                 + "<br>"
    255                                 + "Do you want to undelete them too?</html>",
    256                                 Long.toString(dependent.size()), Long.toString(r.id)),
    257                                 tr("Undelete dependent primitives?"),
    258                                 JOptionPane.YES_NO_OPTION,
    259                                 JOptionPane.QUESTION_MESSAGE,
    260                                 null,
    261                                 options,
    262                                 options[0]
    263                 );
    264 
    265                 switch(ret) {
    266                 case JOptionPane.CLOSED_OPTION: return false;
    267                 case JOptionPane.YES_OPTION: return true;
    268                 case JOptionPane.NO_OPTION: return false;
    269                 }
    270                 return false;
    271 
    272             }
    273 
    274             @Override
    275             protected void realRun() throws SAXException, IOException, OsmTransferException {
    276                 ArrayList<OsmPrimitive> candidate = getCandidateRelationMembers(r);
    277                 ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
    278                 Main.pleaseWaitDlg.progress.setMinimum(0);
    279                 Main.pleaseWaitDlg.progress.setMaximum(candidate.size());
    280 
    281                 for (int i=0; i<candidate.size();i++) {
    282                     OsmPrimitive primitive = candidate.get(i);
    283                     Main.pleaseWaitDlg.progress.setValue(i);
    284                     Main.pleaseWaitDlg.currentAction.setText(tr("Checking whether primitive {0} is gone ...", primitive.id));
    285                     if (isGone(primitive)) {
    286                         toDelete.add(primitive);
    287                     }
    288                 }
    289                 if (toDelete.size() > 0) {
    290                     if (!confirmUndeleteDependentPrimitives(r, toDelete))
    291                         return;
    292                 }
    293 
    294                 toDelete.add(r);
    295                 cmd = new UndeletePrimitivesCommand(toDelete);
    296             }
    297         }
    298 
    299         Main.worker.submit(new RelationMemberGoneChecker());
    300     }
    301 
    302     /**
    303      * User has decided to keep his local version of a primitive which had been deleted
    304      * on the server
    305      *
    306      * @param id the primitive id
    307      */
    308     protected void handlePrimitiveGoneKeepMine(long id) {
    309         OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
    310         if (primitive instanceof Node) {
    311             undeleteNode((Node)primitive);
    312         } else if (primitive instanceof Way) {
    313             undeleteWay((Way)primitive);
    314         } else if (primitive instanceof Relation) {
    315             undeleteRelation((Relation)primitive);
    316         }
    317     }
    318 
    319     /**
    320      * User has decided to delete his local version of a primitive which had been deleted
    321      * on the server
    322      *
    323      * @param id the primitive id
    324      */
    325     protected void handlePrimitiveGoneDeleteMine(long id) {
    326         OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
    327         PurgePrimitivesCommand cmd = new PurgePrimitivesCommand(primitive);
    328         Main.main.undoRedo.add(cmd);
    329         Main.map.mapView.repaint();
    330     }
    331 
    332     /**
    33331     * handle an exception thrown because a primitive was deleted on the server
    33432     *
     
    33634     */
    33735    protected void handlePrimitiveGoneException(long id) {
    338         Object[] options = new Object[] {
    339                 tr("Keep mine"),
    340                 tr("Delete mine"),
    341                 tr("Cancel")
    342         };
    343         Object defaultOption = options[0];
    344         String msg =  tr("<html>The OSM primitive with id <strong>{0}</strong> has been deleted<br>"
    345                 + "on the server by another mapper.<br>"
    346                 + "<br>"
    347                 + "Click <strong>{1}</strong> to keep your primitive and ignore the deleted state.<br>"
    348                 + "Your primitive will be assigend a new id.<br>"
    349                 + "Click <strong>{2}</strong> to accept the state on the server and to delete your primitive.<br>"
    350                 + "Click <strong>{3}</strong> to cancel.<br>",
    351                 id, options[0], options[1], options[2]
    352         );
    353         int ret = JOptionPane.showOptionDialog(
    354                 null,
    355                 msg,
    356                 tr("Primitive deleted on the server"),
    357                 JOptionPane.YES_NO_CANCEL_OPTION,
    358                 JOptionPane.ERROR_MESSAGE,
    359                 null,
    360                 options,
    361                 defaultOption
    362         );
    363         switch(ret) {
    364         case JOptionPane.CLOSED_OPTION: return;
    365         case JOptionPane.CANCEL_OPTION: return;
    366         case 0: handlePrimitiveGoneKeepMine(id); break;
    367         case 1: handlePrimitiveGoneDeleteMine(id); break;
    368         default:
    369             // should not happen
    370             throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
     36        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
     37        reader.append(Main.main.editLayer().data,id);
     38        DataSet ds = null;
     39        try {
     40            ds = reader.parseOsm();
     41        } catch(Exception e) {
     42            handleUpdateException(e);
     43            return;
    37144        }
     45        Main.main.editLayer().mergeFrom(ds);
    37246    }
    37347
     
    37852     * @param e the exception
    37953     */
    380     protected void handleUpdateException(long id, Exception e) {
    381         if (e instanceof OsmApiException) {
    382             OsmApiException ex = (OsmApiException)e;
    383             // if the primitive was deleted on the server ask the user
    384             // whether he wants to undelete it
    385             //
    386             if (ex.getResponseCode() == HttpURLConnection.HTTP_GONE) {
    387                 handlePrimitiveGoneException(id);
    388                 return;
    389             }
    390         }
    391 
     54    protected void handleUpdateException(Exception e) {
    39255        e.printStackTrace();
    39356        JOptionPane.showMessageDialog(
     
    40669        JOptionPane.showMessageDialog(
    40770                Main.parent,
    408                 tr("Could not find primitive with id {0} in the current dataset", id),
     71                tr("Could not find primitive with id {0} in the current dataset", new Long(id).toString()),
    40972                tr("Missing primitive"),
    41073                JOptionPane.ERROR_MESSAGE
     
    41376
    41477    /**
    415      * Updates the primitive with id <code>id</code> with the current state kept on the server.
    41678     *
    417      * @param id
     79     *
     80     *
    41881     */
    419     public void updatePrimitive(long id) {
    420         OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
    421         if (primitive == null) {
    422             handleMissingPrimitive(id);
    423             return;
    424         }
    425         OsmServerObjectReader reader = new OsmServerObjectReader(
    426                 id,
    427                 OsmPrimitiveType.from(primitive),
    428                 true);
     82    public void updatePrimitives(Collection<OsmPrimitive> selection) {
     83        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
     84        reader.append(selection);
    42985        DataSet ds = null;
    43086        try {
    43187            ds = reader.parseOsm();
    43288        } catch(Exception e) {
    433             handleUpdateException(id, e);
     89            handleUpdateException(e);
    43490            return;
    43591        }
     
    43793    }
    43894
     95    public void updatePrimitive(long id) {
     96        OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
     97        Set<OsmPrimitive> s = new HashSet<OsmPrimitive>();
     98        s.add(primitive);
     99        updatePrimitives(s);
     100    }
    439101
    440102    public UpdateSelectionAction() {
     
    461123            return;
    462124        }
    463 
    464         // check whether the current selection has an acceptable range.
    465         // We don't want to hammer the API with hundreds of individual
    466         // GET requests.
    467         //
    468         if (selection.size() > DEFAULT_MAX_SIZE_UPDATE_SELECTION) {
    469             JOptionPane.showMessageDialog(
    470                     Main.parent,
    471                     tr("<html>There are  <strong>{0}</strong> primitives <br>"
    472                             + "selected for individual update. Please reduce the selection<br>"
    473                             + "to max. {1} primitives.</html>",
    474                             selection.size(), DEFAULT_MAX_SIZE_UPDATE_SELECTION
    475                     ),
    476                     tr("Selection too big"),
    477                     JOptionPane.WARNING_MESSAGE
    478             );
    479             return;
    480         }
    481         for(OsmPrimitive primitive : selection) {
    482             // FIXME: users should be able to abort this loop
    483             //
    484             updatePrimitive(primitive.id);
    485         }
     125        updatePrimitives(selection);
    486126    }
    487127}
  • trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTaskList.java

    r1682 r1690  
    128128    protected void handlePotentiallyDeletedPrimitives(Set<Long> potentiallyDeleted) {
    129129        String [] options = {
    130                 "Check individually",
     130                "Check on the server",
    131131                "Ignore"
    132132        };
     
    138138                + "conflict.<br>"
    139139                + "<br>"
    140                 + "Click <strong>{1}</strong> to check these primitives individually.<br>"
     140                + "Click <strong>{1}</strong> to check the state of these primitives<br>"
     141                + "on the server.<br>"
    141142                + "Click <strong>{2}</strong> to ignore.<br>"
    142143                + "</html>",
  • trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java

    r1654 r1690  
    6666
    6767        if (decision.equals(MergeDecisionType.KEEP_MINE)) {
    68             // do nothing
     68            if (my.deleted) {
     69                // because my was involved in a conflict it my still be referred
     70                // to from a way or a relation. Fix this now.
     71                //
     72                Main.main.editLayer().data.unlinkReferencesToPrimitive(my);
     73            }
    6974        } else if (decision.equals(MergeDecisionType.KEEP_THEIR)) {
    70             my.deleted = their.deleted;
     75            if (their.deleted) {
     76                Main.main.editLayer().data.unlinkReferencesToPrimitive(my);
     77                my.delete(true);
     78            } else {
     79                my.deleted = their.deleted;
     80            }
    7181        } else
    7282            // should not happen
  • trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java

    r1670 r1690  
    66import java.util.ArrayList;
    77import java.util.Collection;
     8import java.util.HashMap;
    89import java.util.List;
     10import java.util.Map;
    911
    1012import javax.swing.JLabel;
     
    3335 */
    3436public class PurgePrimitivesCommand extends Command{
     37
    3538
    3639    /**
     
    141144    private ArrayList<OsmParentChildPair> pairs;
    142145
     146    private Map<OsmPrimitive, OsmPrimitive> resolvedConflicts;
     147
    143148    /**
    144149     * constructor
     
    149154        purgedPrimitives = new ArrayList<OsmPrimitive>();
    150155        pairs = new ArrayList<OsmParentChildPair>();
     156        resolvedConflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
    151157    }
    152158
     
    180186            if (pair.getParent() instanceof Way) {
    181187                Way w = (Way)pair.getParent();
    182                 System.out.println("removing reference from way " + w.id);
     188                System.out.println(tr("removing reference from way {0}",w.id));
    183189                w.nodes.remove(primitive);
    184190                // if a way ends up with less than two node we
     
    194200            } else if (pair.getParent() instanceof Relation) {
    195201                Relation r = (Relation)pair.getParent();
    196                 System.out.println("removing reference from relation " + r.id);
     202                System.out.println(tr("removing reference from relation {0}",r.id));
    197203                r.removeMembersFor(primitive);
    198204            }
     
    220226            }
    221227            purgedPrimitives.add(toPurge);
     228            if (Main.map.conflictDialog.conflicts.containsKey(toPurge)) {
     229                resolvedConflicts.put(toPurge, Main.map.conflictDialog.conflicts.get(toPurge));
     230                Main.map.conflictDialog.removeConflictForPrimitive(toPurge);
     231            }
    222232        }
    223233        return super.executeCommand();
     
    236246    @Override
    237247    public void undoCommand() {
     248
     249        // restore purged primitives
     250        //
    238251        for (OsmPrimitive purged : purgedPrimitives) {
    239252            Main.ds.addPrimitive(purged);
     253        }
     254
     255        // restore conflicts
     256        //
     257        for (OsmPrimitive primitive : resolvedConflicts.keySet()) {
     258            Main.map.conflictDialog.addConflict(primitive, resolvedConflicts.get(primitive));
    240259        }
    241260
  • trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java

    r1670 r1690  
    66import java.util.ArrayList;
    77import java.util.Collection;
     8import java.util.HashMap;
     9import java.util.Map;
    810
    911import javax.swing.JLabel;
     
    1113import javax.swing.tree.MutableTreeNode;
    1214
     15import org.openstreetmap.josm.Main;
    1316import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1417import org.openstreetmap.josm.tools.ImageProvider;
     
    2427    /** the node to undelete */
    2528    private ArrayList<OsmPrimitive> toUndelete;
     29    private Map<OsmPrimitive,OsmPrimitive> resolvedConflicts;
    2630
     31    protected UndeletePrimitivesCommand() {
     32        toUndelete = new ArrayList<OsmPrimitive>();
     33        resolvedConflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
     34    }
    2735    /**
    2836     * constructor
     
    3038     */
    3139    public UndeletePrimitivesCommand(OsmPrimitive node) {
    32         toUndelete = new ArrayList<OsmPrimitive>();
     40        this();
    3341        toUndelete.add(node);
    3442    }
     
    3947     */
    4048    public UndeletePrimitivesCommand(OsmPrimitive ... toUndelete) {
    41         this.toUndelete = new ArrayList<OsmPrimitive>();
     49        this();
    4250        for (int i=0; i < toUndelete.length; i++) {
    4351            this.toUndelete.add(toUndelete[i]);
     
    5058     */
    5159    public UndeletePrimitivesCommand(Collection<OsmPrimitive> toUndelete) {
    52         this.toUndelete = new ArrayList<OsmPrimitive>();
     60        this();
    5361        this.toUndelete.addAll(toUndelete);
    5462    }
     
    7078        super.executeCommand();
    7179        for(OsmPrimitive primitive: toUndelete) {
     80            if (Main.map.conflictDialog.conflicts.containsKey(primitive)) {
     81                resolvedConflicts.put(primitive, Main.map.conflictDialog.conflicts.get(primitive));
     82                Main.map.conflictDialog.removeConflictForPrimitive(primitive);
     83            }
    7284            primitive.id = 0;
    7385        }
     
    8092        modified.addAll(toUndelete);
    8193    }
     94    @Override
     95    public void undoCommand() {
     96        super.undoCommand();
     97
     98        for (OsmPrimitive my: resolvedConflicts.keySet()) {
     99            if (!Main.map.conflictDialog.conflicts.containsKey(my)) {
     100                Main.map.conflictDialog.addConflict(my, resolvedConflicts.get(my));
     101            }
     102        }
     103    }
    82104}
  • trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java

    r1670 r1690  
    1111
    1212import org.openstreetmap.josm.Main;
    13 import org.openstreetmap.josm.data.osm.Node;
    1413import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1514import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    16 import org.openstreetmap.josm.data.osm.Relation;
    17 import org.openstreetmap.josm.data.osm.Way;
    1815import org.openstreetmap.josm.tools.ImageProvider;
    1916
  • trunk/src/org/openstreetmap/josm/data/osm/DataSet.java

    r1677 r1690  
    88import java.util.HashSet;
    99import java.util.HashMap;
     10import java.util.Iterator;
    1011import java.util.LinkedList;
    1112import java.util.List;
     
    8081        Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
    8182        for (OsmPrimitive osm : allPrimitives())
    82             if (!osm.deleted) {
     83            if (osm.visible && !osm.deleted) {
    8384                o.add(osm);
    8485            }
     
    8990        Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
    9091        for (OsmPrimitive osm : allPrimitives())
    91             if (!osm.deleted && !osm.incomplete) {
     92            if (osm.visible && !osm.deleted && !osm.incomplete) {
    9293                o.add(osm);
    9394            }
     
    9899        Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
    99100        for (OsmPrimitive osm : allPrimitives())
    100             if (!osm.deleted && !osm.incomplete && !(osm instanceof Relation)) {
     101            if (osm.visible && !osm.deleted && !osm.incomplete && !(osm instanceof Relation)) {
    101102                o.add(osm);
    102103            }
     
    311312        return ret;
    312313    }
     314
     315    protected void deleteWay(Way way) {
     316        way.nodes.clear();
     317        way.delete(true);
     318    }
     319
     320    /**
     321     * removes all references from ways in this dataset to a particular node
     322     *
     323     * @param node the node
     324     */
     325    public void unlinkNodeFromWays(Node node) {
     326        for (Way way: ways) {
     327            if (way.nodes.contains(node)) {
     328                way.nodes.remove(node);
     329                if (way.nodes.size() < 2) {
     330                    deleteWay(way);
     331                }
     332            }
     333        }
     334    }
     335
     336    /**
     337     * removes all references from relations in this dataset  to this primitive
     338     *
     339     * @param primitive the primitive
     340     */
     341    public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
     342        for (Relation relation : relations) {
     343            Iterator<RelationMember> it = relation.members.iterator();
     344            while(it.hasNext()) {
     345                RelationMember member = it.next();
     346                if (member.member.equals(primitive)) {
     347                    it.remove();
     348                }
     349            }
     350        }
     351    }
     352
     353    /**
     354     * removes all references from from other primitives  to the
     355     * referenced primitive
     356     *
     357     * @param referencedPrimitive the referenced primitive
     358     */
     359    public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
     360        if (referencedPrimitive instanceof Node) {
     361            unlinkNodeFromWays((Node)referencedPrimitive);
     362            unlinkPrimitiveFromRelations(referencedPrimitive);
     363        } else {
     364            unlinkPrimitiveFromRelations(referencedPrimitive);
     365        }
     366    }
    313367}
  • trunk/src/org/openstreetmap/josm/data/osm/Node.java

    r1640 r1690  
    3131
    3232    public final void setEastNorth(EastNorth eastNorth) {
    33        this.eastNorth = eastNorth;
    34        this.coor = Main.proj.eastNorth2latlon(eastNorth);
     33        this.eastNorth = eastNorth;
     34        this.coor = Main.proj.eastNorth2latlon(eastNorth);
    3535    }
    3636
     
    8787    }
    8888
     89    /**
     90     * @deprecated
     91     * @see #hasEqualSemanticAttributes(OsmPrimitive)
     92     * @see #hasEqualTechnicalAttributes(OsmPrimitive)
     93     */
     94    @Deprecated
    8995    @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
    9096        if (osm instanceof Node) {
     
    99105    }
    100106
     107    @Override
     108    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
     109        if (other == null || ! (other instanceof Node) )
     110            return false;
     111        if (! super.hasEqualSemanticAttributes(other))
     112            return false;
     113        Node n = (Node)other;
     114        if (coor == null && n.coor == null)
     115            return true;
     116        else if (coor != null && n.coor != null)
     117            return coor.equals(n.coor);
     118        else
     119            return false;
     120    }
     121
    101122    public int compareTo(OsmPrimitive o) {
    102123        return o instanceof Node ? Long.valueOf(id).compareTo(o.id) : 1;
    103124    }
    104125
     126    @Override
    105127    public String getName() {
    106128        String name;
     
    109131        } else {
    110132            name = get("name");
    111             if (name == null)
     133            if (name == null) {
    112134                name = id == 0 ? tr("node") : ""+id;
     135            }
    113136            name += " (" + coor.latToString(mCord) + ", " + coor.lonToString(mCord) + ")";
    114137        }
  • trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    r1530 r1690  
    4343    public void putError(String text, Boolean isError)
    4444    {
    45         if(errors == null)
     45        if(errors == null) {
    4646            errors = new ArrayList<String>();
     47        }
    4748        String s = isError ? tr("Error: {0}", text) : tr("Warning: {0}", text);
    4849        errors.add(s);
     
    9192     * Visibility status as specified by the server. The visible attribute was
    9293     * introduced with the 0.4 API to be able to communicate deleted objects
    93      * (they will have visible=false). Currently JOSM does never deal with
    94      * these, so this is really for future use only.
     94     * (they will have visible=false).
    9595     */
    9696    public boolean visible = true;
     
    195195    @Override public boolean equals(Object obj) {
    196196        if (id == 0) return obj == this;
    197         if (obj instanceof OsmPrimitive) { // not null too
     197        if (obj instanceof OsmPrimitive)
    198198            return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
    199         }
    200199        return false;
    201200    }
     
    226225     */
    227226    public final void put(String key, String value) {
    228         if (value == null)
     227        if (value == null) {
    229228            remove(key);
    230         else {
    231             if (keys == null)
     229        } else {
     230            if (keys == null) {
    232231                keys = new HashMap<String, String>();
     232            }
    233233            keys.put(key, value);
    234234        }
     
    241241        if (keys != null) {
    242242            keys.remove(key);
    243             if (keys.isEmpty())
     243            if (keys.isEmpty()) {
    244244                keys = null;
     245            }
    245246        }
    246247        mappaintStyle = null;
     
    280281        version = osm.version;
    281282        incomplete = osm.incomplete;
     283        visible = osm.visible;
    282284        clearCached();
    283285        clearErrors();
     
    288290     * but for the whole object (for conflict resolving)
    289291     * @param semanticOnly if <code>true</code>, modified flag and timestamp are not compared
    290      */
     292     *
     293     * @deprecated
     294     * @see #hasEqualSemanticAttributes(OsmPrimitive)
     295     * @see #hasEqualTechnicalAttributes(OsmPrimitive)
     296     */
     297    @Deprecated
    291298    public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
    292299        return id == osm.id
    293300        && incomplete == osm.incomplete
    294301        && deleted == osm.deleted
    295         && (semanticOnly || (modified == osm.modified
    296          && timestamp == osm.timestamp
    297          && version == osm.version
    298          && visible == osm.visible
    299          && (user == null ? osm.user==null : user==osm.user)))
     302        && (semanticOnly || (
     303                modified == osm.modified
     304                && timestamp == osm.timestamp
     305                && version == osm.version
     306                && visible == osm.visible
     307                && (user == null ? osm.user==null : user==osm.user))
     308        )
    300309        && (keys == null ? osm.keys==null : keys.equals(osm.keys));
     310    }
     311
     312    /**
     313     * Replies true if this primitive and other are equal with respect to their
     314     * semantic attributes.
     315     * <ol>
     316     *   <li>equal id</ol>
     317     *   <li>both are complete or both are incomplete</li>
     318     *   <li>both have the same tags</li>
     319     * </ol>
     320     * @param other
     321     * @return true if this primitive and other are equal with respect to their
     322     * semantic attributes.
     323     */
     324    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
     325        if (id != other.id)
     326            return false;
     327        if (incomplete && ! other.incomplete || !incomplete  && other.incomplete)
     328            return false;
     329        return (keys == null ? other.keys==null : keys.equals(other.keys));
     330    }
     331
     332    /**
     333     * Replies true if this primitive and other are equal with respect to their
     334     * technical attributes. The attributes:
     335     * <ol>
     336     *   <li>deleted</ol>
     337     *   <li>modified</ol>
     338     *   <li>timestamp</ol>
     339     *   <li>version</ol>
     340     *   <li>visible</ol>
     341     *   <li>user</ol>
     342     * </ol>
     343     * have to be equal
     344     * @param other the other primitive
     345     * @return true if this primitive and other are equal with respect to their
     346     * technical attributes
     347     */
     348    public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
     349        if (other == null) return false;
     350
     351        return
     352        deleted == other.deleted
     353        && modified == other.modified
     354        && timestamp == other.timestamp
     355        && version == other.version
     356        && visible == other.visible
     357        && (user == null ? other.user==null : user==other.user);
    301358    }
    302359
     
    312369        if (keys != null) {
    313370            for (Entry<String,String> e : keys.entrySet()) {
    314                 if (!uninteresting.contains(e.getKey())) {
     371                if (!uninteresting.contains(e.getKey()))
    315372                    return true;
    316                 }
    317373            }
    318374        }
     
    327383        if (keys != null) {
    328384            for (Entry<String,String> e : keys.entrySet()) {
    329                 if (directionKeys.contains(e.getKey())) {
     385                if (directionKeys.contains(e.getKey()))
    330386                    return true;
    331                 }
    332387            }
    333388        }
  • trunk/src/org/openstreetmap/josm/data/osm/Relation.java

    r1677 r1690  
    7070    }
    7171
     72    @Deprecated
    7273    @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
    7374        return osm instanceof Relation ? super.realEqual(osm, semanticOnly) && members.equals(((Relation)osm).members) : false;
     75    }
     76
     77    @Override
     78    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
     79        if (other == null || ! (other instanceof Relation) )
     80            return false;
     81        if (! super.hasEqualSemanticAttributes(other))
     82            return false;
     83        Relation r = (Relation)other;
     84        return members.equals(r.members);
    7485    }
    7586
  • trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java

    r1169 r1690  
    3333
    3434    @Override public boolean equals(Object other) {
    35         if (!(other instanceof RelationMember)) return false;
     35        if (other == null || !(other instanceof RelationMember)) return false;
    3636        RelationMember otherMember = (RelationMember) other;
    3737        return otherMember.role.equals(role) && otherMember.member.equals(member);
  • trunk/src/org/openstreetmap/josm/data/osm/Way.java

    r1677 r1690  
    3939    public void visitNodes(Visitor v) {
    4040        if (incomplete) return;
    41         for (Node n : this.nodes)
     41        for (Node n : this.nodes) {
    4242            v.visit(n);
     43        }
    4344    }
    4445
     
    100101    }
    101102
     103    @Deprecated
    102104    @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
    103105        return osm instanceof Way ? super.realEqual(osm, semanticOnly) && nodes.equals(((Way)osm).nodes) : false;
     106    }
     107
     108    @Override
     109    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
     110        if (other == null || ! (other instanceof Way) )
     111            return false;
     112        if (! super.hasEqualSemanticAttributes(other))
     113            return false;
     114        Way w = (Way)other;
     115        return nodes.equals(w.nodes);
    104116    }
    105117
     
    110122    }
    111123
     124    @Override
    112125    public String getName() {
    113126        String name;
     
    116129        } else {
    117130            name = get("name");
    118             if (name == null) name = get("ref");
     131            if (name == null) {
     132                name = get("ref");
     133            }
    119134            if (name == null) {
    120135                name =
    121136                    (get("highway") != null) ? tr("highway") :
    122                     (get("railway") != null) ? tr("railway") :
    123                     (get("waterway") != null) ? tr("waterway") :
    124                     (get("landuse") != null) ? tr("landuse") : "";
     137                        (get("railway") != null) ? tr("railway") :
     138                            (get("waterway") != null) ? tr("waterway") :
     139                                (get("landuse") != null) ? tr("landuse") : "";
    125140            }
    126141
     
    128143            String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
    129144            name += (name.length() > 0) ? " ("+nodes+")" : nodes;
    130             if(errors != null)
     145            if(errors != null) {
    131146                name = "*"+name;
     147            }
    132148        }
    133149        return name;
     
    138154        boolean closed = (lastNode() == n && firstNode() == n);
    139155        int i;
    140         while ((i = nodes.indexOf(n)) >= 0)
     156        while ((i = nodes.indexOf(n)) >= 0) {
    141157            nodes.remove(i);
     158        }
    142159        i = nodes.size();
    143         if (closed && i > 2) // close again
     160        if (closed && i > 2) {
    144161            addNode(firstNode());
    145         // prevent closed ways with less than 3 different nodes
    146         else if (i >= 2 && i <= 3 && nodes.get(0) == nodes.get(i-1))
     162        } else if (i >= 2 && i <= 3 && nodes.get(0) == nodes.get(i-1)) {
    147163            nodes.remove(i-1);
     164        }
    148165    }
    149166
     
    151168        if (incomplete) return;
    152169        for(OsmPrimitive p : selection) {
    153            if (p instanceof Node) {
    154                removeNode((Node)p);
    155            }
    156        }
     170            if (p instanceof Node) {
     171                removeNode((Node)p);
     172            }
     173        }
    157174    }
    158175
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java

    r1677 r1690  
    22package org.openstreetmap.josm.data.osm.visitor;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    46import java.util.Collection;
    5 import java.util.Date;
    67import java.util.HashMap;
    78import java.util.Iterator;
    89import java.util.LinkedList;
    910import java.util.Map;
     11import java.util.logging.Logger;
    1012
    1113import org.openstreetmap.josm.data.osm.DataSet;
     14import org.openstreetmap.josm.data.osm.Node;
     15import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1216import org.openstreetmap.josm.data.osm.Relation;
    1317import org.openstreetmap.josm.data.osm.RelationMember;
    14 import org.openstreetmap.josm.data.osm.Node;
    15 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1618import org.openstreetmap.josm.data.osm.Way;
    1719
    1820/**
    19  * A visitor that get a data set at construction time and merge every visited object
     21 * A visitor that gets a data set at construction time and merges every visited object
    2022 * into it.
    2123 *
    2224 * @author imi
     25 * @author Gubaer
    2326 */
    2427public class MergeVisitor extends AbstractVisitor {
     28    private static Logger logger = Logger.getLogger(MergeVisitor.class.getName());
    2529
    2630    /**
     
    2832     * round than merged)
    2933     */
    30     public Map<OsmPrimitive, OsmPrimitive> conflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
    31 
    32     private final DataSet ds;
    33     private final DataSet mergeds;
     34    private Map<OsmPrimitive, OsmPrimitive> conflicts;
     35
     36    private final DataSet myDataSet;
     37    private final DataSet theirDataSet;
    3438
    3539    private final HashMap<Long, Node> nodeshash = new HashMap<Long, Node>();
     
    4246     * in ds.nodes instead.
    4347     */
    44     private final Map<OsmPrimitive, OsmPrimitive> merged
    45         = new HashMap<OsmPrimitive, OsmPrimitive>();
    46 
    47     public MergeVisitor(DataSet ds, DataSet mergeds) {
    48         this.ds = ds;
    49         this.mergeds = mergeds;
    50 
    51         for (Node n : ds.nodes) if (n.id != 0) nodeshash.put(n.id, n);
    52         for (Way w : ds.ways) if (w.id != 0) wayshash.put(w.id, w);
    53         for (Relation r : ds.relations) if (r.id != 0) relshash.put(r.id, r);
    54     }
    55 
    56     private <P extends OsmPrimitive> void genMerge(P other,
    57             Collection<P> myprims, Collection<P> mergeprims,
    58             HashMap<Long, P> primhash) {
    59         // 1. Try to find an identical prim with the same id.
    60         if (mergeById(myprims, primhash, other))
    61             return;
    62 
    63         // 2. Try to find a prim we can merge with the prim from the other ds.
    64         for (P my : myprims) {
    65             // LinkedList.contains calls equal, and OsmPrimitive.equal
    66             // compares just the id.
    67             if (match(my, other) && !mergeprims.contains(my)) {
    68                 merged.put(other, my);
    69                 mergeCommon(my, other);
     48    private Map<OsmPrimitive, OsmPrimitive> merged;
     49
     50    /**
     51     * constructor
     52     *
     53     * The visitor will merge <code>theirDataSet</code> onto <code>myDataSet</code>
     54     *
     55     * @param myDataSet  dataset with my primitives
     56     * @param theirDataSet dataset with their primitives.
     57     */
     58    public MergeVisitor(DataSet myDataSet, DataSet theirDataSet) {
     59        this.myDataSet = myDataSet;
     60        this.theirDataSet = theirDataSet;
     61
     62        for (Node n : myDataSet.nodes) if (n.id != 0) {
     63            nodeshash.put(n.id, n);
     64        }
     65        for (Way w : myDataSet.ways) if (w.id != 0) {
     66            wayshash.put(w.id, w);
     67        }
     68        for (Relation r : myDataSet.relations) if (r.id != 0) {
     69            relshash.put(r.id, r);
     70        }
     71        conflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
     72        merged = new HashMap<OsmPrimitive, OsmPrimitive>();
     73    }
     74
     75    /**
     76     * Merges a primitive <code>other</code> of type <P> onto my primitives.
     77     *
     78     * If other.id != 0 it tries to merge it with an corresponding primitive from
     79     * my dataset with the same id. If this is not possible a conflict is remembered
     80     * in {@see #conflicts}.
     81     *
     82     * If other.id == 0 it tries to find a primitive in my dataset with id == 0 which
     83     * is semantically equal. If it finds one it merges its technical attributes onto
     84     * my primitive.
     85     *
     86     * @param <P>  the type of the other primitive
     87     * @param other  the other primitive
     88     * @param myPrimitives the collection of my relevant primitives (i.e. only my
     89     *    primitives of the same type)
     90     * @param otherPrimitives  the collection of the other primitives
     91     * @param primitivesWithDefinedIds the collection of my primitives with an
     92     *   assigned id (i.e. id != 0)
     93     */
     94    protected <P extends OsmPrimitive> void mergePrimitive(P other,
     95            Collection<P> myPrimitives, Collection<P> otherPrimitives,
     96            HashMap<Long, P> primitivesWithDefinedIds) {
     97
     98        if (other.id > 0 ) {
     99            // try to merge onto a matching primitive with the same
     100            // defined id
     101            //
     102            if (mergeById(myPrimitives, primitivesWithDefinedIds, other))
    70103                return;
    71             }
    72         }
    73 
    74         // 3. No idea how to merge that.  Simply add it unchanged.
    75         myprims.add(other);
     104        } else {
     105            // try to merge onto a primitive with which has no id assigned
     106            // yet but which is equal in its semantic attributes
     107            //
     108            for (P my : myPrimitives) {
     109                if (my.id >0 ) {
     110                    continue;
     111                }
     112                if (my.hasEqualSemanticAttributes(other)) {
     113                    // copy the technical attributes from their
     114                    // version
     115                    if (other.deleted) {
     116                        myDataSet.unlinkReferencesToPrimitive(my);
     117                        my.delete(true);
     118                    }
     119                    my.visible = other.visible;
     120                    my.user = other.user;
     121                    my.setTimestamp(other.getTimestamp());
     122                    my.modified = other.modified;
     123                    merged.put(other, my);
     124                    return;
     125                }
     126            }
     127        }
     128        // If we get here we didn't find a suitable primitive in
     129        // my dataset. Just add other to my dataset.
     130        //
     131        myPrimitives.add(other);
    76132    }
    77133
    78134    public void visit(Node other) {
    79         genMerge(other, ds.nodes, mergeds.nodes, nodeshash);
     135        mergePrimitive(other, myDataSet.nodes, theirDataSet.nodes, nodeshash);
    80136    }
    81137
    82138    public void visit(Way other) {
    83139        fixWay(other);
    84         genMerge(other, ds.ways, mergeds.ways, wayshash);
     140        mergePrimitive(other, myDataSet.ways, theirDataSet.ways, wayshash);
    85141    }
    86142
    87143    public void visit(Relation other) {
    88144        fixRelation(other);
    89         genMerge(other, ds.relations, mergeds.relations, relshash);
     145        mergePrimitive(other, myDataSet.relations, theirDataSet.relations, relshash);
    90146    }
    91147
     
    95151     */
    96152    public void fixReferences() {
    97         for (Way w : ds.ways) fixWay(w);
    98         for (Relation r : ds.relations) fixRelation(r);
     153        for (Way w : myDataSet.ways) {
     154            fixWay(w);
     155        }
     156        for (Relation r : myDataSet.relations) {
     157            fixRelation(r);
     158        }
    99159        for (OsmPrimitive osm : conflicts.values())
    100             if (osm instanceof Way)
     160            if (osm instanceof Way) {
    101161                fixWay((Way)osm);
    102             else if (osm instanceof Relation)
     162            } else if (osm instanceof Relation) {
    103163                fixRelation((Relation) osm);
     164            }
    104165    }
    105166
     
    110171            Node otherN = (Node) merged.get(n);
    111172            newNodes.add(otherN == null ? n : otherN);
    112             if (otherN != null)
     173            if (otherN != null) {
    113174                replacedSomething = true;
     175            }
    114176        }
    115177        if (replacedSomething) {
     
    139201    }
    140202
    141     private static <P extends OsmPrimitive> boolean match(P p1, P p2) {
    142         if ((p1.id == 0 || p2.id == 0) && !p1.incomplete && !p2.incomplete) {
    143             return realMatch(p1, p2);
    144         }
    145         return p1.id == p2.id;
    146     }
    147 
    148     /** @return true if the prims have pretty much the same data, i.e. the
    149      * same position, the same members, ...
    150      */
    151     // Java cannot dispatch on generics...
    152     private static boolean realMatch(OsmPrimitive p1, OsmPrimitive p2) {
    153         if (p1 instanceof Node && p2 instanceof Node) {
    154             return realMatch((Node) p1, (Node) p2);
    155         } else if (p1 instanceof Way && p2 instanceof Way) {
    156             return realMatch((Way) p1, (Way) p2);
    157         } else if (p1 instanceof Relation && p2 instanceof Relation) {
    158             return realMatch((Relation) p1, (Relation) p2);
    159         } else {
    160             throw new RuntimeException("arguments have unknown type");
    161         }
    162     }
    163 
    164     private static boolean realMatch(Node n1, Node n2) {
    165         return n1.getCoor().equalsEpsilon(n2.getCoor());
    166     }
    167 
    168     private static boolean realMatch(Way w1, Way w2) {
    169         if (w1.nodes.size() != w2.nodes.size())
    170             return false;
    171         Iterator<Node> it = w1.nodes.iterator();
    172         for (Node n : w2.nodes)
    173             if (!match(n, it.next()))
    174                 return false;
    175         return true;
    176     }
    177 
    178     private static boolean realMatch(Relation w1, Relation w2) {
    179         // FIXME this is not perfect yet...
    180         if (w1.members.size() != w2.members.size())
    181             return false;
    182         for (RelationMember em : w1.members) {
    183             if (!w2.members.contains(em)) {
    184                 return false;
    185             }
    186         }
    187         return true;
    188     }
    189 
    190     /**
    191      * Merge the common parts of an osm primitive.
    192      * @param my The object, the information gets merged into
    193      * @param other The object, the information gets merged from
    194      */
    195     private void mergeCommon(OsmPrimitive my, OsmPrimitive other) {
    196         if (other.deleted)
    197             my.delete(true);
    198         if (my.id == 0 || !my.modified || other.modified) {
    199             if (my.id == 0 && other.id != 0) {
    200                 my.id = other.id;
    201                 my.modified = other.modified; // match a new node
    202                 my.version = other.version;
    203             } else if (my.id != 0 && other.id != 0 && other.modified)
    204                 my.modified = true;
    205         }
    206         if (other.keys == null)
    207             return;
    208         if (my.keySet().containsAll(other.keys.keySet()))
    209             return;
    210         if (my.keys == null)
    211             my.keys = other.keys;
    212         else
    213             my.keys.putAll(other.keys);
    214 
    215         my.modified = true;
    216     }
    217 
    218203    /**
    219204     * Tries to merge a primitive <code>other</code> into an existing primitive with the same id.
    220205     *
    221206     * @param myPrimitives the complete set of my primitives (potential merge targets)
    222      * @param myPrimitivesWithID the map of primitives (potential merge targets) with an id <> 0, for faster lookup
     207     * @param myPrimitivesWithDefinedIds the map of primitives (potential merge targets) with an id <> 0, for faster lookup
    223208     *    by id. Key is the id, value the primitive with the given value. myPrimitives.valueSet() is a
    224209     *    subset of primitives.
    225      * @param other  the other primitive which is to be merged with a primitive in primitives if possible
     210     * @param other  the other primitive which is to be merged onto a primitive in my primitives
    226211     * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise
    227212     */
    228213    private <P extends OsmPrimitive> boolean mergeById(
    229             Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWithID, P other) {
     214            Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWithDefinedIds, P other) {
    230215
    231216        // merge other into an existing primitive with the same id, if possible
    232217        //
    233         if (myPrimitivesWithID.containsKey(other.id)) {
    234             P my = myPrimitivesWithID.get(other.id);
    235             if (my.realEqual(other, true /* compare semantic fields only */)) {
    236                 // make sure the merge target becomes the higher version number
    237                 // and the later timestamp
    238                 //
    239                 my.version = Math.max(other.version, my.version);
    240                 if (other.getTimestamp().after(my.getTimestamp())) {
    241                     my.setTimestamp(other.getTimestamp());
     218        if (myPrimitivesWithDefinedIds.containsKey(other.id)) {
     219            P my = myPrimitivesWithDefinedIds.get(other.id);
     220            if (my.version <= other.version) {
     221                if (! my.visible && other.visible) {
     222                    // should not happen
     223                    //
     224                    logger.warning(tr("My primitive with id {0} and version {1} is visible although "
     225                            + "their primitive with lower version {2} is not visible. "
     226                            + "Can't deal with this inconsistency. Keeping my primitive. ",
     227                            Long.toString(my.id),Long.toString(my.version), Long.toString(other.version)
     228                    ));
     229                    merged.put(other, my);
     230                } else if (my.visible && ! other.visible) {
     231                    // this is always a conflict because the user has to decide whether
     232                    // he wants to create a clone of its local primitive or whether he
     233                    // wants to purge my from the local dataset. He can't keep it unchanged
     234                    // because it was deleted on the server.
     235                    //
     236                    conflicts.put(my,other);
     237                } else if (! my.modified && other.modified) {
     238                    // my not modified. We can assume that other is the most recent version.
     239                    // clone it onto my. But check first, whether other is deleted. if so,
     240                    // make sure that my is not references anymore in myDataSet.
     241                    //
     242                    if (other.deleted) {
     243                        myDataSet.unlinkReferencesToPrimitive(my);
     244                    }
     245                    my.cloneFrom(other);
     246                    merged.put(other, my);
     247                } else if (! my.modified && !other.modified) {
     248                    // nothing to merge
     249                    //
     250                    merged.put(other,my);
     251                } else if (my.deleted != other.deleted) {
     252                    // if we get here my is modified. Differences in deleted state
     253                    // have to be resolved manually
     254                    //
     255                    conflicts.put(my,other);
     256                } else if (! my.hasEqualSemanticAttributes(other)) {
     257                    // my is modified and is not semantically equal with other. Can't automatically
     258                    // resolve the differences
     259                    // =>  create a conflict
     260                    conflicts.put(my,other);
     261                } else {
     262                    // clone from other, but keep the modified flag. Clone will mainly copy
     263                    // technical attributes like timestamp or user information. Semantic
     264                    // attributes should already be equal if we get here.
     265                    //
     266                    my.cloneFrom(other);
     267                    my.modified = true;
     268                    merged.put(other, my);
    242269                }
     270            } else {
     271                // my.version > other.version => keep my version
    243272                merged.put(other, my);
    244                 return true;
    245             }
    246         }
    247 
    248         // try to merge into one of the existing primitives
    249         //
    250         for (P my : myPrimitives) {
    251             if (my.realEqual(other, false /* compare all fields */)) {
    252                 merged.put(other, my);
    253                 return true; // no merge needed.
    254             }
    255             if (my.realEqual(other, true)) {
    256                 // they differ in modified/version combination only. Auto-resolve it.
    257                 merged.put(other, my);
    258                 if (my.version < other.version) {
    259                     my.version = other.version;
    260                     my.modified = other.modified;
    261                     my.setTimestamp(other.getTimestamp());
    262                 }
    263                 return true; // merge done.
    264             }
    265             if (my.id == other.id && my.id != 0) {
    266                 if (my.incomplete || other.incomplete) {
    267                     if (my.incomplete) {
    268                         my.cloneFrom(other);
    269                     }
    270                 } else if (my.modified && other.modified) {
    271                     conflicts.put(my, other);
    272                 } else if (!my.modified && !other.modified) {
    273                     if (my.version < other.version) {
    274                         my.cloneFrom(other);
    275                     }
    276                 } else if (other.modified) {
    277                     if (my.version > other.version) {
    278                         conflicts.put(my, other);
    279                     } else {
    280                         my.cloneFrom(other);
    281                     }
    282                 } else if (my.modified) {
    283                     if (my.version < other.version) {
    284                         conflicts.put(my, other);
    285                     }
    286                 }
    287                 merged.put(other, my);
    288                 return true;
    289             }
     273            }
     274            return true;
    290275        }
    291276        return false;
    292277    }
     278
     279
     280    /**
     281     * Runs the merge operation. Successfully merged {@see OsmPrimitive}s are in
     282     * {@see #getMyDataSet()}.
     283     *
     284     * See {@see #getConflicts()} for a map of conflicts after the merge operation.
     285     */
     286    public void merge() {
     287        for (final OsmPrimitive primitive : theirDataSet.allPrimitives()) {
     288            primitive.visit(this);
     289        }
     290        fixReferences();
     291    }
     292
     293    /**
     294     * replies my dataset
     295     *
     296     * @return
     297     */
     298    public DataSet getMyDataSet() {
     299        return myDataSet;
     300    }
     301
     302
     303    /**
     304     * replies the map of conflicts
     305     *
     306     * @return the map of conflicts
     307     */
     308    public Map<OsmPrimitive, OsmPrimitive> getConflicts() {
     309        return conflicts;
     310    }
    293311}
  • trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java

    r1654 r1690  
    2323import org.openstreetmap.josm.gui.conflict.nodes.NodeListMergeModel;
    2424import org.openstreetmap.josm.gui.conflict.nodes.NodeListMerger;
     25import org.openstreetmap.josm.gui.conflict.properties.OperationCancelledException;
    2526import org.openstreetmap.josm.gui.conflict.properties.PropertiesMergeModel;
    2627import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger;
     
    145146        this.their =  their;
    146147        propertiesMerger.getModel().populate(my, their);
     148        if (propertiesMerger.getModel().hasVisibleStateConflict()) {
     149            tabbedPane.setEnabledAt(1, false);
     150            tabbedPane.setEnabledAt(2, false);
     151            tabbedPane.setEnabledAt(3, false);
     152            return;
     153        }
    147154        tabbedPane.setEnabledAt(0, true);
    148155        tagMerger.getModel().populate(my, their);
     
    165172            tabbedPane.setEnabledAt(3, true);
    166173        }
     174
    167175    }
    168176
     
    173181     * @return the resolution command
    174182     */
    175     public Command buildResolveCommand() {
     183    public Command buildResolveCommand() throws OperationCancelledException {
    176184        ArrayList<Command> commands = new ArrayList<Command>();
    177         if (tagMerger.getModel().getNumResolvedConflicts() > 0) {
    178             commands.add(tagMerger.getModel().buildResolveCommand(my, their));
    179         }
    180         commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their));
    181         if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
    182             NodeListMergeModel model  =(NodeListMergeModel)nodeListMerger.getModel();
    183             commands.add(model.buildResolveCommand((Way)my, (Way)their));
    184         } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) {
    185             RelationMemberListMergeModel model  =(RelationMemberListMergeModel)relationMemberMerger.getModel();
    186             commands.add(model.buildResolveCommand((Relation)my, (Relation)their));
    187         }
    188         if (isResolvedCompletely()) {
    189             commands.add(
    190                     new VersionConflictResolveCommand(my, their)
    191             );
     185        if (propertiesMerger.getModel().hasVisibleStateConflict()) {
     186            if (propertiesMerger.getModel().isDecidedVisibleState()) {
     187                commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their));
     188            }
     189        } else {
     190            if (tagMerger.getModel().getNumResolvedConflicts() > 0) {
     191                commands.add(tagMerger.getModel().buildResolveCommand(my, their));
     192            }
     193            commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their));
     194            if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
     195                NodeListMergeModel model  =(NodeListMergeModel)nodeListMerger.getModel();
     196                commands.add(model.buildResolveCommand((Way)my, (Way)their));
     197            } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) {
     198                RelationMemberListMergeModel model  =(RelationMemberListMergeModel)relationMemberMerger.getModel();
     199                commands.add(model.buildResolveCommand((Relation)my, (Relation)their));
     200            }
     201            if (isResolvedCompletely()) {
     202                commands.add(
     203                        new VersionConflictResolveCommand(my, their)
     204                );
     205            }
    192206        }
    193207        return new SequenceCommand(tr("Conflict Resolution"), commands);
  • trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java

    r1669 r1690  
    33
    44import static org.openstreetmap.josm.gui.conflict.MergeDecisionType.UNDECIDED;
     5import static org.openstreetmap.josm.tools.I18n.tr;
    56
    67import java.beans.PropertyChangeListener;
    78import java.beans.PropertyChangeSupport;
     9import java.io.IOException;
     10import java.net.HttpURLConnection;
    811import java.util.ArrayList;
     12import java.util.HashMap;
    913import java.util.List;
    1014import java.util.Observable;
    1115
     16import javax.swing.JOptionPane;
     17import javax.swing.text.html.HTML;
     18
     19import org.openstreetmap.josm.Main;
    1220import org.openstreetmap.josm.command.Command;
    1321import org.openstreetmap.josm.command.CoordinateConflictResolveCommand;
    1422import org.openstreetmap.josm.command.DeletedStateConflictResolveCommand;
     23import org.openstreetmap.josm.command.PurgePrimitivesCommand;
     24import org.openstreetmap.josm.command.UndeletePrimitivesCommand;
    1525import org.openstreetmap.josm.data.coor.LatLon;
     26import org.openstreetmap.josm.data.osm.DataSet;
    1627import org.openstreetmap.josm.data.osm.Node;
    1728import org.openstreetmap.josm.data.osm.OsmPrimitive;
     29import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     30import org.openstreetmap.josm.data.osm.Relation;
     31import org.openstreetmap.josm.data.osm.RelationMember;
     32import org.openstreetmap.josm.data.osm.Way;
     33import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    1834import org.openstreetmap.josm.gui.conflict.MergeDecisionType;
     35import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger.KeepMyVisibleStateAction;
     36import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
     37import org.openstreetmap.josm.io.OsmApi;
     38import org.openstreetmap.josm.io.OsmApiException;
     39import org.openstreetmap.josm.io.OsmServerObjectReader;
     40import org.openstreetmap.josm.io.OsmTransferException;
     41import org.xml.sax.SAXException;
    1942
    2043/**
    2144 * This is the model for resolving conflicts in the properties of the
    2245 * {@see OsmPrimitive}s. In particular, it represents conflicts in the coordiates of {@see Node}s and
    23  * the deleted state of {@see OsmPrimitive}s.
     46 * the deleted or visible state of {@see OsmPrimitive}s.
    2447 *
    2548 * This model is an {@see Observable}. It notifies registered {@see Observer}s whenever the
     
    3154 * @see Node#getCoor()
    3255 * @see OsmPrimitive#deleted
     56 * @see OsmPrimitive#visible
    3357 *
    3458 */
     
    3761    static public final String RESOLVED_COMPLETELY_PROP = PropertiesMergeModel.class.getName() + ".resolvedCompletely";
    3862
     63    private OsmPrimitive my;
     64
    3965    private LatLon myCoords;
    4066    private LatLon theirCoords;
     
    4369    private boolean myDeletedState;
    4470    private boolean theirDeletedState;
     71    private boolean myVisibleState;
     72    private boolean theirVisibleState;
    4573    private MergeDecisionType deletedMergeDecision;
     74    private MergeDecisionType visibleMergeDecision;
    4675    private final PropertyChangeSupport support;
    4776    private boolean resolvedCompletely;
     
    91120
    92121    /**
     122     * replies true if there is a  conflict in the visible state and if this conflict is
     123     * resolved
     124     *
     125     * @return true if there is a conflict in the visible state and if this conflict is
     126     * resolved; false, otherwise
     127     */
     128    public boolean isDecidedVisibleState() {
     129        return ! visibleMergeDecision.equals(UNDECIDED);
     130    }
     131
     132    /**
    93133     * replies true if the current decision for the coordinate conflict is <code>decision</code>
    94134     *
     
    111151
    112152    /**
     153     * replies true if the current decision for the visible state conflict is <code>decision</code>
     154     *
     155     * @return true if the current decision for the visible state conflict is <code>decision</code>;
     156     *  false, otherwise
     157     */
     158    public boolean isVisibleStateDecision(MergeDecisionType decision) {
     159        return visibleMergeDecision.equals(decision);
     160    }
     161    /**
    113162     * populates the model with the differences between my and their version
    114163     *
     
    117166     */
    118167    public void populate(OsmPrimitive my, OsmPrimitive their) {
     168        this.my = my;
    119169        if (my instanceof Node) {
    120170            myCoords = ((Node)my).getCoor();
     
    128178        theirDeletedState = their.deleted;
    129179
     180        myVisibleState = my.visible;
     181        theirVisibleState = their.visible;
     182
    130183        coordMergeDecision = UNDECIDED;
    131184        deletedMergeDecision = UNDECIDED;
     185        visibleMergeDecision = UNDECIDED;
    132186        setChanged();
    133187        notifyObservers();
     
    209263    }
    210264
    211     public void decideDeletedStateConflict(MergeDecisionType decision) {
     265
     266    /**
     267     * replies my visible state,
     268     * @return my visible state
     269     */
     270    public Boolean getMyVisibleState() {
     271        return myVisibleState;
     272    }
     273
     274    /**
     275     * replies their visible state,
     276     * @return their visible state
     277     */
     278    public  Boolean getTheirVisibleState() {
     279        return theirVisibleState;
     280    }
     281
     282    /**
     283     * replies the merged visible state; null, if the merge decision is
     284     * {@see MergeDecisionType#UNDECIDED}.
     285     *
     286     * @return the merged visible state
     287     */
     288    public Boolean getMergedVisibleState() {
     289        switch(visibleMergeDecision) {
     290        case KEEP_MINE: return myVisibleState;
     291        case KEEP_THEIR: return theirVisibleState;
     292        case UNDECIDED: return null;
     293        }
     294        // should not happen
     295        return null;
     296    }
     297
     298    /**
     299     * decides the conflict between two deleted states
     300     * @param decision the decision (must not be null)
     301     *
     302     * @throws IllegalArgumentException thrown, if decision is null
     303     */
     304    public void decideDeletedStateConflict(MergeDecisionType decision) throws IllegalArgumentException{
     305        if (decision == null)
     306            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision"));
    212307        this.deletedMergeDecision = decision;
     308        setChanged();
     309        notifyObservers();
     310        fireCompletelyResolved();
     311    }
     312
     313    /**
     314     * decides the conflict between two visible states
     315     * @param decision the decision (must not be null)
     316     *
     317     * @throws IllegalArgumentException thrown, if decision is null
     318     */
     319    public void decideVisibleStateConflict(MergeDecisionType decision) throws IllegalArgumentException {
     320        if (decision == null)
     321            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision"));
     322        this.visibleMergeDecision = decision;
    213323        setChanged();
    214324        notifyObservers();
     
    242352
    243353    /**
     354     * replies true if my and their primitive have a conflict between
     355     * their visible states
     356     *
     357     * @return true if my and their primitive have a conflict between
     358     * their visible states
     359     */
     360    public boolean hasVisibleStateConflict() {
     361        return myVisibleState != theirVisibleState;
     362    }
     363
     364    /**
    244365     * replies true if all conflict in this model are resolved
    245366     *
     
    254375            ret = ret && ! deletedMergeDecision.equals(UNDECIDED);
    255376        }
     377        if (hasVisibleStateConflict()) {
     378            ret = ret && ! visibleMergeDecision.equals(UNDECIDED);
     379        }
    256380        return ret;
    257381    }
     
    264388     * @return the list of commands
    265389     */
    266     public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) {
     390    public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) throws OperationCancelledException{
    267391        ArrayList<Command> cmds = new ArrayList<Command>();
     392        if (hasVisibleStateConflict() && isDecidedVisibleState()) {
     393            if (isVisibleStateDecision(MergeDecisionType.KEEP_MINE)) {
     394                try {
     395                    UndeletePrimitivesCommand cmd = createUndeletePrimitiveCommand(my);
     396                    if (cmd == null)
     397                        throw new OperationCancelledException();
     398                    cmds.add(cmd);
     399                } catch(OsmTransferException e) {
     400                    handleExceptionWhileBuildingCommand(e);
     401                    throw new OperationCancelledException(e);
     402                }
     403            } else if (isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)) {
     404                cmds.add(new PurgePrimitivesCommand(my));
     405            }
     406        }
    268407        if (hasCoordConflict() && isDecidedCoord()) {
    269408            cmds.add(new CoordinateConflictResolveCommand((Node)my, (Node)their, coordMergeDecision));
     
    274413        return cmds;
    275414    }
     415
     416    public OsmPrimitive getMyPrimitive() {
     417        return my;
     418    }
     419
     420    /**
     421     *
     422     * @param id
     423     */
     424    protected void handleExceptionWhileBuildingCommand(Exception e) {
     425        e.printStackTrace();
     426        String msg = e.getMessage() != null ? e.getMessage() : e.toString();
     427        msg = msg.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
     428        JOptionPane.showMessageDialog(
     429                Main.parent,
     430                tr("<html>An error occurred while communicating with the server<br>"
     431                        + "Details: {0}</html>",
     432                        msg
     433                ),
     434                tr("Communication with server failed"),
     435                JOptionPane.ERROR_MESSAGE
     436        );
     437    }
     438
     439    /**
     440     * User has decided to keep his local version of a primitive which had been deleted
     441     * on the server
     442     *
     443     * @param id the primitive id
     444     */
     445    protected UndeletePrimitivesCommand createUndeletePrimitiveCommand(OsmPrimitive my) throws OsmTransferException {
     446        if (my instanceof Node)
     447            return createUndeleteNodeCommand((Node)my);
     448        else if (my instanceof Way)
     449            return createUndeleteWayCommand((Way)my);
     450        else if (my instanceof Relation)
     451            return createUndeleteRelationCommand((Relation)my);
     452        return null;
     453    }
     454    /**
     455     * Undelete a node which is already deleted on the server. The API
     456     * doesn't offer a call for "undeleting" a node. We therefore create
     457     * a clone of the node which we flag as new. On the next upload the
     458     * server will assign the node a new id.
     459     *
     460     * @param node the node to undelete
     461     */
     462    protected UndeletePrimitivesCommand  createUndeleteNodeCommand(Node node) {
     463        return new UndeletePrimitivesCommand(node);
     464    }
     465
     466    /**
     467     * displays a confirmation message. The user has to confirm that additional dependent
     468     * nodes should be undeleted too.
     469     *
     470     * @param way  the way
     471     * @param dependent a list of dependent nodes which have to be undelete too
     472     * @return true, if the user confirms; false, otherwise
     473     */
     474    protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) {
     475        String [] options = {
     476                tr("Yes, undelete them too"),
     477                tr("No, cancel operation")
     478        };
     479        int ret = JOptionPane.showOptionDialog(
     480                Main.parent,
     481                tr("<html>There are {0} additional nodes used by way {1}<br>"
     482                        + "which are deleted on the server.<br>"
     483                        + "<br>"
     484                        + "Do you want to undelete these nodes too?</html>",
     485                        Long.toString(dependent.size()), Long.toString(way.id)),
     486                        tr("Undelete additional nodes?"),
     487                        JOptionPane.YES_NO_OPTION,
     488                        JOptionPane.QUESTION_MESSAGE,
     489                        null,
     490                        options,
     491                        options[0]
     492        );
     493
     494        switch(ret) {
     495        case JOptionPane.CLOSED_OPTION: return false;
     496        case JOptionPane.YES_OPTION: return true;
     497        case JOptionPane.NO_OPTION: return false;
     498        }
     499        return false;
     500
     501    }
     502
     503    protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) {
     504        String [] options = {
     505                tr("Yes, undelete them too"),
     506                tr("No, cancel operation")
     507        };
     508        int ret = JOptionPane.showOptionDialog(
     509                Main.parent,
     510                tr("<html>There are {0} additional primitives referred to by relation {1}<br>"
     511                        + "which are deleted on the server.<br>"
     512                        + "<br>"
     513                        + "Do you want to undelete them too?</html>",
     514                        Long.toString(dependent.size()), Long.toString(r.id)),
     515                        tr("Undelete dependent primitives?"),
     516                        JOptionPane.YES_NO_OPTION,
     517                        JOptionPane.QUESTION_MESSAGE,
     518                        null,
     519                        options,
     520                        options[0]
     521        );
     522
     523        switch(ret) {
     524        case JOptionPane.CLOSED_OPTION: return false;
     525        case JOptionPane.YES_OPTION: return true;
     526        case JOptionPane.NO_OPTION: return false;
     527        }
     528        return false;
     529
     530    }
     531
     532    /**
     533     * Creates the undelete command for a way which is already deleted on the server.
     534     *
     535     * This method also checks whether there are additional nodes referred to by
     536     * this way which are deleted on the server too.
     537     *
     538     * @param way the way to undelete
     539     * @return the undelete command
     540     * @see #createUndeleteNodeCommand(Node)
     541     */
     542    protected UndeletePrimitivesCommand createUndeleteWayCommand(final Way way) throws OsmTransferException {
     543
     544        HashMap<Long,OsmPrimitive> candidates = new HashMap<Long,OsmPrimitive>();
     545        for (Node n : way.nodes) {
     546            if (n.id > 0 && ! candidates.values().contains(n)) {
     547                candidates.put(n.id, n);
     548            }
     549        }
     550        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
     551        reader.append(candidates.values());
     552        DataSet ds = reader.parseOsm();
     553
     554        ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
     555        for (OsmPrimitive their : ds.allPrimitives()) {
     556            if (candidates.keySet().contains(their.id) && ! their.visible) {
     557                toDelete.add(candidates.get(their.id));
     558            }
     559        }
     560        if (!toDelete.isEmpty()) {
     561            if (! confirmUndeleteDependentPrimitives(way, toDelete))
     562                // FIXME: throw exception ?
     563                return null;
     564        }
     565        toDelete.add(way);
     566        return new UndeletePrimitivesCommand(toDelete);
     567    }
     568
     569    /**
     570     * Creates an undelete command for a relation which is already deleted on the server.
     571     *
     572     * This method  checks whether there are additional primitives referred to by
     573     * this relation which are already deleted on the server.
     574     *
     575     * @param r the relation
     576     * @return the undelete command
     577     * @see #createUndeleteNodeCommand(Node)
     578     */
     579    protected UndeletePrimitivesCommand createUndeleteRelationCommand(final Relation r) throws OsmTransferException {
     580
     581        HashMap<Long,OsmPrimitive> candidates = new HashMap<Long, OsmPrimitive>();
     582        for (RelationMember m : r.members) {
     583            if (m.member.id > 0 && !candidates.values().contains(m.member)) {
     584                candidates.put(m.member.id,m.member);
     585            }
     586        }
     587
     588        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
     589        reader.append(candidates.values());
     590        DataSet ds = reader.parseOsm();
     591
     592        ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
     593        for (OsmPrimitive their : ds.allPrimitives()) {
     594            if (candidates.keySet().contains(their.id) && ! their.visible) {
     595                toDelete.add(candidates.get(their.id));
     596            }
     597        }
     598        if (!toDelete.isEmpty()) {
     599            if (! confirmUndeleteDependentPrimitives(r, toDelete))
     600                // FIXME: throw exception ?
     601                return null;
     602        }
     603        toDelete.add(r);
     604        return new UndeletePrimitivesCommand(toDelete);
     605    }
     606
    276607}
  • trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMerger.java

    r1676 r1690  
    1818import javax.swing.JButton;
    1919import javax.swing.JLabel;
     20import javax.swing.JOptionPane;
    2021import javax.swing.JPanel;
    21 
     22import javax.swing.SwingUtilities;
     23
     24import org.openstreetmap.josm.Main;
    2225import org.openstreetmap.josm.data.coor.LatLon;
     26import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    2327import org.openstreetmap.josm.gui.conflict.MergeDecisionType;
    2428import org.openstreetmap.josm.tools.ImageProvider;
     
    4852    private JLabel lblTheirDeletedState;
    4953
     54    private JLabel lblMyVisibleState;
     55    private JLabel lblMergedVisibleState;
     56    private JLabel lblTheirVisibleState;
     57
    5058    private final PropertiesMergeModel model;
    5159
     
    5967    }
    6068
    61     protected void build() {
    62         setLayout(new GridBagLayout());
     69    protected void buildHeaderRow() {
    6370        GridBagConstraints gc = new GridBagConstraints();
    6471
    65         // ------------------
    6672        gc.gridx = 1;
    6773        gc.gridy = 0;
     
    8894        lblTheirVersion.setToolTipText(tr("Properties in their dataset, i.e. the server dataset"));
    8995        add(lblTheirVersion, gc);
    90 
    91         // --------------------------------
     96    }
     97
     98    protected void buildCoordinateConflictRows() {
     99        GridBagConstraints gc = new GridBagConstraints();
     100
    92101        gc.gridx = 0;
    93102        gc.gridy = 1;
     
    161170        JButton btnUndecideCoordinates = new JButton(actUndecideCoordinates);
    162171        add(btnUndecideCoordinates, gc);
    163         // ---------------------------------------------------
     172    }
     173
     174    protected void buildDeletedStateConflictRows() {
     175        GridBagConstraints gc = new GridBagConstraints();
    164176
    165177        gc.gridx = 0;
     
    191203        model.addObserver(actKeepMyDeletedState);
    192204        JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState);
    193         btnKeepMyCoordinates.setName("button.keepmydeletedstate");
     205        btnKeepMyDeletedState.setName("button.keepmydeletedstate");
    194206        add(btnKeepMyDeletedState, gc);
    195207
     
    212224        model.addObserver(actKeepTheirDeletedState);
    213225        JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState);
    214         btnKeepMyCoordinates.setName("button.keeptheirdeletedstate");
     226        btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate");
    215227        add(btnKeepTheirDeletedState, gc);
    216228
     
    233245        model.addObserver(actUndecideDeletedState);
    234246        JButton btnUndecideDeletedState = new JButton(actUndecideDeletedState);
    235         btnKeepMyCoordinates.setName("button.undecidedeletedstate");
     247        btnUndecideDeletedState.setName("button.undecidedeletedstate");
    236248        add(btnUndecideDeletedState, gc);
    237 
     249    }
     250
     251    protected void buildVisibleStateRows() {
     252        GridBagConstraints gc = new GridBagConstraints();
     253
     254        gc.gridx = 0;
     255        gc.gridy = 5;
     256        gc.gridwidth = 1;
     257        gc.gridheight = 1;
     258        gc.fill = GridBagConstraints.BOTH;
     259        gc.anchor = GridBagConstraints.LINE_START;
     260        gc.weightx = 0.0;
     261        gc.weighty = 0.0;
     262        gc.insets = new Insets(0,5,0,5);
     263        add(new JLabel(tr("Visible State:")), gc);
     264
     265        gc.gridx = 1;
     266        gc.gridy = 5;
     267        gc.fill = GridBagConstraints.BOTH;
     268        gc.anchor = GridBagConstraints.CENTER;
     269        gc.weightx = 0.33;
     270        gc.weighty = 0.0;
     271        add(lblMyVisibleState = buildValueLabel("label.myvisiblestate"), gc);
     272
     273        gc.gridx = 2;
     274        gc.gridy = 5;
     275        gc.fill = GridBagConstraints.NONE;
     276        gc.anchor = GridBagConstraints.CENTER;
     277        gc.weightx = 0.0;
     278        gc.weighty = 0.0;
     279        KeepMyVisibleStateAction actKeepMyVisibleState = new KeepMyVisibleStateAction();
     280        model.addObserver(actKeepMyVisibleState);
     281        JButton btnKeepMyVisibleState = new JButton(actKeepMyVisibleState);
     282        btnKeepMyVisibleState.setName("button.keepmyvisiblestate");
     283        add(btnKeepMyVisibleState, gc);
     284
     285        gc.gridx = 3;
     286        gc.gridy = 5;
     287        gc.fill = GridBagConstraints.BOTH;
     288        gc.anchor = GridBagConstraints.CENTER;
     289        gc.weightx = 0.33;
     290        gc.weighty = 0.0;
     291        add(lblMergedVisibleState = buildValueLabel("label.mergedvisiblestate"), gc);
     292
     293        gc.gridx = 4;
     294        gc.gridy = 5;
     295        gc.fill = GridBagConstraints.NONE;
     296        gc.anchor = GridBagConstraints.CENTER;
     297        gc.weightx = 0.0;
     298        gc.weighty = 0.0;
     299        KeepTheirVisibleStateAction actKeepTheirVisibleState = new KeepTheirVisibleStateAction();
     300        model.addObserver(actKeepTheirVisibleState);
     301        JButton btnKeepTheirVisibleState = new JButton(actKeepTheirVisibleState);
     302        btnKeepTheirVisibleState.setName("button.keeptheirvisiblestate");
     303        add(btnKeepTheirVisibleState, gc);
     304
     305        gc.gridx = 5;
     306        gc.gridy = 5;
     307        gc.fill = GridBagConstraints.BOTH;
     308        gc.anchor = GridBagConstraints.CENTER;
     309        gc.weightx = 0.33;
     310        gc.weighty = 0.0;
     311        add(lblTheirVisibleState = buildValueLabel("label.theirvisiblestate"), gc);
     312
     313        // ---------------------------------------------------
     314        gc.gridx = 3;
     315        gc.gridy = 6;
     316        gc.fill = GridBagConstraints.NONE;
     317        gc.anchor = GridBagConstraints.CENTER;
     318        gc.weightx = 0.0;
     319        gc.weighty = 0.0;
     320        UndecideVisibleStateConflictAction actUndecideVisibleState = new UndecideVisibleStateConflictAction();
     321        model.addObserver(actUndecideVisibleState);
     322        JButton btnUndecideVisibleState = new JButton(actUndecideVisibleState);
     323        btnUndecideVisibleState.setName("button.undecidevisiblestate");
     324        add(btnUndecideVisibleState, gc);
     325    }
     326
     327    protected void build() {
     328        setLayout(new GridBagLayout());
     329        buildHeaderRow();
     330        buildCoordinateConflictRows();
     331        buildDeletedStateConflictRows();
     332        buildVisibleStateRows();
    238333    }
    239334
     
    265360    }
    266361
    267     protected void updateCoordiates() {
     362    public String visibleStateToString(Boolean visible) {
     363        if (visible == null)
     364            return tr("(none)");
     365        if (visible)
     366            return tr("visible (on the server)");
     367        else
     368            return tr("not visible (on the server)");
     369    }
     370
     371    public String visibleStateToStringMerged(Boolean visible) {
     372        if (visible == null)
     373            return tr("(none)");
     374        if (visible)
     375            return tr("Keep a clone of the local version");
     376        else
     377            return tr("Physically delete from local dataset");
     378    }
     379
     380    protected void updateCoordinates() {
    268381        lblMyCoordinates.setText(coordToString(model.getMyCoords()));
    269382        lblMergedCoordinates.setText(coordToString(model.getMergedCoords()));
     
    320433    }
    321434
     435    protected void updateVisibleState() {
     436        lblMyVisibleState.setText(visibleStateToString(model.getMyVisibleState()));
     437        lblMergedVisibleState.setText(visibleStateToStringMerged(model.getMergedVisibleState()));
     438        lblTheirVisibleState.setText(visibleStateToString(model.getTheirVisibleState()));
     439
     440        if (! model.hasVisibleStateConflict()) {
     441            lblMyVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
     442            lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
     443            lblTheirVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
     444        } else {
     445            if (!model.isDecidedVisibleState()) {
     446                lblMyVisibleState.setBackground(BGCOLOR_UNDECIDED);
     447                lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
     448                lblTheirVisibleState.setBackground(BGCOLOR_UNDECIDED);
     449            } else {
     450                lblMyVisibleState.setBackground(
     451                        model.isVisibleStateDecision(MergeDecisionType.KEEP_MINE)
     452                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
     453                );
     454                lblMergedVisibleState.setBackground(BGCOLOR_DECIDED);
     455                lblTheirVisibleState.setBackground(
     456                        model.isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)
     457                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
     458                );
     459            }
     460        }
     461    }
     462
    322463    public void update(Observable o, Object arg) {
    323         updateCoordiates();
     464        updateCoordinates();
    324465        updateDeletedState();
     466        updateVisibleState();
    325467    }
    326468
     
    418560        }
    419561    }
     562
     563    class KeepMyVisibleStateAction extends AbstractAction implements Observer {
     564        public KeepMyVisibleStateAction() {
     565            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
     566            putValue(Action.SHORT_DESCRIPTION, tr("Keep my visible state"));
     567        }
     568
     569        public void actionPerformed(ActionEvent e) {
     570            if (confirmKeepMine()) {
     571                model.decideVisibleStateConflict(MergeDecisionType.KEEP_MINE);
     572            }
     573        }
     574
     575        public void update(Observable o, Object arg) {
     576            setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState());
     577        }
     578
     579        protected boolean confirmKeepMine() {
     580            String [] options = {
     581                    tr("Yes, reset the id"),
     582                    tr("No, abort")
     583            };
     584            int ret = JOptionPane.showOptionDialog(
     585                    null,
     586                    tr("<html>To keep your local version, JOSM<br>"
     587                            + "has to reset the id of {0} {1} to 0.<br>"
     588                            + "On the next upload the server will assign<br>"
     589                            + "it a new id.<br>"
     590                            + "Do yo agree?</html>",
     591                            OsmPrimitiveType.from(model.getMyPrimitive()).getLocalizedDisplayNamePlural(),
     592                            model.getMyPrimitive().id
     593                    ),
     594                    tr("Reset id to 0"),
     595                    JOptionPane.YES_NO_OPTION,
     596                    JOptionPane.QUESTION_MESSAGE,
     597                    null,
     598                    options,
     599                    options[1]
     600            );
     601            return ret == JOptionPane.YES_OPTION;
     602        }
     603    }
     604
     605    class KeepTheirVisibleStateAction extends AbstractAction implements Observer {
     606        public KeepTheirVisibleStateAction() {
     607            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
     608            putValue(Action.SHORT_DESCRIPTION, tr("Keep their visible state"));
     609        }
     610
     611        public void actionPerformed(ActionEvent e) {
     612            if (confirmKeepTheir()){
     613                model.decideVisibleStateConflict(MergeDecisionType.KEEP_THEIR);
     614            }
     615        }
     616
     617        public void update(Observable o, Object arg) {
     618            setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState());
     619        }
     620
     621        protected boolean confirmKeepTheir() {
     622            String [] options = {
     623                    tr("Yes, purge it"),
     624                    tr("No, abort")
     625            };
     626            int ret = JOptionPane.showOptionDialog(
     627                    null,
     628                    tr("<html>JOSM will have to remove your local primitive with id {0}<br>"
     629                            + "from the dataset.<br>"
     630                            + "Do you agree?</html>",
     631                            model.getMyPrimitive().id
     632                    ),
     633                    tr("Remove from dataset"),
     634                    JOptionPane.YES_NO_OPTION,
     635                    JOptionPane.QUESTION_MESSAGE,
     636                    null,
     637                    options,
     638                    options[1]
     639            );
     640            return ret == JOptionPane.YES_OPTION;
     641        }
     642    }
     643
     644    class UndecideVisibleStateConflictAction extends AbstractAction implements Observer {
     645        public UndecideVisibleStateConflictAction() {
     646            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
     647            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between visible state"));
     648        }
     649
     650        public void actionPerformed(ActionEvent e) {
     651            model.decideVisibleStateConflict(MergeDecisionType.UNDECIDED);
     652        }
     653
     654        public void update(Observable o, Object arg) {
     655            setEnabled(model.hasVisibleStateConflict() && model.isDecidedVisibleState());
     656        }
     657    }
    420658}
  • trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java

    r1677 r1690  
    2323import org.openstreetmap.josm.command.Command;
    2424import org.openstreetmap.josm.gui.conflict.ConflictResolver;
     25import org.openstreetmap.josm.gui.conflict.properties.OperationCancelledException;
    2526import org.openstreetmap.josm.tools.ImageProvider;
    2627
     
    190191                    return;
    191192            }
    192             Command cmd = resolver.buildResolveCommand();
    193             Main.main.undoRedo.add(cmd);
     193            try {
     194                Command cmd = resolver.buildResolveCommand();
     195                Main.main.undoRedo.add(cmd);
     196            } catch(OperationCancelledException e) {
     197                // do nothing. Exception already reported
     198            }
    194199            setVisible(false);
    195200        }
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

    r1670 r1690  
    527527                    final MergeVisitor visitor = new MergeVisitor(Main.main
    528528                            .editLayer().data, dataSet);
    529                     for (final OsmPrimitive osm : dataSet.allPrimitives()) {
    530                         osm.visit(visitor);
    531                     }
    532                     visitor.fixReferences();
     529                    visitor.merge();
    533530
    534531                    // copy the merged layer's data source info
     
    538535                    Main.main.editLayer().fireDataChange();
    539536
    540                     if (visitor.conflicts.isEmpty())
     537                    if (visitor.getConflicts().isEmpty())
    541538                        return;
    542539                    final ConflictDialog dlg = Main.map.conflictDialog;
    543                     dlg.add(visitor.conflicts);
     540                    dlg.add(visitor.getConflicts());
    544541                    JOptionPane.showMessageDialog(Main.parent,
    545542                            tr("There were conflicts during import."));
  • trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    r1677 r1690  
    245245    public void mergeFrom(final DataSet from) {
    246246        final MergeVisitor visitor = new MergeVisitor(data,from);
    247         for (final OsmPrimitive osm : from.allPrimitives()) {
    248             osm.visit(visitor);
    249         }
    250         visitor.fixReferences();
     247        visitor.merge();
    251248
    252249        Area a = data.getDataSourceArea();
     
    274271        Main.map.mapView.repaint();
    275272
    276         if (visitor.conflicts.isEmpty())
     273        if (visitor.getConflicts().isEmpty())
    277274            return;
    278275        final ConflictDialog dlg = Main.map.conflictDialog;
    279         dlg.add(visitor.conflicts);
    280         JOptionPane.showMessageDialog(Main.parent,tr("There were {0} conflicts during import.", visitor.conflicts.size()));
     276        dlg.add(visitor.getConflicts());
     277        JOptionPane.showMessageDialog(Main.parent,tr("There were {0} conflicts during import.", visitor.getConflicts().size()));
    281278        if (!dlg.isVisible()) {
    282279            dlg.action.actionPerformed(new ActionEvent(this, 0, ""));
  • trunk/src/org/openstreetmap/josm/io/OsmApi.java

    r1688 r1690  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.EventQueue;
    67import java.io.BufferedReader;
    78import java.io.BufferedWriter;
     
    323324        }
    324325        notifyStatusMessage(tr("Uploading..."));
     326        setAutoProgressIndication(true);
    325327
    326328        String diff = duv.getDocument();
     
    330332        } catch(Exception e) {
    331333            throw new OsmTransferException(e);
     334        } finally {
     335            setAutoProgressIndication(false);
    332336        }
    333337
     
    481485        Main.pleaseWaitDlg.progress.setValue(current + delta);
    482486    }
     487
     488
     489    protected void setAutoProgressIndication(final boolean enabled) {
     490        EventQueue.invokeLater(
     491                new Runnable() {
     492                    public void run() {
     493                        Main.pleaseWaitDlg.setIndeterminate(enabled);
     494                    }
     495                }
     496        );
     497    }
    483498}
  • trunk/src/org/openstreetmap/josm/io/OsmReader.java

    r1677 r1690  
    99import java.util.ArrayList;
    1010import java.util.Collection;
     11import java.util.Date;
    1112import java.util.HashMap;
     13import java.util.HashSet;
    1214import java.util.LinkedList;
    1315import java.util.Map;
     16import java.util.Set;
    1417import java.util.Map.Entry;
    1518
     
    2932import org.openstreetmap.josm.data.osm.Way;
    3033import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
    31 import org.openstreetmap.josm.data.osm.visitor.Visitor;
    3234import org.openstreetmap.josm.gui.PleaseWaitDialog;
    3335import org.openstreetmap.josm.tools.DateUtils;
     
    4951public class OsmReader {
    5052
    51 //     static long tagsN = 0;
    52 //     static long nodesN = 0;
    53 //     static long waysN = 0;
    54 //     static long relationsN = 0;
    55 //     static long membersN = 0;
    56 
    57      static InputStream currSource;
    58 
    59      /**
    60       * This is used as (readonly) source for finding missing references when not transferred in the
    61       * file.
    62       */
    63      private DataSet references;
    64 
    65      /**
    66       * The dataset to add parsed objects to.
    67       */
    68      private DataSet ds = new DataSet();
    69      public DataSet getDs() { return ds; }
    70 
    71      /**
    72       * Record warnings.  If there were any data inconsistencies, append
    73       * a newline-terminated string.
    74       */
    75      private String parseNotes = new String();
    76      private int parseNotesCount = 0;
    77      public String getParseNotes() {
    78          return parseNotes;
    79      }
    80 
    81      /**
    82       * The visitor to use to add the data to the set.
    83       */
    84      private AddVisitor adder = new AddVisitor(ds);
    85 
    86      /**
    87       * All read nodes after phase 1.
    88       */
    89      private Map<Long, Node> nodes = new HashMap<Long, Node>();
    90 
    91      // TODO: What the hack? Is this really from me? Please, clean this up!
    92      private static class OsmPrimitiveData extends OsmPrimitive {
    93           @Override public void visit(Visitor visitor) {}
    94           public int compareTo(OsmPrimitive o) {return 0;}
    95 
    96           public void copyTo(OsmPrimitive osm) {
    97                osm.id = id;
    98                osm.keys = keys;
    99                osm.modified = modified;
    100                osm.selected = selected;
    101                osm.deleted = deleted;
    102                osm.setTimestamp(getTimestamp());
    103                osm.user = user;
    104                osm.visible = visible;
    105                osm.version = version;
    106                osm.mappaintStyle = null;
    107           }
    108      }
    109 
    110      /**
    111       * Used as a temporary storage for relation members, before they
    112       * are resolved into pointers to real objects.
    113       */
    114      private static class RelationMemberData {
    115           public String type;
    116           public long id;
    117           public RelationMember relationMember;
    118      }
    119 
    120      /**
    121       * Data structure for the remaining way objects
    122       */
    123      private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
    124 
    125      /**
    126       * Data structure for relation objects
    127       */
    128      private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
    129 
    130      private class Parser extends DefaultHandler {
    131           /**
    132            * The current osm primitive to be read.
    133            */
    134           private OsmPrimitive current;
    135           private String generator;
    136           private Map<String, String> keys = new HashMap<String, String>();
    137 //          int n = 0;
    138 
    139           @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
    140                try {
    141 //                    if(n%100000 == 0) {
    142 //                        try {
    143 //                            FileInputStream fis = (FileInputStream)currSource;
    144 //                            FileChannel channel = fis.getChannel();
    145 //                            double perc = (((double)channel.position()) / ((double)channel.size()) * 100.0);
    146 //                            System.out.format(" " + (int)perc + "%%");
    147 //                        }
    148 //                        catch(java.lang.ClassCastException cce) {
    149 //                        }
    150 //                        catch(IOException e) {
    151 //                            System.out.format("Error reading file position " + e);
    152 //                        }
    153 //                    }
    154 //                    n++;
    155 
    156                     if (qName.equals("osm")) {
    157                          if (atts == null)
    158                               throw new SAXException(tr("Unknown version"));
    159                          String v = atts.getValue("version");
    160                          if (v == null)
    161                              throw new SAXException(tr("Version number missing from OSM data"));
    162                          if (!(v.equals("0.5") || v.equals("0.6")))
    163                              throw new SAXException(tr("Unknown version: {0}", v));
    164                          // save generator attribute for later use when creating DataSource objects
    165                          generator = atts.getValue("generator");
    166                          ds.version = v;
    167 
    168                     } else if (qName.equals("bounds")) {
    169                          // new style bounds.
    170                          String minlon = atts.getValue("minlon");
    171                          String minlat = atts.getValue("minlat");
    172                          String maxlon = atts.getValue("maxlon");
    173                          String maxlat = atts.getValue("maxlat");
    174                          String origin = atts.getValue("origin");
    175                          if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
    176                               if (origin == null) origin = generator;
    177                               Bounds bounds = new Bounds(
    178                                   new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
    179                                   new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
    180                               DataSource src = new DataSource(bounds, origin);
    181                               ds.dataSources.add(src);
    182                          }
     53    /**
     54     * This is used as (readonly) source for finding missing references when not transferred in the
     55     * file.
     56     */
     57    private DataSet references;
     58
     59    /**
     60     * The dataset to add parsed objects to.
     61     */
     62    private DataSet ds = new DataSet();
     63    public DataSet getDs() { return ds; }
     64
     65    /**
     66     * Record warnings.  If there were any data inconsistencies, append
     67     * a newline-terminated string.
     68     */
     69    private String parseNotes = new String();
     70    private int parseNotesCount = 0;
     71    public String getParseNotes() {
     72        return parseNotes;
     73    }
     74
     75    /** the list of ids of skipped {@see Way}s, i.e. ways which referred to nodes
     76     * not included in the parsed data
     77     */
     78    private Set<Long> skippedWayIds = new HashSet<Long>();
     79
     80    /**
     81     * The visitor to use to add the data to the set.
     82     */
     83    private AddVisitor adder = new AddVisitor(ds);
     84
     85    /**
     86     * All read nodes after phase 1.
     87     */
     88    private Map<Long, Node> nodes = new HashMap<Long, Node>();
     89
     90
     91    private static class OsmPrimitiveData {
     92        public long id = 0;
     93        public Map<String,String> keys = new HashMap<String, String>();
     94        public boolean modified = false;
     95        public boolean selected = false;
     96        public boolean deleted = false;
     97        public Date timestamp = new Date();
     98        public User user = null;
     99        public boolean visible = true;
     100        public int version = -1;
     101        public LatLon latlon = new LatLon(0,0);
     102
     103        public void copyTo(OsmPrimitive osm) {
     104            osm.id = id;
     105            osm.keys = keys;
     106            osm.modified = modified;
     107            osm.selected = selected;
     108            osm.deleted = deleted;
     109            osm.setTimestamp(timestamp);
     110            osm.user = user;
     111            osm.visible = visible;
     112            osm.version = version;
     113            osm.mappaintStyle = null;
     114        }
     115
     116        public Node createNode() {
     117            Node node = new Node(latlon);
     118            copyTo(node);
     119            return node;
     120        }
     121
     122        public Way createWay() {
     123            Way way = new Way(id);
     124            copyTo(way);
     125            return way;
     126        }
     127
     128        public Relation createRelation() {
     129            Relation rel = new Relation(id);
     130            copyTo(rel);
     131            return rel;
     132        }
     133    }
     134
     135    /**
     136     * Used as a temporary storage for relation members, before they
     137     * are resolved into pointers to real objects.
     138     */
     139    private static class RelationMemberData {
     140        public String type;
     141        public long id;
     142        public RelationMember relationMember;
     143    }
     144
     145    /**
     146     * Data structure for the remaining way objects
     147     */
     148    private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
     149
     150    /**
     151     * Data structure for relation objects
     152     */
     153    private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
     154
     155    private class Parser extends DefaultHandler {
     156        /**
     157         * The current osm primitive to be read.
     158         */
     159        private OsmPrimitiveData current;
     160        private String generator;
     161
     162        @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
     163            try {
     164                if (qName.equals("osm")) {
     165                    if (atts == null)
     166                        throw new SAXException(tr("Unknown version"));
     167                    String v = atts.getValue("version");
     168                    if (v == null)
     169                        throw new SAXException(tr("Version number missing from OSM data"));
     170                    if (!(v.equals("0.5") || v.equals("0.6")))
     171                        throw new SAXException(tr("Unknown version: {0}", v));
     172                    // save generator attribute for later use when creating DataSource objects
     173                    generator = atts.getValue("generator");
     174                    ds.version = v;
     175
     176                } else if (qName.equals("bounds")) {
     177                    // new style bounds.
     178                    String minlon = atts.getValue("minlon");
     179                    String minlat = atts.getValue("minlat");
     180                    String maxlon = atts.getValue("maxlon");
     181                    String maxlat = atts.getValue("maxlat");
     182                    String origin = atts.getValue("origin");
     183                    if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
     184                        if (origin == null) {
     185                            origin = generator;
     186                        }
     187                        Bounds bounds = new Bounds(
     188                                new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
     189                                new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
     190                        DataSource src = new DataSource(bounds, origin);
     191                        ds.dataSources.add(src);
     192                    }
    183193
    184194                    // ---- PARSING NODES AND WAYS ----
    185195
    186                     } else if (qName.equals("node")) {
    187 //                         nodesN++;
    188                          current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
    189                          readCommon(atts, current);
    190                          nodes.put(current.id, (Node)current);
    191                     } else if (qName.equals("way")) {
    192 //                         waysN++;
    193                          current = new OsmPrimitiveData();
    194                          readCommon(atts, current);
    195                          ways.put((OsmPrimitiveData)current, new ArrayList<Long>());
    196                     } else if (qName.equals("nd")) {
    197                          Collection<Long> list = ways.get(current);
    198                          if (list == null)
    199                               throw new SAXException(tr("Found <nd> element in non-way."));
    200                          long id = getLong(atts, "ref");
    201                          if (id == 0)
    202                               throw new SAXException(tr("<nd> has zero ref"));
    203                          list.add(id);
     196                } else if (qName.equals("node")) {
     197                    current = new OsmPrimitiveData();
     198                    current.latlon = new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon"));
     199                    readCommon(atts, current);
     200                } else if (qName.equals("way")) {
     201                    current = new OsmPrimitiveData();
     202                    readCommon(atts, current);
     203                    ways.put(current, new ArrayList<Long>());
     204                } else if (qName.equals("nd")) {
     205                    Collection<Long> list = ways.get(current);
     206                    if (list == null)
     207                        throw new SAXException(tr("Found <nd> element in non-way."));
     208                    long id = getLong(atts, "ref");
     209                    if (id == 0)
     210                        throw new SAXException(tr("<nd> has zero ref"));
     211                    list.add(id);
    204212
    205213                    // ---- PARSING RELATIONS ----
    206214
    207                     } else if (qName.equals("relation")) {
    208                          current = new OsmPrimitiveData();
    209                          readCommon(atts, current);
    210                          relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>());
    211                     } else if (qName.equals("member")) {
    212                          Collection<RelationMemberData> list = relations.get(current);
    213                          if (list == null)
    214                               throw new SAXException(tr("Found <member> element in non-relation."));
    215                          RelationMemberData emd = new RelationMemberData();
    216                          emd.relationMember = new RelationMember();
    217                          String value = atts.getValue("ref");
    218                          if (value == null) {
    219                              throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id));
    220                          }
    221                          try {
    222                              emd.id = Long.parseLong(value);
    223                          } catch(NumberFormatException e) {
    224                              throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value));
    225                          }
    226                          value = atts.getValue("type");
    227                          if (value == null) {
    228                              throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id)));
    229                          }
    230                          if (! (value.equals("way") || value.equals("node") || value.equals("relation"))) {
    231                              throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value));
    232                          }
    233                          emd.type= value;
    234                          value = atts.getValue("role");
    235                          emd.relationMember.role = value;
    236 
    237                          if (emd.id == 0)
    238                               throw new SAXException(tr("Incomplete <member> specification with ref=0"));
    239 
    240                          list.add(emd);
     215                } else if (qName.equals("relation")) {
     216                    current = new OsmPrimitiveData();
     217                    readCommon(atts, current);
     218                    relations.put(current, new LinkedList<RelationMemberData>());
     219                } else if (qName.equals("member")) {
     220                    Collection<RelationMemberData> list = relations.get(current);
     221                    if (list == null)
     222                        throw new SAXException(tr("Found <member> element in non-relation."));
     223                    RelationMemberData emd = new RelationMemberData();
     224                    emd.relationMember = new RelationMember();
     225                    String value = atts.getValue("ref");
     226                    if (value == null)
     227                        throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id));
     228                    try {
     229                        emd.id = Long.parseLong(value);
     230                    } catch(NumberFormatException e) {
     231                        throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value));
     232                    }
     233                    value = atts.getValue("type");
     234                    if (value == null)
     235                        throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id)));
     236                    if (! (value.equals("way") || value.equals("node") || value.equals("relation")))
     237                        throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value));
     238                    emd.type= value;
     239                    value = atts.getValue("role");
     240                    emd.relationMember.role = value;
     241
     242                    if (emd.id == 0)
     243                        throw new SAXException(tr("Incomplete <member> specification with ref=0"));
     244
     245                    list.add(emd);
    241246
    242247                    // ---- PARSING TAGS (applicable to all objects) ----
    243248
    244                     } else if (qName.equals("tag")) {
    245 //                         tagsN++;
    246                         String key = atts.getValue("k");
    247                         String internedKey = keys.get(key);
    248                         if (internedKey == null) {
    249                             internedKey = key;
    250                             keys.put(key, key);
    251                         }
    252                          current.put(internedKey, atts.getValue("v"));
    253                     }
    254                } catch (NumberFormatException x) {
    255                     x.printStackTrace(); // SAXException does not chain correctly
    256                     throw new SAXException(x.getMessage(), x);
    257                } catch (NullPointerException x) {
    258                     x.printStackTrace(); // SAXException does not chain correctly
    259                     throw new SAXException(tr("NullPointerException, possibly some missing tags."), x);
    260                }
    261           }
    262 
    263           private double getDouble(Attributes atts, String value) {
    264                return Double.parseDouble(atts.getValue(value));
    265           }
    266      }
    267 
    268      /**
    269       * Read out the common attributes from atts and put them into this.current.
    270       */
    271      void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
    272           current.id = getLong(atts, "id");
    273           if (current.id == 0)
    274                throw new SAXException(tr("Illegal object with id=0"));
    275 
    276           String time = atts.getValue("timestamp");
    277           if (time != null && time.length() != 0) {
    278                current.setTimestamp(DateUtils.fromString(time));
    279           }
    280 
    281           // user attribute added in 0.4 API
    282           String user = atts.getValue("user");
    283           if (user != null) {
    284                // do not store literally; get object reference for string
    285                current.user = User.get(user);
    286           }
    287 
    288           // uid attribute added in 0.6 API
    289           String uid = atts.getValue("uid");
    290           if (uid != null) {
    291               if (current.user != null) {
    292                   current.user.uid = uid;
    293               }
    294          }
    295 
    296           // visible attribute added in 0.4 API
    297           String visible = atts.getValue("visible");
    298           if (visible != null) {
    299                current.visible = Boolean.parseBoolean(visible);
    300           }
    301 
    302           String version = atts.getValue("version");
    303           current.version = 0;
    304           if (version != null) {
    305               try {
    306                   current.version = Integer.parseInt(version);
    307               } catch(NumberFormatException e) {
    308                   throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version));
    309               }
    310           } else {
    311               // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
    312               //
    313               if (current.id > 0 && ds.version != null && ds.version.equals("0.6")) {
    314                   throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id)));
    315               }
    316           }
    317 
    318           String action = atts.getValue("action");
    319           if (action == null)
    320                return;
    321           if (action.equals("delete"))
    322                current.delete(true);
    323           else if (action.startsWith("modify"))
    324                current.modified = true;
    325      }
    326      private long getLong(Attributes atts, String value) throws SAXException {
    327           String s = atts.getValue(value);
    328           if (s == null)
    329                throw new SAXException(tr("Missing required attribute \"{0}\".",value));
    330           return Long.parseLong(s);
    331      }
    332 
    333      private Node findNode(long id) {
    334          Node n = nodes.get(id);
    335          if (n != null)
    336               return n;
    337          for (Node node : references.nodes)
    338               if (node.id == id)
    339                    return node;
    340          // TODO: This has to be changed to support multiple layers.
    341          for (Node node : Main.ds.nodes)
    342               if (node.id == id)
    343                    return new Node(node);
    344          return null;
    345     }
    346 
    347      private void createWays() {
    348           for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
    349                Way w = new Way();
    350                boolean failed = false;
    351                for (long id : e.getValue()) {
    352                     Node n = findNode(id);
    353                     if (n == null) {
    354                          /* don't report ALL of them, just a few */
    355                          if (parseNotesCount++ < 6) {
    356                              parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id);
    357                          } else if (parseNotesCount == 6) {
    358                              parseNotes += "...\n";
    359                          }
    360                          failed = true;
    361                          break;
    362                     }
    363                     w.nodes.add(n);
    364                }
    365                if (failed) continue;
    366                e.getKey().copyTo(w);
    367                adder.visit(w);
    368           }
    369 
    370      }
    371 
    372      /**
    373       * Return the Way object with the given id, or null if it doesn't
    374       * exist yet. This method only looks at ways stored in the data set.
    375       *
    376       * @param id
    377       * @return way object or null
    378       */
    379      private Way findWay(long id) {
    380           for (Way wy : Main.ds.ways)
    381                if (wy.id == id)
    382                     return wy;
    383           return null;
    384      }
    385 
    386      /**
    387       * Return the Relation object with the given id, or null if it doesn't
    388       * exist yet. This method only looks at relations stored in the data set.
    389       *
    390       * @param id
    391       * @return relation object or null
    392       */
    393      private Relation findRelation(long id) {
    394           for (Relation e : ds.relations)
    395                if (e.id == id)
    396                     return e;
    397           for (Relation e : Main.ds.relations)
    398                if (e.id == id)
    399                     return e;
    400           return null;
    401      }
    402 
    403      /**
    404       * Create relations. This is slightly different than n/s/w because
    405       * unlike other objects, relations may reference other relations; it
    406       * is not guaranteed that a referenced relation will have been created
    407       * before it is referenced. So we have to create all relations first,
    408       * and populate them later.
    409       */
    410      private void createRelations() {
    411 
    412           // pass 1 - create all relations
    413           for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
    414                Relation en = new Relation();
    415                e.getKey().copyTo(en);
    416                adder.visit(en);
    417           }
    418 
    419           // Cache the ways here for much better search performance
    420           HashMap<Long, Way> hm = new HashMap<Long, Way>(10000);
    421           for (Way wy : ds.ways)
     249                } else if (qName.equals("tag")) {
     250                    String key = atts.getValue("k");
     251                    String value = atts.getValue("v");
     252                    current.keys.put(key,value);
     253                }
     254            } catch (NumberFormatException x) {
     255                x.printStackTrace(); // SAXException does not chain correctly
     256                throw new SAXException(x.getMessage(), x);
     257            } catch (NullPointerException x) {
     258                x.printStackTrace(); // SAXException does not chain correctly
     259                throw new SAXException(tr("NullPointerException, possibly some missing tags."), x);
     260            }
     261        }
     262
     263        @Override
     264        public void endElement(String uri, String localName, String qName) throws SAXException {
     265            if (qName.equals("node")) {
     266                nodes.put(current.id, current.createNode());
     267            }
     268        }
     269
     270        private double getDouble(Attributes atts, String value) {
     271            return Double.parseDouble(atts.getValue(value));
     272        }
     273    }
     274
     275    /**
     276     * Read out the common attributes from atts and put them into this.current.
     277     */
     278    void readCommon(Attributes atts, OsmPrimitiveData current) throws SAXException {
     279        current.id = getLong(atts, "id");
     280        if (current.id == 0)
     281            throw new SAXException(tr("Illegal object with id=0"));
     282
     283        String time = atts.getValue("timestamp");
     284        if (time != null && time.length() != 0) {
     285            current.timestamp =  DateUtils.fromString(time);
     286        }
     287
     288        // user attribute added in 0.4 API
     289        String user = atts.getValue("user");
     290        if (user != null) {
     291            // do not store literally; get object reference for string
     292            current.user = User.get(user);
     293        }
     294
     295        // uid attribute added in 0.6 API
     296        String uid = atts.getValue("uid");
     297        if (uid != null) {
     298            if (current.user != null) {
     299                current.user.uid = uid;
     300            }
     301        }
     302
     303        // visible attribute added in 0.4 API
     304        String visible = atts.getValue("visible");
     305        if (visible != null) {
     306            current.visible = Boolean.parseBoolean(visible);
     307        }
     308
     309        String version = atts.getValue("version");
     310        current.version = 0;
     311        if (version != null) {
     312            try {
     313                current.version = Integer.parseInt(version);
     314            } catch(NumberFormatException e) {
     315                throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version));
     316            }
     317        } else {
     318            // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
     319            //
     320            if (current.id > 0 && ds.version != null && ds.version.equals("0.6"))
     321                throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id)));
     322        }
     323
     324        String action = atts.getValue("action");
     325        if (action == null)
     326            return;
     327        if (action.equals("delete")) {
     328            current.deleted = true;
     329        } else if (action.startsWith("modify")) {
     330            current.modified = true;
     331        }
     332    }
     333    private long getLong(Attributes atts, String value) throws SAXException {
     334        String s = atts.getValue(value);
     335        if (s == null)
     336            throw new SAXException(tr("Missing required attribute \"{0}\".",value));
     337        return Long.parseLong(s);
     338    }
     339
     340    private Node findNode(long id) {
     341        Node n = nodes.get(id);
     342        if (n != null)
     343            return n;
     344        for (Node node : references.nodes)
     345            if (node.id == id)
     346                return node;
     347        // TODO: This has to be changed to support multiple layers.
     348        for (Node node : Main.ds.nodes)
     349            if (node.id == id)
     350                return new Node(node);
     351        return null;
     352    }
     353
     354    protected void createWays() {
     355        for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
     356            Way w = new Way();
     357            boolean failed = false;
     358            for (long id : e.getValue()) {
     359                Node n = findNode(id);
     360                if (n == null) {
     361                    /* don't report ALL of them, just a few */
     362                    if (parseNotesCount++ < 6) {
     363                        parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id);
     364                    } else if (parseNotesCount == 6) {
     365                        parseNotes += "...\n";
     366                    }
     367                    failed = true;
     368                    break;
     369                }
     370                w.nodes.add(n);
     371            }
     372            if (failed) {
     373                skippedWayIds.add(e.getKey().id);
     374                continue;
     375            }
     376            e.getKey().copyTo(w);
     377            adder.visit(w);
     378        }
     379
     380    }
     381
     382    /**
     383     * Return the Way object with the given id, or null if it doesn't
     384     * exist yet. This method only looks at ways stored in the data set.
     385     *
     386     * @param id
     387     * @return way object or null
     388     */
     389    private Way findWay(long id) {
     390        for (Way wy : Main.ds.ways)
     391            if (wy.id == id)
     392                return wy;
     393        return null;
     394    }
     395
     396    /**
     397     * Return the Relation object with the given id, or null if it doesn't
     398     * exist yet. This method only looks at relations stored in the data set.
     399     *
     400     * @param id
     401     * @return relation object or null
     402     */
     403    private Relation findRelation(long id) {
     404        for (Relation e : ds.relations)
     405            if (e.id == id)
     406                return e;
     407        for (Relation e : Main.ds.relations)
     408            if (e.id == id)
     409                return e;
     410        return null;
     411    }
     412
     413    /**
     414     * Create relations. This is slightly different than n/s/w because
     415     * unlike other objects, relations may reference other relations; it
     416     * is not guaranteed that a referenced relation will have been created
     417     * before it is referenced. So we have to create all relations first,
     418     * and populate them later.
     419     */
     420    private void createRelations() {
     421
     422        // pass 1 - create all relations
     423        for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
     424            Relation en = new Relation();
     425            e.getKey().copyTo(en);
     426            adder.visit(en);
     427        }
     428
     429        // Cache the ways here for much better search performance
     430        HashMap<Long, Way> hm = new HashMap<Long, Way>(10000);
     431        for (Way wy : ds.ways) {
    422432            hm.put(wy.id, wy);
    423 
    424           // pass 2 - sort out members
    425           for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
    426                Relation en = findRelation(e.getKey().id);
    427                if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
    428 
    429                for (RelationMemberData emd : e.getValue()) {
    430                     RelationMember em = emd.relationMember;
    431                     if (emd.type.equals("node")) {
    432                          em.member = findNode(emd.id);
    433                          if (em.member == null) {
    434                               em.member = new Node(emd.id);
    435                               adder.visit((Node)em.member);
    436                          }
    437                     } else if (emd.type.equals("way")) {
    438                          em.member = hm.get(emd.id);
    439                          if (em.member == null)
    440                             em.member = findWay(emd.id);
    441                          if (em.member == null) {
    442                               em.member = new Way(emd.id);
    443                               adder.visit((Way)em.member);
    444                          }
    445                     } else if (emd.type.equals("relation")) {
    446                          em.member = findRelation(emd.id);
    447                          if (em.member == null) {
    448                               em.member = new Relation(emd.id);
    449                               adder.visit((Relation)em.member);
    450                          }
    451                     } else {
    452                          // this is an error.
    453                     }
    454                     en.members.add(em);
    455                }
    456           }
    457           hm = null;
    458      }
    459 
    460      /**
    461       * Parse the given input source and return the dataset.
    462       * @param ref The dataset that is search in for references first. If
    463       *      the Reference is not found here, Main.ds is searched and a copy of the
    464       *  element found there is returned.
    465       */
    466      public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
    467           return parseDataSetOsm(source, ref, pleaseWaitDlg).ds;
    468      }
    469 
    470      public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
    471           OsmReader osm = new OsmReader();
    472           osm.references = ref == null ? new DataSet() : ref;
    473 
    474           currSource = source;
    475 
    476           // phase 1: Parse nodes and read in raw ways
    477           InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
    478           try {
    479              SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
    480           } catch (ParserConfigurationException e1) {
    481              e1.printStackTrace(); // broken SAXException chaining
    482              throw new SAXException(e1);
    483           }
    484 
    485           Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data..."));
    486           Main.pleaseWaitDlg.setIndeterminate(true);
    487 
    488 //          System.out.println("Parser finished: Tags " + tagsN + " Nodes " + nodesN + " Ways " + waysN +
    489 //            " Relations " + relationsN + " Members " + membersN);
    490 
    491           for (Node n : osm.nodes.values())
    492                osm.adder.visit(n);
    493 
    494           try {
    495                osm.createWays();
    496                osm.createRelations();
    497           } catch (NumberFormatException e) {
    498                e.printStackTrace();
    499                throw new SAXException(tr("Ill-formed node id"));
    500           }
    501 
    502           // clear all negative ids (new to this file)
    503           for (OsmPrimitive o : osm.ds.allPrimitives())
    504                if (o.id < 0)
    505                     o.id = 0;
    506 
    507 //          System.out.println("Data loaded!");
    508           Main.pleaseWaitDlg.setIndeterminate(false);
    509           Main.pleaseWaitDlg.progress.setValue(0);
    510 
    511           return osm;
    512      }
     433        }
     434
     435        // pass 2 - sort out members
     436        for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
     437            Relation en = findRelation(e.getKey().id);
     438            if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
     439
     440            for (RelationMemberData emd : e.getValue()) {
     441                RelationMember em = emd.relationMember;
     442                if (emd.type.equals("node")) {
     443                    em.member = findNode(emd.id);
     444                    if (em.member == null) {
     445                        em.member = new Node(emd.id);
     446                        adder.visit((Node)em.member);
     447                    }
     448                } else if (emd.type.equals("way")) {
     449                    em.member = hm.get(emd.id);
     450                    if (em.member == null) {
     451                        em.member = findWay(emd.id);
     452                    }
     453                    if (em.member == null) {
     454                        em.member = new Way(emd.id);
     455                        adder.visit((Way)em.member);
     456                    }
     457                } else if (emd.type.equals("relation")) {
     458                    em.member = findRelation(emd.id);
     459                    if (em.member == null) {
     460                        em.member = new Relation(emd.id);
     461                        adder.visit((Relation)em.member);
     462                    }
     463                } else {
     464                    // this is an error.
     465                }
     466                en.members.add(em);
     467            }
     468        }
     469        hm = null;
     470    }
     471
     472    /**
     473     * Parse the given input source and return the dataset.
     474     * @param ref The dataset that is search in for references first. If
     475     *      the Reference is not found here, Main.ds is searched and a copy of the
     476     *  element found there is returned.
     477     */
     478    public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
     479        return parseDataSetOsm(source, ref, pleaseWaitDlg).ds;
     480    }
     481
     482    public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
     483        OsmReader osm = new OsmReader();
     484        osm.references = ref == null ? new DataSet() : ref;
     485
     486        // phase 1: Parse nodes and read in raw ways
     487        InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
     488        try {
     489            SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
     490        } catch (ParserConfigurationException e1) {
     491            e1.printStackTrace(); // broken SAXException chaining
     492            throw new SAXException(e1);
     493        }
     494
     495        Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data..."));
     496        Main.pleaseWaitDlg.setIndeterminate(true);
     497
     498        for (Node n : osm.nodes.values()) {
     499            osm.adder.visit(n);
     500        }
     501
     502        try {
     503            osm.createWays();
     504            osm.createRelations();
     505        } catch (NumberFormatException e) {
     506            e.printStackTrace();
     507            throw new SAXException(tr("Ill-formed node id"));
     508        }
     509
     510        // clear all negative ids (new to this file)
     511        for (OsmPrimitive o : osm.ds.allPrimitives())
     512            if (o.id < 0) {
     513                o.id = 0;
     514            }
     515
     516        Main.pleaseWaitDlg.setIndeterminate(false);
     517        Main.pleaseWaitDlg.progress.setValue(0);
     518
     519        return osm;
     520    }
     521
     522    /**
     523     * replies a set of ids of skipped {@see Way}s, i.e. ways which were included in the downloaded
     524     * data but which referred to nodes <strong>not</strong>  available in the downloaded data
     525     *
     526     * @return the set of ids
     527     */
     528    public Set<Long> getSkippedWayIds() {
     529        return skippedWayIds;
     530    }
    513531}
Note: See TracChangeset for help on using the changeset viewer.