1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.gui.conflict.nodes;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 |
|
---|
6 | import java.beans.PropertyChangeEvent;
|
---|
7 | import java.beans.PropertyChangeListener;
|
---|
8 | import java.util.ArrayList;
|
---|
9 | import java.util.List;
|
---|
10 | import java.util.logging.Logger;
|
---|
11 |
|
---|
12 | import javax.swing.DefaultListSelectionModel;
|
---|
13 | import javax.swing.ListSelectionModel;
|
---|
14 | import javax.swing.table.DefaultTableModel;
|
---|
15 | import javax.swing.table.TableModel;
|
---|
16 |
|
---|
17 | import org.openstreetmap.josm.command.WayNodesConflictResolverCommand;
|
---|
18 | import org.openstreetmap.josm.data.osm.Node;
|
---|
19 | import org.openstreetmap.josm.data.osm.Way;
|
---|
20 |
|
---|
21 | public class NodeListMergeModel {
|
---|
22 | private static final Logger logger = Logger.getLogger(NodeListMergeModel.class.getName());
|
---|
23 |
|
---|
24 | public static final String PROP_FROZEN = NodeListMergeModel.class.getName() + ".frozen";
|
---|
25 |
|
---|
26 |
|
---|
27 | private ArrayList<Node> myNodes;
|
---|
28 | private ArrayList<Node> theirNodes;
|
---|
29 | private ArrayList<Node> mergedNodes;
|
---|
30 |
|
---|
31 |
|
---|
32 | private DefaultTableModel myNodesTableModel;
|
---|
33 | private DefaultTableModel theirNodesTableModel;
|
---|
34 | private DefaultTableModel mergedNodesTableModel;
|
---|
35 |
|
---|
36 | private DefaultListSelectionModel myNodesSelectionModel;
|
---|
37 | private DefaultListSelectionModel theirNodesSelectionModel;
|
---|
38 | private DefaultListSelectionModel mergedNodesSelectionModel;
|
---|
39 |
|
---|
40 | private ArrayList<PropertyChangeListener> listeners;
|
---|
41 | private boolean isFrozen = false;
|
---|
42 |
|
---|
43 |
|
---|
44 | public NodeListMergeModel() {
|
---|
45 | myNodes = new ArrayList<Node>();
|
---|
46 | theirNodes = new ArrayList<Node>();
|
---|
47 | mergedNodes = new ArrayList<Node>();
|
---|
48 |
|
---|
49 | myNodesTableModel = new NodeListTableModel(myNodes);
|
---|
50 | theirNodesTableModel = new NodeListTableModel(theirNodes);
|
---|
51 | mergedNodesTableModel = new NodeListTableModel(mergedNodes);
|
---|
52 |
|
---|
53 | myNodesSelectionModel = new DefaultListSelectionModel();
|
---|
54 | theirNodesSelectionModel = new DefaultListSelectionModel();
|
---|
55 | mergedNodesSelectionModel = new DefaultListSelectionModel();
|
---|
56 |
|
---|
57 | listeners = new ArrayList<PropertyChangeListener>();
|
---|
58 |
|
---|
59 | setFrozen(true);
|
---|
60 | }
|
---|
61 |
|
---|
62 |
|
---|
63 | public void addPropertyChangeListener(PropertyChangeListener listener) {
|
---|
64 | synchronized(listeners) {
|
---|
65 | if (listener != null && ! listeners.contains(listener)) {
|
---|
66 | listeners.add(listener);
|
---|
67 | }
|
---|
68 | }
|
---|
69 | }
|
---|
70 |
|
---|
71 | public void removePropertyChangeListener(PropertyChangeListener listener) {
|
---|
72 | synchronized(listeners) {
|
---|
73 | if (listener != null && listeners.contains(listener)) {
|
---|
74 | listeners.remove(listener);
|
---|
75 | }
|
---|
76 | }
|
---|
77 | }
|
---|
78 |
|
---|
79 | protected void fireFrozenChanged(boolean oldValue, boolean newValue) {
|
---|
80 | synchronized(listeners) {
|
---|
81 | PropertyChangeEvent evt = new PropertyChangeEvent(this, PROP_FROZEN, oldValue, newValue);
|
---|
82 | for (PropertyChangeListener listener: listeners) {
|
---|
83 | listener.propertyChange(evt);
|
---|
84 | }
|
---|
85 | }
|
---|
86 | }
|
---|
87 |
|
---|
88 | public void setFrozen(boolean isFrozen) {
|
---|
89 | boolean oldValue = this.isFrozen;
|
---|
90 | this.isFrozen = isFrozen;
|
---|
91 | fireFrozenChanged(oldValue, this.isFrozen);
|
---|
92 | }
|
---|
93 |
|
---|
94 | public boolean isFrozen() {
|
---|
95 | return isFrozen;
|
---|
96 | }
|
---|
97 |
|
---|
98 | public TableModel getMyNodesTableModel() {
|
---|
99 | return myNodesTableModel;
|
---|
100 | }
|
---|
101 |
|
---|
102 | public TableModel getTheirNodesTableModel() {
|
---|
103 | return theirNodesTableModel;
|
---|
104 | }
|
---|
105 |
|
---|
106 | public TableModel getMergedNodesTableModel() {
|
---|
107 | return mergedNodesTableModel;
|
---|
108 | }
|
---|
109 |
|
---|
110 | public ListSelectionModel getMyNodesSelectionModel() {
|
---|
111 | return myNodesSelectionModel;
|
---|
112 | }
|
---|
113 |
|
---|
114 | public ListSelectionModel getTheirNodesSelectionModel() {
|
---|
115 | return theirNodesSelectionModel;
|
---|
116 | }
|
---|
117 |
|
---|
118 | public ListSelectionModel getMergedNodesSelectionModel() {
|
---|
119 | return mergedNodesSelectionModel;
|
---|
120 | }
|
---|
121 |
|
---|
122 |
|
---|
123 | protected void fireModelDataChanged() {
|
---|
124 | myNodesTableModel.fireTableDataChanged();
|
---|
125 | theirNodesTableModel.fireTableDataChanged();
|
---|
126 | mergedNodesTableModel.fireTableDataChanged();
|
---|
127 | }
|
---|
128 |
|
---|
129 | protected void copyNodesToTop(List<Node> source, int []rows) {
|
---|
130 | if (rows == null || rows.length == 0) {
|
---|
131 | return;
|
---|
132 | }
|
---|
133 | for (int i = rows.length - 1; i >= 0; i--) {
|
---|
134 | int row = rows[i];
|
---|
135 | Node n = source.get(row);
|
---|
136 | mergedNodes.add(0, n);
|
---|
137 | }
|
---|
138 | fireModelDataChanged();
|
---|
139 | mergedNodesSelectionModel.setSelectionInterval(0, rows.length -1);
|
---|
140 | }
|
---|
141 |
|
---|
142 | /**
|
---|
143 | * Copies the nodes given by indices in rows from the list of my nodes to the
|
---|
144 | * list of merged nodes. Inserts the nodes at the top of the list of merged
|
---|
145 | * nodes.
|
---|
146 | *
|
---|
147 | * @param rows the indices
|
---|
148 | */
|
---|
149 | public void copyMyNodesToTop(int [] rows) {
|
---|
150 | copyNodesToTop(myNodes, rows);
|
---|
151 | }
|
---|
152 |
|
---|
153 | /**
|
---|
154 | * Copies the nodes given by indices in rows from the list of their nodes to the
|
---|
155 | * list of merged nodes. Inserts the nodes at the top of the list of merged
|
---|
156 | * nodes.
|
---|
157 | *
|
---|
158 | * @param rows the indices
|
---|
159 | */
|
---|
160 | public void copyTheirNodesToTop(int [] rows) {
|
---|
161 | copyNodesToTop(theirNodes, rows);
|
---|
162 | }
|
---|
163 |
|
---|
164 | /**
|
---|
165 | * Copies the nodes given by indices in rows from the list of nodes in source to the
|
---|
166 | * list of merged nodes. Inserts the nodes at the end of the list of merged
|
---|
167 | * nodes.
|
---|
168 | *
|
---|
169 | * @param source the list of nodes to copy from
|
---|
170 | * @param rows the indices
|
---|
171 | */
|
---|
172 |
|
---|
173 | public void copyNodesToEnd(List<Node> source, int [] rows) {
|
---|
174 | if (rows == null || rows.length == 0) {
|
---|
175 | return;
|
---|
176 | }
|
---|
177 | for (int row : rows) {
|
---|
178 | Node n = source.get(row);
|
---|
179 | mergedNodes.add(n);
|
---|
180 | }
|
---|
181 | fireModelDataChanged();
|
---|
182 | mergedNodesSelectionModel.setSelectionInterval(mergedNodes.size()-rows.length, mergedNodes.size() -1);
|
---|
183 |
|
---|
184 | }
|
---|
185 |
|
---|
186 | /**
|
---|
187 | * Copies the nodes given by indices in rows from the list of my nodes to the
|
---|
188 | * list of merged nodes. Inserts the nodes at the end of the list of merged
|
---|
189 | * nodes.
|
---|
190 | *
|
---|
191 | * @param rows the indices
|
---|
192 | */
|
---|
193 | public void copyMyNodesToEnd(int [] rows) {
|
---|
194 | copyNodesToEnd(myNodes, rows);
|
---|
195 | }
|
---|
196 |
|
---|
197 | /**
|
---|
198 | * Copies the nodes given by indices in rows from the list of their nodes to the
|
---|
199 | * list of merged nodes. Inserts the nodes at the end of the list of merged
|
---|
200 | * nodes.
|
---|
201 | *
|
---|
202 | * @param rows the indices
|
---|
203 | */
|
---|
204 | public void copyTheirNodesToEnd(int [] rows) {
|
---|
205 | copyNodesToEnd(theirNodes, rows);
|
---|
206 | }
|
---|
207 |
|
---|
208 | /**
|
---|
209 | * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the
|
---|
210 | * list of merged nodes. Inserts the nodes before row given by current.
|
---|
211 | *
|
---|
212 | * @param source the list of nodes to copy from
|
---|
213 | * @param rows the indices
|
---|
214 | * @param current the row index before which the nodes are inserted
|
---|
215 | * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
|
---|
216 | *
|
---|
217 | */
|
---|
218 | protected void copyNodesBeforeCurrent(List<Node> source, int [] rows, int current) {
|
---|
219 | if (rows == null || rows.length == 0) {
|
---|
220 | return;
|
---|
221 | }
|
---|
222 | if (current < 0 || current >= mergedNodes.size()) {
|
---|
223 | throw new IllegalArgumentException(tr("parameter current out of range: got {0}", current));
|
---|
224 | }
|
---|
225 | for (int i=rows.length -1; i>=0; i--) {
|
---|
226 | int row = rows[i];
|
---|
227 | Node n = source.get(row);
|
---|
228 | mergedNodes.add(current, n);
|
---|
229 | }
|
---|
230 | fireModelDataChanged();
|
---|
231 | mergedNodesSelectionModel.setSelectionInterval(current, current + rows.length-1);
|
---|
232 | }
|
---|
233 |
|
---|
234 | /**
|
---|
235 | * Copies the nodes given by indices in rows from the list of my nodes to the
|
---|
236 | * list of merged nodes. Inserts the nodes before row given by current.
|
---|
237 | *
|
---|
238 | * @param rows the indices
|
---|
239 | * @param current the row index before which the nodes are inserted
|
---|
240 | * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
|
---|
241 | *
|
---|
242 | */
|
---|
243 | public void copyMyNodesBeforeCurrent(int [] rows, int current) {
|
---|
244 | copyNodesBeforeCurrent(myNodes,rows,current);
|
---|
245 | }
|
---|
246 |
|
---|
247 | /**
|
---|
248 | * Copies the nodes given by indices in rows from the list of their nodes to the
|
---|
249 | * list of merged nodes. Inserts the nodes before row given by current.
|
---|
250 | *
|
---|
251 | * @param rows the indices
|
---|
252 | * @param current the row index before which the nodes are inserted
|
---|
253 | * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
|
---|
254 | *
|
---|
255 | */
|
---|
256 | public void copyTheirNodesBeforeCurrent(int [] rows, int current) {
|
---|
257 | copyNodesBeforeCurrent(theirNodes,rows,current);
|
---|
258 | }
|
---|
259 |
|
---|
260 | /**
|
---|
261 | * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the
|
---|
262 | * list of merged nodes. Inserts the nodes after the row given by current.
|
---|
263 | *
|
---|
264 | * @param source the list of nodes to copy from
|
---|
265 | * @param rows the indices
|
---|
266 | * @param current the row index after which the nodes are inserted
|
---|
267 | * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
|
---|
268 | *
|
---|
269 | */
|
---|
270 | protected void copyNodesAfterCurrent(List<Node> source, int [] rows, int current) {
|
---|
271 | if (rows == null || rows.length == 0) {
|
---|
272 | return;
|
---|
273 | }
|
---|
274 | if (current < 0 || current >= mergedNodes.size()) {
|
---|
275 | throw new IllegalArgumentException(tr("parameter current out of range: got {0}", current));
|
---|
276 | }
|
---|
277 | if (current == mergedNodes.size() -1) {
|
---|
278 | copyMyNodesToEnd(rows);
|
---|
279 | } else {
|
---|
280 | for (int i=rows.length -1; i>=0; i--) {
|
---|
281 | int row = rows[i];
|
---|
282 | Node n = source.get(row);
|
---|
283 | mergedNodes.add(current+1, n);
|
---|
284 | }
|
---|
285 | }
|
---|
286 | fireModelDataChanged();
|
---|
287 | mergedNodesSelectionModel.setSelectionInterval(current+1, current + rows.length-1);
|
---|
288 | }
|
---|
289 |
|
---|
290 | /**
|
---|
291 | * Copies the nodes given by indices in rows from the list of my nodes to the
|
---|
292 | * list of merged nodes. Inserts the nodes after the row given by current.
|
---|
293 | *
|
---|
294 | * @param rows the indices
|
---|
295 | * @param current the row index after which the nodes are inserted
|
---|
296 | * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
|
---|
297 | *
|
---|
298 | */
|
---|
299 | public void copyMyNodesAfterCurrent(int [] rows, int current) {
|
---|
300 | copyNodesAfterCurrent(myNodes, rows, current);
|
---|
301 | }
|
---|
302 |
|
---|
303 | /**
|
---|
304 | * Copies the nodes given by indices in rows from the list of my nodes to the
|
---|
305 | * list of merged nodes. Inserts the nodes after the row given by current.
|
---|
306 | *
|
---|
307 | * @param rows the indices
|
---|
308 | * @param current the row index after which the nodes are inserted
|
---|
309 | * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
|
---|
310 | *
|
---|
311 | */
|
---|
312 | public void copyTheirNodesAfterCurrent(int [] rows, int current) {
|
---|
313 | copyNodesAfterCurrent(theirNodes, rows, current);
|
---|
314 | }
|
---|
315 |
|
---|
316 | /**
|
---|
317 | * Moves the nodes given by indices in rows up by one position in the list
|
---|
318 | * of merged nodes.
|
---|
319 | *
|
---|
320 | * @param rows the indices
|
---|
321 | *
|
---|
322 | */
|
---|
323 | protected void moveUpMergedNodes(int [] rows) {
|
---|
324 | if (rows == null || rows.length == 0) {
|
---|
325 | return;
|
---|
326 | }
|
---|
327 | if (rows[0] == 0) {
|
---|
328 | // can't move up
|
---|
329 | return;
|
---|
330 | }
|
---|
331 | for (int row: rows) {
|
---|
332 | Node n = mergedNodes.get(row);
|
---|
333 | mergedNodes.remove(row);
|
---|
334 | mergedNodes.add(row -1, n);
|
---|
335 | }
|
---|
336 | fireModelDataChanged();
|
---|
337 | mergedNodesSelectionModel.clearSelection();
|
---|
338 | for (int row: rows) {
|
---|
339 | mergedNodesSelectionModel.addSelectionInterval(row-1, row-1);
|
---|
340 | }
|
---|
341 | }
|
---|
342 |
|
---|
343 | /**
|
---|
344 | * Moves the nodes given by indices in rows down by one position in the list
|
---|
345 | * of merged nodes.
|
---|
346 | *
|
---|
347 | * @param rows the indices
|
---|
348 | */
|
---|
349 | protected void moveDownMergedNodes(int [] rows) {
|
---|
350 | if (rows == null || rows.length == 0) {
|
---|
351 | return;
|
---|
352 | }
|
---|
353 | if (rows[rows.length -1] == mergedNodes.size() -1) {
|
---|
354 | // can't move down
|
---|
355 | return;
|
---|
356 | }
|
---|
357 | for (int i = rows.length-1; i>=0;i--) {
|
---|
358 | int row = rows[i];
|
---|
359 | Node n = mergedNodes.get(row);
|
---|
360 | mergedNodes.remove(row);
|
---|
361 | mergedNodes.add(row +1, n);
|
---|
362 | }
|
---|
363 | fireModelDataChanged();
|
---|
364 | mergedNodesSelectionModel.clearSelection();
|
---|
365 | for (int row: rows) {
|
---|
366 | mergedNodesSelectionModel.addSelectionInterval(row+1, row+1);
|
---|
367 | }
|
---|
368 | }
|
---|
369 |
|
---|
370 | /**
|
---|
371 | * Removes the nodes given by indices in rows from the list
|
---|
372 | * of merged nodes.
|
---|
373 | *
|
---|
374 | * @param rows the indices
|
---|
375 | */
|
---|
376 | protected void removeMergedNodes(int [] rows) {
|
---|
377 | if (rows == null || rows.length == 0) {
|
---|
378 | return;
|
---|
379 | }
|
---|
380 | for (int i = rows.length-1; i>=0;i--) {
|
---|
381 | mergedNodes.remove(rows[i]);
|
---|
382 | }
|
---|
383 | fireModelDataChanged();
|
---|
384 | mergedNodesSelectionModel.clearSelection();
|
---|
385 | }
|
---|
386 |
|
---|
387 |
|
---|
388 | /**
|
---|
389 | * Replies true if the list of my nodes and the list of their
|
---|
390 | * nodes are equal, i.e. if they consists of a list of nodes with
|
---|
391 | * identical ids in the same order.
|
---|
392 | *
|
---|
393 | * @return true, if the lists are equal; false otherwise
|
---|
394 | */
|
---|
395 | protected boolean myAndTheirNodesEqual() {
|
---|
396 | if (myNodes.size() != theirNodes.size()) {
|
---|
397 | return false;
|
---|
398 | }
|
---|
399 | for (int i=0; i < myNodes.size(); i++) {
|
---|
400 | if (myNodes.get(i).id != theirNodes.get(i).id) {
|
---|
401 | return false;
|
---|
402 | }
|
---|
403 | }
|
---|
404 | return true;
|
---|
405 | }
|
---|
406 |
|
---|
407 | /**
|
---|
408 | * Populates the model with the nodes in the two {@see Way}s <code>my</code> and
|
---|
409 | * <code>their</code>.
|
---|
410 | *
|
---|
411 | * @param my my way (i.e. the way in the local dataset)
|
---|
412 | * @param their their way (i.e. the way in the server dataset)
|
---|
413 | * @exception IllegalArgumentException thrown, if my is null
|
---|
414 | * @exception IllegalArgumentException thrown, if their is null
|
---|
415 | */
|
---|
416 | public void populate(Way my, Way their) {
|
---|
417 | if (my == null)
|
---|
418 | throw new IllegalArgumentException("parameter 'way' must not be null");
|
---|
419 | if (their == null)
|
---|
420 | throw new IllegalArgumentException("parameter 'their' must not be null");
|
---|
421 | mergedNodes.clear();
|
---|
422 | myNodes.clear();
|
---|
423 | theirNodes.clear();
|
---|
424 | for (Node n : my.nodes) {
|
---|
425 | myNodes.add(n);
|
---|
426 | }
|
---|
427 | for (Node n : their.nodes) {
|
---|
428 | theirNodes.add(n);
|
---|
429 | }
|
---|
430 | if (myAndTheirNodesEqual()) {
|
---|
431 | mergedNodes = new ArrayList<Node>(myNodes);
|
---|
432 | setFrozen(true);
|
---|
433 | } else {
|
---|
434 | setFrozen(false);
|
---|
435 | }
|
---|
436 |
|
---|
437 | fireModelDataChanged();
|
---|
438 | }
|
---|
439 |
|
---|
440 | /**
|
---|
441 | * Builds the command to resolve conflicts in the node list of a way
|
---|
442 | *
|
---|
443 | * @param my my way. Must not be null.
|
---|
444 | * @param their their way. Must not be null
|
---|
445 | * @return the command
|
---|
446 | * @exception IllegalArgumentException thrown, if my is null or not a {@see Way}
|
---|
447 | * @exception IllegalArgumentException thrown, if their is null or not a {@see Way}
|
---|
448 | * @exception IllegalStateException thrown, if the merge is not yet frozen
|
---|
449 | */
|
---|
450 | public WayNodesConflictResolverCommand buildResolveCommand(Way my, Way their) {
|
---|
451 | if (my == null) {
|
---|
452 | throw new IllegalArgumentException("parameter my most not be null");
|
---|
453 | }
|
---|
454 | if (their == null) {
|
---|
455 | throw new IllegalArgumentException("parameter my most not be null");
|
---|
456 | }
|
---|
457 | if (! isFrozen()) {
|
---|
458 | throw new IllegalArgumentException("merged nodes not frozen yet. Can't build resolution command");
|
---|
459 | }
|
---|
460 | return new WayNodesConflictResolverCommand(my, their, mergedNodes);
|
---|
461 | }
|
---|
462 |
|
---|
463 | class NodeListTableModel extends DefaultTableModel {
|
---|
464 | private ArrayList<Node> nodes;
|
---|
465 |
|
---|
466 | public NodeListTableModel(ArrayList<Node> nodes) {
|
---|
467 | this.nodes = nodes;
|
---|
468 | }
|
---|
469 |
|
---|
470 | @Override
|
---|
471 | public int getRowCount() {
|
---|
472 | return nodes == null ? 0 : nodes.size();
|
---|
473 | }
|
---|
474 |
|
---|
475 | @Override
|
---|
476 | public Object getValueAt(int row, int column) {
|
---|
477 | return nodes.get(row);
|
---|
478 | }
|
---|
479 |
|
---|
480 | @Override
|
---|
481 | public boolean isCellEditable(int row, int column) {
|
---|
482 | return false;
|
---|
483 | }
|
---|
484 | }
|
---|
485 |
|
---|
486 |
|
---|
487 | }
|
---|