source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java@ 11747

Last change on this file since 11747 was 11747, checked in by Don-vip, 7 years ago

checkstyle - NoWhiteSpaceBefore ...

  • Property svn:eol-style set to native
File size: 31.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.pair;
3
4import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_MERGED;
5import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.MY_WITH_THEIR;
6import static org.openstreetmap.josm.gui.conflict.pair.ComparePairType.THEIR_WITH_MERGED;
7import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MERGED_ENTRIES;
8import static org.openstreetmap.josm.gui.conflict.pair.ListRole.MY_ENTRIES;
9import static org.openstreetmap.josm.gui.conflict.pair.ListRole.THEIR_ENTRIES;
10import static org.openstreetmap.josm.tools.I18n.tr;
11
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.ArrayList;
15import java.util.EnumMap;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import javax.swing.AbstractListModel;
22import javax.swing.ComboBoxModel;
23import javax.swing.DefaultListSelectionModel;
24import javax.swing.JOptionPane;
25import javax.swing.JTable;
26import javax.swing.ListSelectionModel;
27import javax.swing.table.DefaultTableModel;
28import javax.swing.table.TableModel;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.command.conflict.ConflictResolveCommand;
32import org.openstreetmap.josm.data.conflict.Conflict;
33import org.openstreetmap.josm.data.osm.DataSet;
34import org.openstreetmap.josm.data.osm.OsmPrimitive;
35import org.openstreetmap.josm.data.osm.PrimitiveId;
36import org.openstreetmap.josm.data.osm.RelationMember;
37import org.openstreetmap.josm.gui.HelpAwareOptionPane;
38import org.openstreetmap.josm.gui.help.HelpUtil;
39import org.openstreetmap.josm.gui.util.ChangeNotifier;
40import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
41import org.openstreetmap.josm.tools.CheckParameterUtil;
42import org.openstreetmap.josm.tools.Utils;
43
44/**
45 * ListMergeModel is a model for interactively comparing and merging two list of entries
46 * of type T. It maintains three lists of entries of type T:
47 * <ol>
48 * <li>the list of <em>my</em> entries</li>
49 * <li>the list of <em>their</em> entries</li>
50 * <li>the list of <em>merged</em> entries</li>
51 * </ol>
52 *
53 * A ListMergeModel is a factory for three {@link TableModel}s and three {@link ListSelectionModel}s:
54 * <ol>
55 * <li>the table model and the list selection for for a {@link JTable} which shows my entries.
56 * See {@link #getMyTableModel()} and {@link AbstractListMergeModel#getMySelectionModel()}</li>
57 * <li>dito for their entries and merged entries</li>
58 * </ol>
59 *
60 * A ListMergeModel can be ''frozen''. If it's frozen, it doesn't accept additional merge
61 * decisions. {@link PropertyChangeListener}s can register for property value changes of
62 * {@link #FROZEN_PROP}.
63 *
64 * ListMergeModel is an abstract class. Three methods have to be implemented by subclasses:
65 * <ul>
66 * <li>{@link AbstractListMergeModel#cloneEntryForMergedList} - clones an entry of type T</li>
67 * <li>{@link AbstractListMergeModel#isEqualEntry} - checks whether two entries are equals </li>
68 * <li>{@link AbstractListMergeModel#setValueAt(DefaultTableModel, Object, int, int)} - handles values edited in
69 * a JTable, dispatched from {@link TableModel#setValueAt(Object, int, int)} </li>
70 * </ul>
71 * A ListMergeModel is used in combination with a {@link AbstractListMerger}.
72 *
73 * @param <T> the type of the list entries
74 * @param <C> the type of conflict resolution command
75 * @see AbstractListMerger
76 */
77public abstract class AbstractListMergeModel<T extends PrimitiveId, C extends ConflictResolveCommand> extends ChangeNotifier {
78 public static final String FROZEN_PROP = AbstractListMergeModel.class.getName() + ".frozen";
79
80 private static final int MAX_DELETED_PRIMITIVE_IN_DIALOG = 5;
81
82 protected Map<ListRole, ArrayList<T>> entries;
83
84 protected EntriesTableModel myEntriesTableModel;
85 protected EntriesTableModel theirEntriesTableModel;
86 protected EntriesTableModel mergedEntriesTableModel;
87
88 protected EntriesSelectionModel myEntriesSelectionModel;
89 protected EntriesSelectionModel theirEntriesSelectionModel;
90 protected EntriesSelectionModel mergedEntriesSelectionModel;
91
92 private final Set<PropertyChangeListener> listeners;
93 private boolean isFrozen;
94 private final ComparePairListModel comparePairListModel;
95
96 private DataSet myDataset;
97 private Map<PrimitiveId, PrimitiveId> mergedMap;
98
99 /**
100 * Creates a clone of an entry of type T suitable to be included in the
101 * list of merged entries
102 *
103 * @param entry the entry
104 * @return the cloned entry
105 */
106 protected abstract T cloneEntryForMergedList(T entry);
107
108 /**
109 * checks whether two entries are equal. This is not necessarily the same as
110 * e1.equals(e2).
111 *
112 * @param e1 the first entry
113 * @param e2 the second entry
114 * @return true, if the entries are equal, false otherwise.
115 */
116 public abstract boolean isEqualEntry(T e1, T e2);
117
118 /**
119 * Handles method dispatches from {@link TableModel#setValueAt(Object, int, int)}.
120 *
121 * @param model the table model
122 * @param value the value to be set
123 * @param row the row index
124 * @param col the column index
125 *
126 * @see TableModel#setValueAt(Object, int, int)
127 */
128 protected abstract void setValueAt(DefaultTableModel model, Object value, int row, int col);
129
130 /**
131 * Replies primitive from my dataset referenced by entry
132 * @param entry entry
133 * @return Primitive from my dataset referenced by entry
134 */
135 public OsmPrimitive getMyPrimitive(T entry) {
136 return getMyPrimitiveById(entry);
137 }
138
139 public final OsmPrimitive getMyPrimitiveById(PrimitiveId entry) {
140 OsmPrimitive result = myDataset.getPrimitiveById(entry);
141 if (result == null && mergedMap != null) {
142 PrimitiveId id = mergedMap.get(entry);
143 if (id == null && entry instanceof OsmPrimitive) {
144 id = mergedMap.get(((OsmPrimitive) entry).getPrimitiveId());
145 }
146 if (id != null) {
147 result = myDataset.getPrimitiveById(id);
148 }
149 }
150 return result;
151 }
152
153 protected void buildMyEntriesTableModel() {
154 myEntriesTableModel = new EntriesTableModel(MY_ENTRIES);
155 }
156
157 protected void buildTheirEntriesTableModel() {
158 theirEntriesTableModel = new EntriesTableModel(THEIR_ENTRIES);
159 }
160
161 protected void buildMergedEntriesTableModel() {
162 mergedEntriesTableModel = new EntriesTableModel(MERGED_ENTRIES);
163 }
164
165 protected List<T> getMergedEntries() {
166 return entries.get(MERGED_ENTRIES);
167 }
168
169 protected List<T> getMyEntries() {
170 return entries.get(MY_ENTRIES);
171 }
172
173 protected List<T> getTheirEntries() {
174 return entries.get(THEIR_ENTRIES);
175 }
176
177 public int getMyEntriesSize() {
178 return getMyEntries().size();
179 }
180
181 public int getMergedEntriesSize() {
182 return getMergedEntries().size();
183 }
184
185 public int getTheirEntriesSize() {
186 return getTheirEntries().size();
187 }
188
189 /**
190 * Constructs a new {@code ListMergeModel}.
191 */
192 public AbstractListMergeModel() {
193 entries = new EnumMap<>(ListRole.class);
194 for (ListRole role : ListRole.values()) {
195 entries.put(role, new ArrayList<T>());
196 }
197
198 buildMyEntriesTableModel();
199 buildTheirEntriesTableModel();
200 buildMergedEntriesTableModel();
201
202 myEntriesSelectionModel = new EntriesSelectionModel(entries.get(MY_ENTRIES));
203 theirEntriesSelectionModel = new EntriesSelectionModel(entries.get(THEIR_ENTRIES));
204 mergedEntriesSelectionModel = new EntriesSelectionModel(entries.get(MERGED_ENTRIES));
205
206 listeners = new HashSet<>();
207 comparePairListModel = new ComparePairListModel();
208
209 setFrozen(true);
210 }
211
212 public void addPropertyChangeListener(PropertyChangeListener listener) {
213 synchronized (listeners) {
214 if (listener != null && !listeners.contains(listener)) {
215 listeners.add(listener);
216 }
217 }
218 }
219
220 public void removePropertyChangeListener(PropertyChangeListener listener) {
221 synchronized (listeners) {
222 if (listener != null && listeners.contains(listener)) {
223 listeners.remove(listener);
224 }
225 }
226 }
227
228 protected void fireFrozenChanged(boolean oldValue, boolean newValue) {
229 synchronized (listeners) {
230 PropertyChangeEvent evt = new PropertyChangeEvent(this, FROZEN_PROP, oldValue, newValue);
231 listeners.forEach(listener -> listener.propertyChange(evt));
232 }
233 }
234
235 public final void setFrozen(boolean isFrozen) {
236 boolean oldValue = this.isFrozen;
237 this.isFrozen = isFrozen;
238 fireFrozenChanged(oldValue, this.isFrozen);
239 }
240
241 public final boolean isFrozen() {
242 return isFrozen;
243 }
244
245 public OsmPrimitivesTableModel getMyTableModel() {
246 return myEntriesTableModel;
247 }
248
249 public OsmPrimitivesTableModel getTheirTableModel() {
250 return theirEntriesTableModel;
251 }
252
253 public OsmPrimitivesTableModel getMergedTableModel() {
254 return mergedEntriesTableModel;
255 }
256
257 public EntriesSelectionModel getMySelectionModel() {
258 return myEntriesSelectionModel;
259 }
260
261 public EntriesSelectionModel getTheirSelectionModel() {
262 return theirEntriesSelectionModel;
263 }
264
265 public EntriesSelectionModel getMergedSelectionModel() {
266 return mergedEntriesSelectionModel;
267 }
268
269 protected void fireModelDataChanged() {
270 myEntriesTableModel.fireTableDataChanged();
271 theirEntriesTableModel.fireTableDataChanged();
272 mergedEntriesTableModel.fireTableDataChanged();
273 fireStateChanged();
274 }
275
276 protected void copyToTop(ListRole role, int... rows) {
277 copy(role, rows, 0);
278 mergedEntriesSelectionModel.setSelectionInterval(0, rows.length -1);
279 }
280
281 /**
282 * Copies the nodes given by indices in rows from the list of my nodes to the
283 * list of merged nodes. Inserts the nodes at the top of the list of merged
284 * nodes.
285 *
286 * @param rows the indices
287 */
288 public void copyMyToTop(int... rows) {
289 copyToTop(MY_ENTRIES, rows);
290 }
291
292 /**
293 * Copies the nodes given by indices in rows from the list of their nodes to the
294 * list of merged nodes. Inserts the nodes at the top of the list of merged
295 * nodes.
296 *
297 * @param rows the indices
298 */
299 public void copyTheirToTop(int... rows) {
300 copyToTop(THEIR_ENTRIES, rows);
301 }
302
303 /**
304 * Copies the nodes given by indices in rows from the list of nodes in source to the
305 * list of merged nodes. Inserts the nodes at the end of the list of merged
306 * nodes.
307 *
308 * @param source the list of nodes to copy from
309 * @param rows the indices
310 */
311
312 public void copyToEnd(ListRole source, int... rows) {
313 copy(source, rows, getMergedEntriesSize());
314 mergedEntriesSelectionModel.setSelectionInterval(getMergedEntriesSize()-rows.length, getMergedEntriesSize() -1);
315
316 }
317
318 /**
319 * Copies the nodes given by indices in rows from the list of my nodes to the
320 * list of merged nodes. Inserts the nodes at the end of the list of merged
321 * nodes.
322 *
323 * @param rows the indices
324 */
325 public void copyMyToEnd(int... rows) {
326 copyToEnd(MY_ENTRIES, rows);
327 }
328
329 /**
330 * Copies the nodes given by indices in rows from the list of their nodes to the
331 * list of merged nodes. Inserts the nodes at the end of the list of merged
332 * nodes.
333 *
334 * @param rows the indices
335 */
336 public void copyTheirToEnd(int... rows) {
337 copyToEnd(THEIR_ENTRIES, rows);
338 }
339
340 public void clearMerged() {
341 getMergedEntries().clear();
342 fireModelDataChanged();
343 }
344
345 protected final void initPopulate(OsmPrimitive my, OsmPrimitive their, Map<PrimitiveId, PrimitiveId> mergedMap) {
346 CheckParameterUtil.ensureParameterNotNull(my, "my");
347 CheckParameterUtil.ensureParameterNotNull(their, "their");
348 this.myDataset = my.getDataSet();
349 this.mergedMap = mergedMap;
350 getMergedEntries().clear();
351 getMyEntries().clear();
352 getTheirEntries().clear();
353 }
354
355 protected void alertCopyFailedForDeletedPrimitives(List<PrimitiveId> deletedIds) {
356 List<String> items = new ArrayList<>();
357 for (int i = 0; i < Math.min(MAX_DELETED_PRIMITIVE_IN_DIALOG, deletedIds.size()); i++) {
358 items.add(deletedIds.get(i).toString());
359 }
360 if (deletedIds.size() > MAX_DELETED_PRIMITIVE_IN_DIALOG) {
361 items.add(tr("{0} more...", deletedIds.size() - MAX_DELETED_PRIMITIVE_IN_DIALOG));
362 }
363 StringBuilder sb = new StringBuilder();
364 sb.append("<html>")
365 .append(tr("The following objects could not be copied to the target object<br>because they are deleted in the target dataset:"))
366 .append(Utils.joinAsHtmlUnorderedList(items))
367 .append("</html>");
368 HelpAwareOptionPane.showOptionDialog(
369 Main.parent,
370 sb.toString(),
371 tr("Merging deleted objects failed"),
372 JOptionPane.WARNING_MESSAGE,
373 HelpUtil.ht("/Dialog/Conflict#MergingDeletedPrimitivesFailed")
374 );
375 }
376
377 private void copy(ListRole sourceRole, int[] rows, int position) {
378 if (position < 0 || position > getMergedEntriesSize())
379 throw new IllegalArgumentException("Position must be between 0 and "+getMergedEntriesSize()+" but is "+position);
380 List<T> newItems = new ArrayList<>(rows.length);
381 List<T> source = entries.get(sourceRole);
382 List<PrimitiveId> deletedIds = new ArrayList<>();
383 for (int row: rows) {
384 T entry = source.get(row);
385 OsmPrimitive primitive = getMyPrimitive(entry);
386 if (!primitive.isDeleted()) {
387 T clone = cloneEntryForMergedList(entry);
388 newItems.add(clone);
389 } else {
390 deletedIds.add(primitive.getPrimitiveId());
391 }
392 }
393 getMergedEntries().addAll(position, newItems);
394 fireModelDataChanged();
395 if (!deletedIds.isEmpty()) {
396 alertCopyFailedForDeletedPrimitives(deletedIds);
397 }
398 }
399
400 public void copyAll(ListRole source) {
401 getMergedEntries().clear();
402
403 int[] rows = new int[entries.get(source).size()];
404 for (int i = 0; i < rows.length; i++) {
405 rows[i] = i;
406 }
407 copy(source, rows, 0);
408 }
409
410 /**
411 * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the
412 * list of merged nodes. Inserts the nodes before row given by current.
413 *
414 * @param source the list of nodes to copy from
415 * @param rows the indices
416 * @param current the row index before which the nodes are inserted
417 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
418 */
419 protected void copyBeforeCurrent(ListRole source, int[] rows, int current) {
420 copy(source, rows, current);
421 mergedEntriesSelectionModel.setSelectionInterval(current, current + rows.length-1);
422 }
423
424 /**
425 * Copies the nodes given by indices in rows from the list of my nodes to the
426 * list of merged nodes. Inserts the nodes before row given by current.
427 *
428 * @param rows the indices
429 * @param current the row index before which the nodes are inserted
430 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
431 */
432 public void copyMyBeforeCurrent(int[] rows, int current) {
433 copyBeforeCurrent(MY_ENTRIES, rows, current);
434 }
435
436 /**
437 * Copies the nodes given by indices in rows from the list of their nodes to the
438 * list of merged nodes. Inserts the nodes before row given by current.
439 *
440 * @param rows the indices
441 * @param current the row index before which the nodes are inserted
442 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
443 */
444 public void copyTheirBeforeCurrent(int[] rows, int current) {
445 copyBeforeCurrent(THEIR_ENTRIES, rows, current);
446 }
447
448 /**
449 * Copies the nodes given by indices in rows from the list of nodes <code>source</code> to the
450 * list of merged nodes. Inserts the nodes after the row given by current.
451 *
452 * @param source the list of nodes to copy from
453 * @param rows the indices
454 * @param current the row index after which the nodes are inserted
455 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
456 */
457 protected void copyAfterCurrent(ListRole source, int[] rows, int current) {
458 copy(source, rows, current + 1);
459 mergedEntriesSelectionModel.setSelectionInterval(current+1, current + rows.length-1);
460 fireStateChanged();
461 }
462
463 /**
464 * Copies the nodes given by indices in rows from the list of my nodes to the
465 * list of merged nodes. Inserts the nodes after the row given by current.
466 *
467 * @param rows the indices
468 * @param current the row index after which the nodes are inserted
469 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
470 */
471 public void copyMyAfterCurrent(int[] rows, int current) {
472 copyAfterCurrent(MY_ENTRIES, rows, current);
473 }
474
475 /**
476 * Copies the nodes given by indices in rows from the list of my nodes to the
477 * list of merged nodes. Inserts the nodes after the row given by current.
478 *
479 * @param rows the indices
480 * @param current the row index after which the nodes are inserted
481 * @throws IllegalArgumentException if current &lt; 0 or &gt;= #nodes in list of merged nodes
482 */
483 public void copyTheirAfterCurrent(int[] rows, int current) {
484 copyAfterCurrent(THEIR_ENTRIES, rows, current);
485 }
486
487 /**
488 * Moves the nodes given by indices in rows up by one position in the list
489 * of merged nodes.
490 *
491 * @param rows the indices
492 *
493 */
494 public void moveUpMerged(int... rows) {
495 if (rows == null || rows.length == 0)
496 return;
497 if (rows[0] == 0)
498 // can't move up
499 return;
500 List<T> mergedEntries = getMergedEntries();
501 for (int row: rows) {
502 T n = mergedEntries.get(row);
503 mergedEntries.remove(row);
504 mergedEntries.add(row -1, n);
505 }
506 fireModelDataChanged();
507 mergedEntriesSelectionModel.setValueIsAdjusting(true);
508 mergedEntriesSelectionModel.clearSelection();
509 for (int row: rows) {
510 mergedEntriesSelectionModel.addSelectionInterval(row-1, row-1);
511 }
512 mergedEntriesSelectionModel.setValueIsAdjusting(false);
513 }
514
515 /**
516 * Moves the nodes given by indices in rows down by one position in the list
517 * of merged nodes.
518 *
519 * @param rows the indices
520 */
521 public void moveDownMerged(int... rows) {
522 if (rows == null || rows.length == 0)
523 return;
524 List<T> mergedEntries = getMergedEntries();
525 if (rows[rows.length -1] == mergedEntries.size() -1)
526 // can't move down
527 return;
528 for (int i = rows.length-1; i >= 0; i--) {
529 int row = rows[i];
530 T n = mergedEntries.get(row);
531 mergedEntries.remove(row);
532 mergedEntries.add(row +1, n);
533 }
534 fireModelDataChanged();
535 mergedEntriesSelectionModel.setValueIsAdjusting(true);
536 mergedEntriesSelectionModel.clearSelection();
537 for (int row: rows) {
538 mergedEntriesSelectionModel.addSelectionInterval(row+1, row+1);
539 }
540 mergedEntriesSelectionModel.setValueIsAdjusting(false);
541 }
542
543 /**
544 * Removes the nodes given by indices in rows from the list
545 * of merged nodes.
546 *
547 * @param rows the indices
548 */
549 public void removeMerged(int... rows) {
550 if (rows == null || rows.length == 0)
551 return;
552
553 List<T> mergedEntries = getMergedEntries();
554
555 for (int i = rows.length-1; i >= 0; i--) {
556 mergedEntries.remove(rows[i]);
557 }
558 fireModelDataChanged();
559 mergedEntriesSelectionModel.clearSelection();
560 }
561
562 /**
563 * Replies true if the list of my entries and the list of their
564 * entries are equal
565 *
566 * @return true, if the lists are equal; false otherwise
567 */
568 protected boolean myAndTheirEntriesEqual() {
569
570 if (getMyEntriesSize() != getTheirEntriesSize())
571 return false;
572 for (int i = 0; i < getMyEntriesSize(); i++) {
573 if (!isEqualEntry(getMyEntries().get(i), getTheirEntries().get(i)))
574 return false;
575 }
576 return true;
577 }
578
579 /**
580 * This an adapter between a {@link JTable} and one of the three entry lists
581 * in the role {@link ListRole} managed by the {@link AbstractListMergeModel}.
582 *
583 * From the point of view of the {@link JTable} it is a {@link TableModel}.
584 *
585 * @see AbstractListMergeModel#getMyTableModel()
586 * @see AbstractListMergeModel#getTheirTableModel()
587 * @see AbstractListMergeModel#getMergedTableModel()
588 */
589 public class EntriesTableModel extends DefaultTableModel implements OsmPrimitivesTableModel {
590 private final ListRole role;
591
592 /**
593 *
594 * @param role the role
595 */
596 public EntriesTableModel(ListRole role) {
597 this.role = role;
598 }
599
600 @Override
601 public int getRowCount() {
602 int count = Math.max(getMyEntries().size(), getMergedEntries().size());
603 return Math.max(count, getTheirEntries().size());
604 }
605
606 @Override
607 public Object getValueAt(int row, int column) {
608 if (row < entries.get(role).size())
609 return entries.get(role).get(row);
610 return null;
611 }
612
613 @Override
614 public boolean isCellEditable(int row, int column) {
615 return false;
616 }
617
618 @Override
619 public void setValueAt(Object value, int row, int col) {
620 AbstractListMergeModel.this.setValueAt(this, value, row, col);
621 }
622
623 /**
624 * Returns the list merge model.
625 * @return the list merge model
626 */
627 public AbstractListMergeModel<T, C> getListMergeModel() {
628 return AbstractListMergeModel.this;
629 }
630
631 /**
632 * replies true if the {@link ListRole} of this {@link EntriesTableModel}
633 * participates in the current {@link ComparePairType}
634 *
635 * @return true, if the if the {@link ListRole} of this {@link EntriesTableModel}
636 * participates in the current {@link ComparePairType}
637 *
638 * @see AbstractListMergeModel.ComparePairListModel#getSelectedComparePair()
639 */
640 public boolean isParticipatingInCurrentComparePair() {
641 return getComparePairListModel()
642 .getSelectedComparePair()
643 .isParticipatingIn(role);
644 }
645
646 /**
647 * replies true if the entry at <code>row</code> is equal to the entry at the
648 * same position in the opposite list of the current {@link ComparePairType}.
649 *
650 * @param row the row number
651 * @return true if the entry at <code>row</code> is equal to the entry at the
652 * same position in the opposite list of the current {@link ComparePairType}
653 * @throws IllegalStateException if this model is not participating in the
654 * current {@link ComparePairType}
655 * @see ComparePairType#getOppositeRole(ListRole)
656 * @see #getRole()
657 * @see #getOppositeEntries()
658 */
659 public boolean isSamePositionInOppositeList(int row) {
660 if (!isParticipatingInCurrentComparePair())
661 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString()));
662 if (row >= getEntries().size()) return false;
663 if (row >= getOppositeEntries().size()) return false;
664
665 T e1 = getEntries().get(row);
666 T e2 = getOppositeEntries().get(row);
667 return isEqualEntry(e1, e2);
668 }
669
670 /**
671 * replies true if the entry at the current position is present in the opposite list
672 * of the current {@link ComparePairType}.
673 *
674 * @param row the current row
675 * @return true if the entry at the current position is present in the opposite list
676 * of the current {@link ComparePairType}.
677 * @throws IllegalStateException if this model is not participating in the
678 * current {@link ComparePairType}
679 * @see ComparePairType#getOppositeRole(ListRole)
680 * @see #getRole()
681 * @see #getOppositeEntries()
682 */
683 public boolean isIncludedInOppositeList(int row) {
684 if (!isParticipatingInCurrentComparePair())
685 throw new IllegalStateException(tr("List in role {0} is currently not participating in a compare pair.", role.toString()));
686
687 if (row >= getEntries().size()) return false;
688 T e1 = getEntries().get(row);
689 return getOppositeEntries().stream().anyMatch(e2 -> isEqualEntry(e1, e2));
690 }
691
692 protected List<T> getEntries() {
693 return entries.get(role);
694 }
695
696 /**
697 * replies the opposite list of entries with respect to the current {@link ComparePairType}
698 *
699 * @return the opposite list of entries
700 */
701 protected List<T> getOppositeEntries() {
702 ListRole opposite = getComparePairListModel().getSelectedComparePair().getOppositeRole(role);
703 return entries.get(opposite);
704 }
705
706 public ListRole getRole() {
707 return role;
708 }
709
710 @Override
711 public OsmPrimitive getReferredPrimitive(int idx) {
712 Object value = getValueAt(idx, 1);
713 if (value instanceof OsmPrimitive) {
714 return (OsmPrimitive) value;
715 } else if (value instanceof RelationMember) {
716 return ((RelationMember) value).getMember();
717 } else {
718 Main.error("Unknown object type: "+value);
719 return null;
720 }
721 }
722 }
723
724 /**
725 * This is the selection model to be used in a {@link JTable} which displays
726 * an entry list managed by {@link AbstractListMergeModel}.
727 *
728 * The model ensures that only rows displaying an entry in the entry list
729 * can be selected. "Empty" rows can't be selected.
730 *
731 * @see AbstractListMergeModel#getMySelectionModel()
732 * @see AbstractListMergeModel#getMergedSelectionModel()
733 * @see AbstractListMergeModel#getTheirSelectionModel()
734 *
735 */
736 protected class EntriesSelectionModel extends DefaultListSelectionModel {
737 private final transient List<T> entries;
738
739 public EntriesSelectionModel(List<T> nodes) {
740 this.entries = nodes;
741 }
742
743 @Override
744 public void addSelectionInterval(int index0, int index1) {
745 if (entries.isEmpty()) return;
746 if (index0 > entries.size() - 1) return;
747 index0 = Math.min(entries.size()-1, index0);
748 index1 = Math.min(entries.size()-1, index1);
749 super.addSelectionInterval(index0, index1);
750 }
751
752 @Override
753 public void insertIndexInterval(int index, int length, boolean before) {
754 if (entries.isEmpty()) return;
755 if (before) {
756 int newindex = Math.min(entries.size()-1, index);
757 if (newindex < index - length) return;
758 length = length - (index - newindex);
759 super.insertIndexInterval(newindex, length, before);
760 } else {
761 if (index > entries.size() -1) return;
762 length = Math.min(entries.size()-1 - index, length);
763 super.insertIndexInterval(index, length, before);
764 }
765 }
766
767 @Override
768 public void moveLeadSelectionIndex(int leadIndex) {
769 if (entries.isEmpty()) return;
770 leadIndex = Math.max(0, leadIndex);
771 leadIndex = Math.min(entries.size() - 1, leadIndex);
772 super.moveLeadSelectionIndex(leadIndex);
773 }
774
775 @Override
776 public void removeIndexInterval(int index0, int index1) {
777 if (entries.isEmpty()) return;
778 index0 = Math.max(0, index0);
779 index0 = Math.min(entries.size() - 1, index0);
780
781 index1 = Math.max(0, index1);
782 index1 = Math.min(entries.size() - 1, index1);
783 super.removeIndexInterval(index0, index1);
784 }
785
786 @Override
787 public void removeSelectionInterval(int index0, int index1) {
788 if (entries.isEmpty()) return;
789 index0 = Math.max(0, index0);
790 index0 = Math.min(entries.size() - 1, index0);
791
792 index1 = Math.max(0, index1);
793 index1 = Math.min(entries.size() - 1, index1);
794 super.removeSelectionInterval(index0, index1);
795 }
796
797 @Override
798 public void setAnchorSelectionIndex(int anchorIndex) {
799 if (entries.isEmpty()) return;
800 anchorIndex = Math.min(entries.size() - 1, anchorIndex);
801 super.setAnchorSelectionIndex(anchorIndex);
802 }
803
804 @Override
805 public void setLeadSelectionIndex(int leadIndex) {
806 if (entries.isEmpty()) return;
807 leadIndex = Math.min(entries.size() - 1, leadIndex);
808 super.setLeadSelectionIndex(leadIndex);
809 }
810
811 @Override
812 public void setSelectionInterval(int index0, int index1) {
813 if (entries.isEmpty()) return;
814 index0 = Math.max(0, index0);
815 index0 = Math.min(entries.size() - 1, index0);
816
817 index1 = Math.max(0, index1);
818 index1 = Math.min(entries.size() - 1, index1);
819
820 super.setSelectionInterval(index0, index1);
821 }
822 }
823
824 public ComparePairListModel getComparePairListModel() {
825 return this.comparePairListModel;
826 }
827
828 public class ComparePairListModel extends AbstractListModel<ComparePairType> implements ComboBoxModel<ComparePairType> {
829
830 private int selectedIdx;
831 private final List<ComparePairType> compareModes;
832
833 /**
834 * Constructs a new {@code ComparePairListModel}.
835 */
836 public ComparePairListModel() {
837 this.compareModes = new ArrayList<>();
838 compareModes.add(MY_WITH_THEIR);
839 compareModes.add(MY_WITH_MERGED);
840 compareModes.add(THEIR_WITH_MERGED);
841 selectedIdx = 0;
842 }
843
844 @Override
845 public ComparePairType getElementAt(int index) {
846 if (index < compareModes.size())
847 return compareModes.get(index);
848 throw new IllegalArgumentException(tr("Unexpected value of parameter ''index''. Got {0}.", index));
849 }
850
851 @Override
852 public int getSize() {
853 return compareModes.size();
854 }
855
856 @Override
857 public Object getSelectedItem() {
858 return compareModes.get(selectedIdx);
859 }
860
861 @Override
862 public void setSelectedItem(Object anItem) {
863 int i = compareModes.indexOf(anItem);
864 if (i < 0)
865 throw new IllegalStateException(tr("Item {0} not found in list.", anItem));
866 selectedIdx = i;
867 fireModelDataChanged();
868 }
869
870 public ComparePairType getSelectedComparePair() {
871 return compareModes.get(selectedIdx);
872 }
873 }
874
875 /**
876 * Builds the command to resolve conflicts in the list.
877 *
878 * @param conflict the conflict data set
879 * @return the command
880 * @throws IllegalStateException if the merge is not yet frozen
881 */
882 public abstract C buildResolveCommand(Conflict<? extends OsmPrimitive> conflict);
883}
Note: See TracBrowser for help on using the repository browser.