001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.relation;
003
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.BitSet;
007import java.util.Collection;
008import java.util.Collections;
009import java.util.EnumSet;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Set;
013import java.util.TreeSet;
014import java.util.concurrent.CopyOnWriteArrayList;
015import java.util.stream.Collectors;
016
017import javax.swing.DefaultListSelectionModel;
018import javax.swing.ListSelectionModel;
019import javax.swing.event.TableModelEvent;
020import javax.swing.event.TableModelListener;
021import javax.swing.table.AbstractTableModel;
022
023import org.openstreetmap.josm.data.SelectionChangedListener;
024import org.openstreetmap.josm.data.osm.DataSet;
025import org.openstreetmap.josm.data.osm.OsmPrimitive;
026import org.openstreetmap.josm.data.osm.Relation;
027import org.openstreetmap.josm.data.osm.RelationMember;
028import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
029import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
030import org.openstreetmap.josm.data.osm.event.DataSetListener;
031import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
032import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
033import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
034import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
035import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
036import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
037import org.openstreetmap.josm.gui.MainApplication;
038import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter;
039import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
040import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
041import org.openstreetmap.josm.gui.layer.OsmDataLayer;
042import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
043import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
044import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
045import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
046import org.openstreetmap.josm.gui.util.GuiHelper;
047import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
048import org.openstreetmap.josm.tools.JosmRuntimeException;
049import org.openstreetmap.josm.tools.bugreport.BugReport;
050
051/**
052 * This is the base model used for the {@link MemberTable}. It holds the member data.
053 */
054public class MemberTableModel extends AbstractTableModel
055implements TableModelListener, SelectionChangedListener, DataSetListener, OsmPrimitivesTableModel {
056
057    /**
058     * data of the table model: The list of members and the cached WayConnectionType of each member.
059     **/
060    private final transient List<RelationMember> members;
061    private transient List<WayConnectionType> connectionType;
062    private final transient Relation relation;
063
064    private DefaultListSelectionModel listSelectionModel;
065    private final transient CopyOnWriteArrayList<IMemberModelListener> listeners;
066    private final transient OsmDataLayer layer;
067    private final transient TaggingPresetHandler presetHandler;
068
069    private final transient WayConnectionTypeCalculator wayConnectionTypeCalculator = new WayConnectionTypeCalculator();
070    private final transient RelationSorter relationSorter = new RelationSorter();
071
072    /**
073     * constructor
074     * @param relation relation
075     * @param layer data layer
076     * @param presetHandler tagging preset handler
077     */
078    public MemberTableModel(Relation relation, OsmDataLayer layer, TaggingPresetHandler presetHandler) {
079        this.relation = relation;
080        this.members = new ArrayList<>();
081        this.listeners = new CopyOnWriteArrayList<>();
082        this.layer = layer;
083        this.presetHandler = presetHandler;
084        addTableModelListener(this);
085    }
086
087    /**
088     * Returns the data layer.
089     * @return the data layer
090     */
091    public OsmDataLayer getLayer() {
092        return layer;
093    }
094
095    /**
096     * Registers listeners (selection change and dataset change).
097     */
098    public void register() {
099        DataSet.addSelectionListener(this);
100        getLayer().data.addDataSetListener(this);
101    }
102
103    /**
104     * Unregisters listeners (selection change and dataset change).
105     */
106    public void unregister() {
107        DataSet.removeSelectionListener(this);
108        getLayer().data.removeDataSetListener(this);
109    }
110
111    /* --------------------------------------------------------------------------- */
112    /* Interface SelectionChangedListener                                          */
113    /* --------------------------------------------------------------------------- */
114    @Override
115    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
116        if (MainApplication.getLayerManager().getActiveDataLayer() != this.layer) return;
117        // just trigger a repaint
118        Collection<RelationMember> sel = getSelectedMembers();
119        fireTableDataChanged();
120        setSelectedMembers(sel);
121    }
122
123    /* --------------------------------------------------------------------------- */
124    /* Interface DataSetListener                                                   */
125    /* --------------------------------------------------------------------------- */
126    @Override
127    public void dataChanged(DataChangedEvent event) {
128        // just trigger a repaint - the display name of the relation members may have changed
129        Collection<RelationMember> sel = getSelectedMembers();
130        GuiHelper.runInEDT(this::fireTableDataChanged);
131        setSelectedMembers(sel);
132    }
133
134    @Override
135    public void nodeMoved(NodeMovedEvent event) {
136        // ignore
137    }
138
139    @Override
140    public void primitivesAdded(PrimitivesAddedEvent event) {
141        // ignore
142    }
143
144    @Override
145    public void primitivesRemoved(PrimitivesRemovedEvent event) {
146        // ignore - the relation in the editor might become out of sync with the relation
147        // in the dataset. We will deal with it when the relation editor is closed or
148        // when the changes in the editor are applied.
149    }
150
151    @Override
152    public void relationMembersChanged(RelationMembersChangedEvent event) {
153        // ignore - the relation in the editor might become out of sync with the relation
154        // in the dataset. We will deal with it when the relation editor is closed or
155        // when the changes in the editor are applied.
156    }
157
158    @Override
159    public void tagsChanged(TagsChangedEvent event) {
160        // just refresh the respective table cells
161        //
162        Collection<RelationMember> sel = getSelectedMembers();
163        for (int i = 0; i < members.size(); i++) {
164            if (members.get(i).getMember() == event.getPrimitive()) {
165                fireTableCellUpdated(i, 1 /* the column with the primitive name */);
166            }
167        }
168        setSelectedMembers(sel);
169    }
170
171    @Override
172    public void wayNodesChanged(WayNodesChangedEvent event) {
173        // ignore
174    }
175
176    @Override
177    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
178        // ignore
179    }
180
181    /* --------------------------------------------------------------------------- */
182
183    public void addMemberModelListener(IMemberModelListener listener) {
184        if (listener != null) {
185            listeners.addIfAbsent(listener);
186        }
187    }
188
189    public void removeMemberModelListener(IMemberModelListener listener) {
190        listeners.remove(listener);
191    }
192
193    protected void fireMakeMemberVisible(int index) {
194        for (IMemberModelListener listener : listeners) {
195            listener.makeMemberVisible(index);
196        }
197    }
198
199    /**
200     * Populates this model from the given relation.
201     * @param relation relation
202     */
203    public void populate(Relation relation) {
204        members.clear();
205        if (relation != null) {
206            // make sure we work with clones of the relation members in the model.
207            members.addAll(new Relation(relation).getMembers());
208        }
209        fireTableDataChanged();
210    }
211
212    @Override
213    public int getColumnCount() {
214        return 3;
215    }
216
217    @Override
218    public int getRowCount() {
219        return members.size();
220    }
221
222    @Override
223    public Object getValueAt(int rowIndex, int columnIndex) {
224        switch (columnIndex) {
225        case 0:
226            return members.get(rowIndex).getRole();
227        case 1:
228            return members.get(rowIndex).getMember();
229        case 2:
230            return getWayConnection(rowIndex);
231        }
232        // should not happen
233        return null;
234    }
235
236    @Override
237    public boolean isCellEditable(int rowIndex, int columnIndex) {
238        return columnIndex == 0;
239    }
240
241    @Override
242    public void setValueAt(Object value, int rowIndex, int columnIndex) {
243        // fix #10524 - IndexOutOfBoundsException: Index: 2, Size: 2
244        if (rowIndex >= members.size()) {
245            return;
246        }
247        RelationMember member = members.get(rowIndex);
248        String role = value.toString();
249        if (member.hasRole(role))
250            return;
251        RelationMember newMember = new RelationMember(role, member.getMember());
252        members.remove(rowIndex);
253        members.add(rowIndex, newMember);
254        fireTableDataChanged();
255    }
256
257    @Override
258    public OsmPrimitive getReferredPrimitive(int idx) {
259        return members.get(idx).getMember();
260    }
261
262    public void moveUp(int... selectedRows) {
263        if (!canMoveUp(selectedRows))
264            return;
265
266        for (int row : selectedRows) {
267            RelationMember member1 = members.get(row);
268            RelationMember member2 = members.get(row - 1);
269            members.set(row, member2);
270            members.set(row - 1, member1);
271        }
272        fireTableDataChanged();
273        getSelectionModel().setValueIsAdjusting(true);
274        getSelectionModel().clearSelection();
275        BitSet selected = new BitSet();
276        for (int row : selectedRows) {
277            row--;
278            selected.set(row);
279        }
280        addToSelectedMembers(selected);
281        getSelectionModel().setValueIsAdjusting(false);
282        fireMakeMemberVisible(selectedRows[0] - 1);
283    }
284
285    public void moveDown(int... selectedRows) {
286        if (!canMoveDown(selectedRows))
287            return;
288
289        for (int i = selectedRows.length - 1; i >= 0; i--) {
290            int row = selectedRows[i];
291            RelationMember member1 = members.get(row);
292            RelationMember member2 = members.get(row + 1);
293            members.set(row, member2);
294            members.set(row + 1, member1);
295        }
296        fireTableDataChanged();
297        getSelectionModel();
298        getSelectionModel().setValueIsAdjusting(true);
299        getSelectionModel().clearSelection();
300        BitSet selected = new BitSet();
301        for (int row : selectedRows) {
302            row++;
303            selected.set(row);
304        }
305        addToSelectedMembers(selected);
306        getSelectionModel().setValueIsAdjusting(false);
307        fireMakeMemberVisible(selectedRows[0] + 1);
308    }
309
310    public void remove(int... selectedRows) {
311        if (!canRemove(selectedRows))
312            return;
313        int offset = 0;
314        for (int row : selectedRows) {
315            row -= offset;
316            if (members.size() > row) {
317                members.remove(row);
318                offset++;
319            }
320        }
321        fireTableDataChanged();
322    }
323
324    public boolean canMoveUp(int... rows) {
325        if (rows == null || rows.length == 0)
326            return false;
327        Arrays.sort(rows);
328        return rows[0] > 0 && !members.isEmpty();
329    }
330
331    public boolean canMoveDown(int... rows) {
332        if (rows == null || rows.length == 0)
333            return false;
334        Arrays.sort(rows);
335        return !members.isEmpty() && rows[rows.length - 1] < members.size() - 1;
336    }
337
338    public boolean canRemove(int... rows) {
339        return rows != null && rows.length != 0;
340    }
341
342    public DefaultListSelectionModel getSelectionModel() {
343        if (listSelectionModel == null) {
344            listSelectionModel = new DefaultListSelectionModel();
345            listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
346        }
347        return listSelectionModel;
348    }
349
350    public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) {
351        if (primitives == null)
352            return;
353        members.removeIf(member -> primitives.contains(member.getMember()));
354        fireTableDataChanged();
355    }
356
357    /**
358     * Applies this member model to the given relation.
359     * @param relation relation
360     */
361    public void applyToRelation(Relation relation) {
362        relation.setMembers(members.stream()
363                .filter(rm -> !rm.getMember().isDeleted()).collect(Collectors.toList()));
364    }
365
366    public boolean hasSameMembersAs(Relation relation) {
367        if (relation == null)
368            return false;
369        if (relation.getMembersCount() != members.size())
370            return false;
371        for (int i = 0; i < relation.getMembersCount(); i++) {
372            if (!relation.getMember(i).equals(members.get(i)))
373                return false;
374        }
375        return true;
376    }
377
378    /**
379     * Replies the set of incomplete primitives
380     *
381     * @return the set of incomplete primitives
382     */
383    public Set<OsmPrimitive> getIncompleteMemberPrimitives() {
384        Set<OsmPrimitive> ret = new HashSet<>();
385        for (RelationMember member : members) {
386            if (member.getMember().isIncomplete()) {
387                ret.add(member.getMember());
388            }
389        }
390        return ret;
391    }
392
393    /**
394     * Replies the set of selected incomplete primitives
395     *
396     * @return the set of selected incomplete primitives
397     */
398    public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() {
399        Set<OsmPrimitive> ret = new HashSet<>();
400        for (RelationMember member : getSelectedMembers()) {
401            if (member.getMember().isIncomplete()) {
402                ret.add(member.getMember());
403            }
404        }
405        return ret;
406    }
407
408    /**
409     * Replies true if at least one the relation members is incomplete
410     *
411     * @return true if at least one the relation members is incomplete
412     */
413    public boolean hasIncompleteMembers() {
414        for (RelationMember member : members) {
415            if (member.getMember().isIncomplete())
416                return true;
417        }
418        return false;
419    }
420
421    /**
422     * Replies true if at least one of the selected members is incomplete
423     *
424     * @return true if at least one of the selected members is incomplete
425     */
426    public boolean hasIncompleteSelectedMembers() {
427        for (RelationMember member : getSelectedMembers()) {
428            if (member.getMember().isIncomplete())
429                return true;
430        }
431        return false;
432    }
433
434    protected List<Integer> getSelectedIndices() {
435        List<Integer> selectedIndices = new ArrayList<>();
436        for (int i = 0; i < members.size(); i++) {
437            if (getSelectionModel().isSelectedIndex(i)) {
438                selectedIndices.add(i);
439            }
440        }
441        return selectedIndices;
442    }
443
444    private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) {
445        if (primitives == null)
446            return;
447        int idx = index;
448        for (OsmPrimitive primitive : primitives) {
449            final RelationMember member = getRelationMemberForPrimitive(primitive);
450            members.add(idx++, member);
451        }
452        fireTableDataChanged();
453        getSelectionModel().clearSelection();
454        getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1);
455        fireMakeMemberVisible(index);
456    }
457
458    RelationMember getRelationMemberForPrimitive(final OsmPrimitive primitive) {
459        final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(
460                EnumSet.of(relation != null ? TaggingPresetType.forPrimitive(relation) : TaggingPresetType.RELATION),
461                presetHandler.getSelection().iterator().next().getKeys(), false);
462        Collection<String> potentialRoles = new TreeSet<>();
463        for (TaggingPreset tp : presets) {
464            String suggestedRole = tp.suggestRoleForOsmPrimitive(primitive);
465            if (suggestedRole != null) {
466                potentialRoles.add(suggestedRole);
467            }
468        }
469        // TODO: propose user to choose role among potential ones instead of picking first one
470        final String role = potentialRoles.isEmpty() ? "" : potentialRoles.iterator().next();
471        return new RelationMember(role == null ? "" : role, primitive);
472    }
473
474    void addMembersAtIndexKeepingOldSelection(final Iterable<RelationMember> newMembers, final int index) {
475        int idx = index;
476        for (RelationMember member : newMembers) {
477            members.add(idx++, member);
478        }
479        invalidateConnectionType();
480        fireTableRowsInserted(index, idx - 1);
481    }
482
483    public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) {
484        addMembersAtIndex(primitives, 0);
485    }
486
487    public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) {
488        addMembersAtIndex(primitives, members.size());
489    }
490
491    public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) {
492        addMembersAtIndex(primitives, idx);
493    }
494
495    public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) {
496        addMembersAtIndex(primitives, idx + 1);
497    }
498
499    /**
500     * Replies the number of members which refer to a particular primitive
501     *
502     * @param primitive the primitive
503     * @return the number of members which refer to a particular primitive
504     */
505    public int getNumMembersWithPrimitive(OsmPrimitive primitive) {
506        int count = 0;
507        for (RelationMember member : members) {
508            if (member.getMember().equals(primitive)) {
509                count++;
510            }
511        }
512        return count;
513    }
514
515    /**
516     * updates the role of the members given by the indices in <code>idx</code>
517     *
518     * @param idx the array of indices
519     * @param role the new role
520     */
521    public void updateRole(int[] idx, String role) {
522        if (idx == null || idx.length == 0)
523            return;
524        for (int row : idx) {
525            // fix #7885 - IndexOutOfBoundsException: Index: 39, Size: 39
526            if (row >= members.size()) {
527                continue;
528            }
529            RelationMember oldMember = members.get(row);
530            RelationMember newMember = new RelationMember(role, oldMember.getMember());
531            members.remove(row);
532            members.add(row, newMember);
533        }
534        fireTableDataChanged();
535        BitSet selected = new BitSet();
536        for (int row : idx) {
537            selected.set(row);
538        }
539        addToSelectedMembers(selected);
540    }
541
542    /**
543     * Get the currently selected relation members
544     *
545     * @return a collection with the currently selected relation members
546     */
547    public Collection<RelationMember> getSelectedMembers() {
548        List<RelationMember> selectedMembers = new ArrayList<>();
549        for (int i : getSelectedIndices()) {
550            selectedMembers.add(members.get(i));
551        }
552        return selectedMembers;
553    }
554
555    /**
556     * Replies the set of selected referers. Never null, but may be empty.
557     *
558     * @return the set of selected referers
559     */
560    public Collection<OsmPrimitive> getSelectedChildPrimitives() {
561        Collection<OsmPrimitive> ret = new ArrayList<>();
562        for (RelationMember m: getSelectedMembers()) {
563            ret.add(m.getMember());
564        }
565        return ret;
566    }
567
568    /**
569     * Replies the set of selected referers. Never null, but may be empty.
570     * @param referenceSet reference set
571     *
572     * @return the set of selected referers
573     */
574    public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) {
575        Set<OsmPrimitive> ret = new HashSet<>();
576        if (referenceSet == null) return null;
577        for (RelationMember m: members) {
578            if (referenceSet.contains(m.getMember())) {
579                ret.add(m.getMember());
580            }
581        }
582        return ret;
583    }
584
585    /**
586     * Selects the members in the collection selectedMembers
587     *
588     * @param selectedMembers the collection of selected members
589     */
590    public void setSelectedMembers(Collection<RelationMember> selectedMembers) {
591        if (selectedMembers == null || selectedMembers.isEmpty()) {
592            getSelectionModel().clearSelection();
593            return;
594        }
595
596        // lookup the indices for the respective members
597        //
598        Set<Integer> selectedIndices = new HashSet<>();
599        for (RelationMember member : selectedMembers) {
600            for (int idx = 0; idx < members.size(); ++idx) {
601                if (member.equals(members.get(idx))) {
602                    selectedIndices.add(idx);
603                }
604            }
605        }
606        setSelectedMembersIdx(selectedIndices);
607    }
608
609    /**
610     * Selects the members in the collection selectedIndices
611     *
612     * @param selectedIndices the collection of selected member indices
613     */
614    public void setSelectedMembersIdx(Collection<Integer> selectedIndices) {
615        if (selectedIndices == null || selectedIndices.isEmpty()) {
616            getSelectionModel().clearSelection();
617            return;
618        }
619        // select the members
620        //
621        getSelectionModel().setValueIsAdjusting(true);
622        getSelectionModel().clearSelection();
623        BitSet selected = new BitSet();
624        for (int row : selectedIndices) {
625            selected.set(row);
626        }
627        addToSelectedMembers(selected);
628        getSelectionModel().setValueIsAdjusting(false);
629        // make the first selected member visible
630        //
631        if (!selectedIndices.isEmpty()) {
632            fireMakeMemberVisible(Collections.min(selectedIndices));
633        }
634    }
635
636    /**
637     * Add one or more members indices to the selection.
638     * Detect groups of consecutive indices.
639     * Only one costly call of addSelectionInterval is performed for each group
640
641     * @param selectedIndices selected indices as a bitset
642     * @return number of groups
643     */
644    private int addToSelectedMembers(BitSet selectedIndices) {
645        if (selectedIndices == null || selectedIndices.isEmpty()) {
646            return 0;
647        }
648        // select the members
649        //
650        int start = selectedIndices.nextSetBit(0);
651        int end;
652        int steps = 0;
653        int last = selectedIndices.length();
654        while (start >= 0) {
655            end = selectedIndices.nextClearBit(start);
656            steps++;
657            getSelectionModel().addSelectionInterval(start, end-1);
658            start = selectedIndices.nextSetBit(end);
659            if (start < 0 || end == last)
660                break;
661        }
662        return steps;
663    }
664
665    /**
666     * Replies true if the index-th relation members refers
667     * to an editable relation, i.e. a relation which is not
668     * incomplete.
669     *
670     * @param index the index
671     * @return true, if the index-th relation members refers
672     * to an editable relation, i.e. a relation which is not
673     * incomplete
674     */
675    public boolean isEditableRelation(int index) {
676        if (index < 0 || index >= members.size())
677            return false;
678        RelationMember member = members.get(index);
679        if (!member.isRelation())
680            return false;
681        Relation r = member.getRelation();
682        return !r.isIncomplete();
683    }
684
685    /**
686     * Replies true if there is at least one relation member given as {@code members}
687     * which refers to at least on the primitives in {@code primitives}.
688     *
689     * @param members the members
690     * @param primitives the collection of primitives
691     * @return true if there is at least one relation member in this model
692     * which refers to at least on the primitives in <code>primitives</code>; false
693     * otherwise
694     */
695    public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) {
696        if (primitives == null || primitives.isEmpty())
697            return false;
698        Set<OsmPrimitive> referrers = new HashSet<>();
699        for (RelationMember member : members) {
700            referrers.add(member.getMember());
701        }
702        for (OsmPrimitive referred : primitives) {
703            if (referrers.contains(referred))
704                return true;
705        }
706        return false;
707    }
708
709    /**
710     * Replies true if there is at least one relation member in this model
711     * which refers to at least on the primitives in <code>primitives</code>.
712     *
713     * @param primitives the collection of primitives
714     * @return true if there is at least one relation member in this model
715     * which refers to at least on the primitives in <code>primitives</code>; false
716     * otherwise
717     */
718    public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) {
719        return hasMembersReferringTo(members, primitives);
720    }
721
722    /**
723     * Selects all members which refer to {@link OsmPrimitive}s in the collections
724     * <code>primitmives</code>. Does nothing is primitives is null.
725     *
726     * @param primitives the collection of primitives
727     */
728    public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) {
729        if (primitives == null) return;
730        getSelectionModel().setValueIsAdjusting(true);
731        getSelectionModel().clearSelection();
732        BitSet selected = new BitSet();
733        for (int i = 0; i < members.size(); i++) {
734            RelationMember m = members.get(i);
735            if (primitives.contains(m.getMember())) {
736                selected.set(i);
737            }
738        }
739        addToSelectedMembers(selected);
740        getSelectionModel().setValueIsAdjusting(false);
741        if (!getSelectedIndices().isEmpty()) {
742            fireMakeMemberVisible(getSelectedIndices().get(0));
743        }
744    }
745
746    /**
747     * Replies true if <code>primitive</code> is currently selected in the layer this
748     * model is attached to
749     *
750     * @param primitive the primitive
751     * @return true if <code>primitive</code> is currently selected in the layer this
752     * model is attached to, false otherwise
753     */
754    public boolean isInJosmSelection(OsmPrimitive primitive) {
755        return layer.data.isSelected(primitive);
756    }
757
758    /**
759     * Sort the selected relation members by the way they are linked.
760     */
761    public void sort() {
762        List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers());
763        List<RelationMember> sortedMembers;
764        List<RelationMember> newMembers;
765        if (selectedMembers.size() <= 1) {
766            newMembers = relationSorter.sortMembers(members);
767            sortedMembers = newMembers;
768        } else {
769            sortedMembers = relationSorter.sortMembers(selectedMembers);
770            List<Integer> selectedIndices = getSelectedIndices();
771            newMembers = new ArrayList<>();
772            boolean inserted = false;
773            for (int i = 0; i < members.size(); i++) {
774                if (selectedIndices.contains(i)) {
775                    if (!inserted) {
776                        newMembers.addAll(sortedMembers);
777                        inserted = true;
778                    }
779                } else {
780                    newMembers.add(members.get(i));
781                }
782            }
783        }
784
785        if (members.size() != newMembers.size())
786            throw new AssertionError();
787
788        members.clear();
789        members.addAll(newMembers);
790        fireTableDataChanged();
791        setSelectedMembers(sortedMembers);
792    }
793
794    /**
795     * Sort the selected relation members and all members below by the way they are linked.
796     */
797    public void sortBelow() {
798        final List<RelationMember> subList = members.subList(Math.max(0, getSelectionModel().getMinSelectionIndex()), members.size());
799        final List<RelationMember> sorted = relationSorter.sortMembers(subList);
800        subList.clear();
801        subList.addAll(sorted);
802        fireTableDataChanged();
803        setSelectedMembers(sorted);
804    }
805
806    WayConnectionType getWayConnection(int i) {
807        try {
808            if (connectionType == null) {
809                connectionType = wayConnectionTypeCalculator.updateLinks(members);
810            }
811            return connectionType.get(i);
812        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
813            throw BugReport.intercept(e).put("i", i).put("members", members).put("relation", relation);
814        }
815    }
816
817    @Override
818    public void tableChanged(TableModelEvent e) {
819        invalidateConnectionType();
820    }
821
822    private void invalidateConnectionType() {
823        connectionType = null;
824    }
825
826    /**
827     * Reverse the relation members.
828     */
829    public void reverse() {
830        List<Integer> selectedIndices = getSelectedIndices();
831        List<Integer> selectedIndicesReversed = getSelectedIndices();
832
833        if (selectedIndices.size() <= 1) {
834            Collections.reverse(members);
835            fireTableDataChanged();
836            setSelectedMembers(members);
837        } else {
838            Collections.reverse(selectedIndicesReversed);
839
840            List<RelationMember> newMembers = new ArrayList<>(members);
841
842            for (int i = 0; i < selectedIndices.size(); i++) {
843                newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i)));
844            }
845
846            if (members.size() != newMembers.size()) throw new AssertionError();
847            members.clear();
848            members.addAll(newMembers);
849            fireTableDataChanged();
850            setSelectedMembersIdx(selectedIndices);
851        }
852    }
853}