001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.dialogs.relation;
003    
004    import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.BACKWARD;
005    import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.FORWARD;
006    import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.NONE;
007    import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.ROUNDABOUT_LEFT;
008    import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.ROUNDABOUT_RIGHT;
009    
010    import java.util.ArrayList;
011    import java.util.Arrays;
012    import java.util.Collection;
013    import java.util.Collections;
014    import java.util.HashSet;
015    import java.util.Iterator;
016    import java.util.LinkedList;
017    import java.util.List;
018    import java.util.Set;
019    import java.util.concurrent.CopyOnWriteArrayList;
020    
021    import javax.swing.DefaultListSelectionModel;
022    import javax.swing.ListSelectionModel;
023    import javax.swing.event.TableModelEvent;
024    import javax.swing.event.TableModelListener;
025    import javax.swing.table.AbstractTableModel;
026    
027    import org.openstreetmap.josm.Main;
028    import org.openstreetmap.josm.data.SelectionChangedListener;
029    import org.openstreetmap.josm.data.coor.EastNorth;
030    import org.openstreetmap.josm.data.osm.DataSet;
031    import org.openstreetmap.josm.data.osm.Node;
032    import org.openstreetmap.josm.data.osm.OsmPrimitive;
033    import org.openstreetmap.josm.data.osm.Relation;
034    import org.openstreetmap.josm.data.osm.RelationMember;
035    import org.openstreetmap.josm.data.osm.Way;
036    import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
037    import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
038    import org.openstreetmap.josm.data.osm.event.DataSetListener;
039    import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
040    import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
041    import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
042    import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
043    import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
044    import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
045    import org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction;
046    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
047    import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
048    
049    public class MemberTableModel extends AbstractTableModel implements TableModelListener, SelectionChangedListener, DataSetListener, OsmPrimitivesTableModel {
050    
051        /**
052         * data of the table model: The list of members and the cached WayConnectionType of each member.
053         **/
054        private List<RelationMember> members;
055        private List<WayConnectionType> connectionType = null;
056    
057        private DefaultListSelectionModel listSelectionModel;
058        private CopyOnWriteArrayList<IMemberModelListener> listeners;
059        private OsmDataLayer layer;
060    
061        private final int UNCONNECTED = Integer.MIN_VALUE;
062    
063        /**
064         * constructor
065         */
066        public MemberTableModel(OsmDataLayer layer) {
067            members = new ArrayList<RelationMember>();
068            listeners = new CopyOnWriteArrayList<IMemberModelListener>();
069            this.layer = layer;
070            addTableModelListener(this);
071        }
072    
073        public OsmDataLayer getLayer() {
074            return layer;
075        }
076    
077        public void register() {
078            DataSet.addSelectionListener(this);
079            getLayer().data.addDataSetListener(this);
080        }
081    
082        public void unregister() {
083            DataSet.removeSelectionListener(this);
084            getLayer().data.removeDataSetListener(this);
085        }
086    
087        /* --------------------------------------------------------------------------- */
088        /* Interface SelectionChangedListener                                          */
089        /* --------------------------------------------------------------------------- */
090        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
091            if (Main.main.getEditLayer() != this.layer) return;
092            // just trigger a repaint
093            Collection<RelationMember> sel = getSelectedMembers();
094            fireTableDataChanged();
095            setSelectedMembers(sel);
096        }
097    
098        /* --------------------------------------------------------------------------- */
099        /* Interface DataSetListener                                                   */
100        /* --------------------------------------------------------------------------- */
101        public void dataChanged(DataChangedEvent event) {
102            // just trigger a repaint - the display name of the relation members may
103            // have changed
104            Collection<RelationMember> sel = getSelectedMembers();
105            fireTableDataChanged();
106            setSelectedMembers(sel);
107        }
108    
109        public void nodeMoved(NodeMovedEvent event) {/* ignore */}
110        public void primitivesAdded(PrimitivesAddedEvent event) {/* ignore */}
111    
112        public void primitivesRemoved(PrimitivesRemovedEvent event) {
113            // ignore - the relation in the editor might become out of sync with the relation
114            // in the dataset. We will deal with it when the relation editor is closed or
115            // when the changes in the editor are applied.
116        }
117    
118        public void relationMembersChanged(RelationMembersChangedEvent event) {
119            // ignore - the relation in the editor might become out of sync with the relation
120            // in the dataset. We will deal with it when the relation editor is closed or
121            // when the changes in the editor are applied.
122        }
123    
124        public void tagsChanged(TagsChangedEvent event) {
125            // just refresh the respective table cells
126            //
127            Collection<RelationMember> sel = getSelectedMembers();
128            for (int i=0; i < members.size();i++) {
129                if (members.get(i).getMember() == event.getPrimitive()) {
130                    fireTableCellUpdated(i, 1 /* the column with the primitive name */);
131                }
132            }
133            setSelectedMembers(sel);
134        }
135    
136        public void wayNodesChanged(WayNodesChangedEvent event) {/* ignore */}
137    
138        public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignore */}
139        /* --------------------------------------------------------------------------- */
140    
141        public void addMemberModelListener(IMemberModelListener listener) {
142            if (listener != null) {
143                listeners.addIfAbsent(listener);
144            }
145        }
146    
147        public void removeMemberModelListener(IMemberModelListener listener) {
148            listeners.remove(listener);
149        }
150    
151        protected void fireMakeMemberVisible(int index) {
152            for (IMemberModelListener listener : listeners) {
153                listener.makeMemberVisible(index);
154            }
155        }
156    
157        public void populate(Relation relation) {
158            members.clear();
159            if (relation != null) {
160                // make sure we work with clones of the relation members
161                // in the model.
162                members.addAll(new Relation(relation).getMembers());
163            }
164            fireTableDataChanged();
165        }
166    
167        public int getColumnCount() {
168            return 3;
169        }
170    
171        public int getRowCount() {
172            return members.size();
173        }
174    
175        public Object getValueAt(int rowIndex, int columnIndex) {
176            switch (columnIndex) {
177            case 0:
178                return members.get(rowIndex).getRole();
179            case 1:
180                return members.get(rowIndex).getMember();
181            case 2:
182                return getWayConnection(rowIndex);
183            }
184            // should not happen
185            return null;
186        }
187    
188        @Override
189        public boolean isCellEditable(int rowIndex, int columnIndex) {
190            return columnIndex == 0;
191        }
192    
193        @Override
194        public void setValueAt(Object value, int rowIndex, int columnIndex) {
195            RelationMember member = members.get(rowIndex);
196            RelationMember newMember = new RelationMember(value.toString(), member.getMember());
197            members.remove(rowIndex);
198            members.add(rowIndex, newMember);
199        }
200    
201        @Override
202        public OsmPrimitive getReferredPrimitive(int idx) {
203            return members.get(idx).getMember();
204        }
205    
206        public void moveUp(int[] selectedRows) {
207            if (!canMoveUp(selectedRows))
208                return;
209    
210            for (int row : selectedRows) {
211                RelationMember member1 = members.get(row);
212                RelationMember member2 = members.get(row - 1);
213                members.set(row, member2);
214                members.set(row - 1, member1);
215            }
216            fireTableDataChanged();
217            getSelectionModel().setValueIsAdjusting(true);
218            getSelectionModel().clearSelection();
219            for (int row : selectedRows) {
220                row--;
221                getSelectionModel().addSelectionInterval(row, row);
222            }
223            getSelectionModel().setValueIsAdjusting(false);
224            fireMakeMemberVisible(selectedRows[0] - 1);
225        }
226    
227        public void moveDown(int[] selectedRows) {
228            if (!canMoveDown(selectedRows))
229                return;
230    
231            for (int i = selectedRows.length - 1; i >= 0; i--) {
232                int row = selectedRows[i];
233                RelationMember member1 = members.get(row);
234                RelationMember member2 = members.get(row + 1);
235                members.set(row, member2);
236                members.set(row + 1, member1);
237            }
238            fireTableDataChanged();
239            getSelectionModel();
240            getSelectionModel().setValueIsAdjusting(true);
241            getSelectionModel().clearSelection();
242            for (int row : selectedRows) {
243                row++;
244                getSelectionModel().addSelectionInterval(row, row);
245            }
246            getSelectionModel().setValueIsAdjusting(false);
247            fireMakeMemberVisible(selectedRows[0] + 1);
248        }
249    
250        public void remove(int[] selectedRows) {
251            if (!canRemove(selectedRows))
252                return;
253            int offset = 0;
254            for (int row : selectedRows) {
255                row -= offset;
256                if (members.size() > row) {
257                    members.remove(row);
258                    offset++;
259                }
260            }
261            fireTableDataChanged();
262        }
263    
264        public boolean canMoveUp(int[] rows) {
265            if (rows == null || rows.length == 0)
266                return false;
267            Arrays.sort(rows);
268            return rows[0] > 0 && members.size() > 0;
269        }
270    
271        public boolean canMoveDown(int[] rows) {
272            if (rows == null || rows.length == 0)
273                return false;
274            Arrays.sort(rows);
275            return members.size() > 0 && rows[rows.length - 1] < members.size() - 1;
276        }
277    
278        public boolean canRemove(int[] rows) {
279            if (rows == null || rows.length == 0)
280                return false;
281            return true;
282        }
283    
284        public DefaultListSelectionModel getSelectionModel() {
285            if (listSelectionModel == null) {
286                listSelectionModel = new DefaultListSelectionModel();
287                listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
288            }
289            return listSelectionModel;
290        }
291    
292        public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) {
293            if (primitives == null)
294                return;
295            Iterator<RelationMember> it = members.iterator();
296            while (it.hasNext()) {
297                RelationMember member = it.next();
298                if (primitives.contains(member.getMember())) {
299                    it.remove();
300                }
301            }
302            fireTableDataChanged();
303        }
304    
305        public void applyToRelation(Relation relation) {
306            relation.setMembers(members);
307        }
308    
309        public boolean hasSameMembersAs(Relation relation) {
310            if (relation == null)
311                return false;
312            if (relation.getMembersCount() != members.size())
313                return false;
314            for (int i = 0; i < relation.getMembersCount(); i++) {
315                if (!relation.getMember(i).equals(members.get(i)))
316                    return false;
317            }
318            return true;
319        }
320    
321        /**
322         * Replies the set of incomplete primitives
323         *
324         * @return the set of incomplete primitives
325         */
326        public Set<OsmPrimitive> getIncompleteMemberPrimitives() {
327            Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
328            for (RelationMember member : members) {
329                if (member.getMember().isIncomplete()) {
330                    ret.add(member.getMember());
331                }
332            }
333            return ret;
334        }
335    
336        /**
337         * Replies the set of selected incomplete primitives
338         *
339         * @return the set of selected incomplete primitives
340         */
341        public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() {
342            Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
343            for (RelationMember member : getSelectedMembers()) {
344                if (member.getMember().isIncomplete()) {
345                    ret.add(member.getMember());
346                }
347            }
348            return ret;
349        }
350    
351        /**
352         * Replies true if at least one the relation members is incomplete
353         *
354         * @return true if at least one the relation members is incomplete
355         */
356        public boolean hasIncompleteMembers() {
357            for (RelationMember member : members) {
358                if (member.getMember().isIncomplete())
359                    return true;
360            }
361            return false;
362        }
363    
364        /**
365         * Replies true if at least one of the selected members is incomplete
366         *
367         * @return true if at least one of the selected members is incomplete
368         */
369        public boolean hasIncompleteSelectedMembers() {
370            for (RelationMember member : getSelectedMembers()) {
371                if (member.getMember().isIncomplete())
372                    return true;
373            }
374            return false;
375        }
376    
377        protected List<Integer> getSelectedIndices() {
378            ArrayList<Integer> selectedIndices = new ArrayList<Integer>();
379            for (int i = 0; i < members.size(); i++) {
380                if (getSelectionModel().isSelectedIndex(i)) {
381                    selectedIndices.add(i);
382                }
383            }
384            return selectedIndices;
385        }
386    
387        private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) {
388            if (primitives == null)
389                return;
390            int idx = index;
391            for (OsmPrimitive primitive : primitives) {
392                RelationMember member = new RelationMember("", primitive);
393                members.add(idx++, member);
394            }
395            fireTableDataChanged();
396            getSelectionModel().clearSelection();
397            getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1);
398            fireMakeMemberVisible(index);
399        }
400    
401        public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) {
402            addMembersAtIndex(primitives, 0);
403        }
404    
405        public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) {
406            addMembersAtIndex(primitives, members.size());
407        }
408    
409        public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) {
410            addMembersAtIndex(primitives, idx);
411        }
412    
413        public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) {
414            addMembersAtIndex(primitives, idx + 1);
415        }
416    
417        /**
418         * Replies the number of members which refer to a particular primitive
419         *
420         * @param primitive the primitive
421         * @return the number of members which refer to a particular primitive
422         */
423        public int getNumMembersWithPrimitive(OsmPrimitive primitive) {
424            int count = 0;
425            for (RelationMember member : members) {
426                if (member.getMember().equals(primitive)) {
427                    count++;
428                }
429            }
430            return count;
431        }
432    
433        /**
434         * updates the role of the members given by the indices in <code>idx</code>
435         *
436         * @param idx the array of indices
437         * @param role the new role
438         */
439        public void updateRole(int[] idx, String role) {
440            if (idx == null || idx.length == 0)
441                return;
442            for (int row : idx) {
443                RelationMember oldMember = members.get(row);
444                RelationMember newMember = new RelationMember(role, oldMember.getMember());
445                members.remove(row);
446                members.add(row, newMember);
447            }
448            fireTableDataChanged();
449            for (int row : idx) {
450                getSelectionModel().addSelectionInterval(row, row);
451            }
452        }
453    
454        /**
455         * Get the currently selected relation members
456         *
457         * @return a collection with the currently selected relation members
458         */
459        public Collection<RelationMember> getSelectedMembers() {
460            ArrayList<RelationMember> selectedMembers = new ArrayList<RelationMember>();
461            for (int i : getSelectedIndices()) {
462                selectedMembers.add(members.get(i));
463            }
464            return selectedMembers;
465        }
466    
467        /**
468         * Replies the set of selected referers. Never null, but may be empty.
469         *
470         * @return the set of selected referers
471         */
472        public Collection<OsmPrimitive> getSelectedChildPrimitives() {
473            Collection<OsmPrimitive> ret = new ArrayList<OsmPrimitive>();
474            for (RelationMember m: getSelectedMembers()) {
475                ret.add(m.getMember());
476            }
477            return ret;
478        }
479    
480        /**
481         * Replies the set of selected referers. Never null, but may be empty.
482         *
483         * @return the set of selected referers
484         */
485        public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) {
486            HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
487            if (referenceSet == null) return null;
488            for (RelationMember m: members) {
489                if (referenceSet.contains(m.getMember())) {
490                    ret.add(m.getMember());
491                }
492            }
493            return ret;
494        }
495    
496        /**
497         * Selects the members in the collection selectedMembers
498         *
499         * @param selectedMembers the collection of selected members
500         */
501        public void setSelectedMembers(Collection<RelationMember> selectedMembers) {
502            if (selectedMembers == null || selectedMembers.isEmpty()) {
503                getSelectionModel().clearSelection();
504                return;
505            }
506    
507            // lookup the indices for the respective members
508            //
509            Set<Integer> selectedIndices = new HashSet<Integer>();
510            for (RelationMember member : selectedMembers) {
511                for (int idx = 0; idx < members.size(); ++idx) {
512                    if (member.equals(members.get(idx))) {
513                        selectedIndices.add(idx);
514                    }
515                }
516            }
517            setSelectedMembersIdx(selectedIndices);
518        }
519    
520        /**
521         * Selects the members in the collection selectedIndices
522         *
523         * @param selectedIndices the collection of selected member indices
524         */
525        public void setSelectedMembersIdx(Collection<Integer> selectedIndices) {
526            if (selectedIndices == null || selectedIndices.isEmpty()) {
527                getSelectionModel().clearSelection();
528                return;
529            }
530            // select the members
531            //
532            getSelectionModel().setValueIsAdjusting(true);
533            getSelectionModel().clearSelection();
534            for (int row : selectedIndices) {
535                getSelectionModel().addSelectionInterval(row, row);
536            }
537            getSelectionModel().setValueIsAdjusting(false);
538            // make the first selected member visible
539            //
540            if (selectedIndices.size() > 0) {
541                fireMakeMemberVisible(Collections.min(selectedIndices));
542            }
543        }
544    
545        /**
546         * Replies true if the index-th relation members referrs
547         * to an editable relation, i.e. a relation which is not
548         * incomplete.
549         *
550         * @param index the index
551         * @return true, if the index-th relation members referrs
552         * to an editable relation, i.e. a relation which is not
553         * incomplete
554         */
555        public boolean isEditableRelation(int index) {
556            if (index < 0 || index >= members.size())
557                return false;
558            RelationMember member = members.get(index);
559            if (!member.isRelation())
560                return false;
561            Relation r = member.getRelation();
562            return !r.isIncomplete();
563        }
564    
565        /**
566         * Replies true if there is at least one relation member given as {@code members}
567         * which refers to at least on the primitives in {@code primitives}.
568         *
569         * @param members the members
570         * @param primitives the collection of primitives
571         * @return true if there is at least one relation member in this model
572         * which refers to at least on the primitives in <code>primitives</code>; false
573         * otherwise
574         */
575        public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) {
576            if (primitives == null || primitives.isEmpty()) {
577                return false;
578            }
579            HashSet<OsmPrimitive> referrers = new HashSet<OsmPrimitive>();
580            for (RelationMember member : members) {
581                referrers.add(member.getMember());
582            }
583            for (OsmPrimitive referred : primitives) {
584                if (referrers.contains(referred)) {
585                    return true;
586                }
587            }
588            return false;
589        }
590    
591        /**
592         * Replies true if there is at least one relation member in this model
593         * which refers to at least on the primitives in <code>primitives</code>.
594         *
595         * @param primitives the collection of primitives
596         * @return true if there is at least one relation member in this model
597         * which refers to at least on the primitives in <code>primitives</code>; false
598         * otherwise
599         */
600        public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) {
601            return hasMembersReferringTo(members, primitives);
602        }
603    
604        /**
605         * Selects all mebers which refer to {@link OsmPrimitive}s in the collections
606         * <code>primitmives</code>. Does nothing is primitives is null.
607         *
608         * @param primitives the collection of primitives
609         */
610        public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) {
611            if (primitives == null) return;
612            getSelectionModel().setValueIsAdjusting(true);
613            getSelectionModel().clearSelection();
614            for (int i=0; i< members.size();i++) {
615                RelationMember m = members.get(i);
616                if (primitives.contains(m.getMember())) {
617                    this.getSelectionModel().addSelectionInterval(i,i);
618                }
619            }
620            getSelectionModel().setValueIsAdjusting(false);
621            if (getSelectedIndices().size() > 0) {
622                fireMakeMemberVisible(getSelectedIndices().get(0));
623            }
624        }
625    
626        /**
627         * Replies true if <code>primitive</code> is currently selected in the layer this
628         * model is attached to
629         *
630         * @param primitive the primitive
631         * @return true if <code>primitive</code> is currently selected in the layer this
632         * model is attached to, false otherwise
633         */
634        public boolean isInJosmSelection(OsmPrimitive primitive) {
635            return layer.data.isSelected(primitive);
636        }
637    
638        /**
639         * Replies true if the layer this model belongs to is equal to the active
640         * layer
641         *
642         * @return true if the layer this model belongs to is equal to the active
643         * layer
644         */
645        protected boolean isActiveLayer() {
646            if (Main.map == null || Main.map.mapView == null) return false;
647            return Main.map.mapView.getActiveLayer() == layer;
648        }
649    
650        /**
651         * get a node we can link against when sorting members
652         * @param element the element we want to link against
653         * @param linked_element already linked against element
654         * @return the unlinked node if element is a way, the node itself if element is a node, null otherwise
655         */
656        /*private static Node getUnusedNode(RelationMember element, RelationMember linked_element)
657        {
658            Node result = null;
659    
660            if (element.isWay()) {
661                Way w = element.getWay();
662                if (linked_element.isWay()) {
663                    Way x = linked_element.getWay();
664                    if ((w.firstNode() == x.firstNode()) || (w.firstNode() == x.lastNode())) {
665                        result = w.lastNode();
666                    } else {
667                        result = w.firstNode();
668                    }
669                } else if (linked_element.isNode()) {
670                    Node m = linked_element.getNode();
671                    if (w.firstNode() == m) {
672                        result = w.lastNode();
673                    } else {
674                        result = w.firstNode();
675                    }
676                }
677            } else if (element.isNode()) {
678                Node n = element.getNode();
679                result = n;
680            }
681    
682            return result;
683        }*/
684    
685        /*
686         * Sort a collection of relation members by the way they are linked.
687         *
688         * @param relationMembers collection of relation members
689         * @return sorted collection of relation members
690         */
691        private List<RelationMember> sortMembers(List<RelationMember> relationMembers) {
692            RelationNodeMap map = new RelationNodeMap(relationMembers);
693            // List of groups of linked members
694            //
695            ArrayList<LinkedList<Integer>> allGroups = new ArrayList<LinkedList<Integer>>();
696    
697            // current group of members that are linked among each other
698            // Two successive members are always linked i.e. have a common node.
699            //
700            LinkedList<Integer> group;
701    
702            Integer first;
703            while ((first = map.pop()) != null) {
704                group = new LinkedList<Integer>();
705                group.add(first);
706    
707                allGroups.add(group);
708    
709                Integer next = first;
710                while ((next = map.popAdjacent(next)) != null) {
711                    group.addLast(next);
712                }
713    
714                // The first element need not be in front of the list.
715                // So the search goes in both directions
716                //
717                next = first;
718                while ((next = map.popAdjacent(next)) != null) {
719                    group.addFirst(next);
720                }
721            }
722    
723            group = new LinkedList<Integer>();
724            group.addAll(map.getNotSortableMembers());
725            allGroups.add(group);
726    
727            ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>();
728            for (LinkedList<Integer> tmpGroup : allGroups) {
729                for (Integer p : tmpGroup) {
730                    newMembers.add(relationMembers.get(p));
731                }
732            }
733            return newMembers;
734        }
735    
736        /**
737         * Sort the selected relation members by the way they are linked.
738         */
739        void sort() {
740            List<RelationMember> selectedMembers = new ArrayList<RelationMember>(getSelectedMembers());
741            List<RelationMember> sortedMembers = null;
742            List<RelationMember> newMembers;
743            if (selectedMembers.size() <= 1) {
744                newMembers = sortMembers(members);
745                sortedMembers = newMembers;
746            } else {
747                sortedMembers = sortMembers(selectedMembers);
748                List<Integer> selectedIndices = getSelectedIndices();
749                newMembers = new ArrayList<RelationMember>();
750                boolean inserted = false;
751                for (int i=0; i < members.size(); i++) {
752                    if (selectedIndices.contains(i)) {
753                        if (!inserted) {
754                            newMembers.addAll(sortedMembers);
755                            inserted = true;
756                        }
757                    } else {
758                        newMembers.add(members.get(i));
759                    }
760                }
761            }
762    
763            if (members.size() != newMembers.size()) throw new AssertionError();
764    
765            members.clear();
766            members.addAll(newMembers);
767            fireTableDataChanged();
768            setSelectedMembers(sortedMembers);
769        }
770    
771        private Direction determineDirection(int ref_i, Direction ref_direction, int k) {
772            return determineDirection(ref_i, ref_direction, k, false);
773        }
774        /**
775         * Determines the direction of way k with respect to the way ref_i.
776         * The way ref_i is assumed to have the direction ref_direction and
777         * to be the predecessor of k.
778         *
779         * If both ways are not linked in any way, NONE is returned.
780         *
781         * Else the direction is given as follows:
782         * Let the relation be a route of oneway streets, and someone travels them in the given order.
783         * Direction is FORWARD if it is legal and BACKWARD if it is illegal to do so for the given way.
784         *
785         **/
786        private Direction determineDirection(int ref_i, final Direction ref_direction, int k, boolean reversed) {
787            if (ref_i < 0 || k < 0 || ref_i >= members.size() || k >= members.size())
788                return NONE;
789            if (ref_direction == NONE)
790                return NONE;
791    
792            final RelationMember m_ref = members.get(ref_i);
793            final RelationMember m = members.get(k);
794            Way way_ref = null;
795            Way way = null;
796    
797            if (m_ref.isWay()) {
798                way_ref = m_ref.getWay();
799            }
800            if (m.isWay()) {
801                way = m.getWay();
802            }
803    
804            if (way_ref == null || way == null)
805                return NONE;
806    
807            /** the list of nodes the way k can dock to */
808            List<Node> refNodes= new ArrayList<Node>();
809    
810            switch (ref_direction) {
811            case FORWARD:
812                refNodes.add(way_ref.lastNode());
813                break;
814            case BACKWARD:
815                refNodes.add(way_ref.firstNode());
816                break;
817            case ROUNDABOUT_LEFT:
818            case ROUNDABOUT_RIGHT:
819                refNodes = way_ref.getNodes();
820                break;
821            }
822    
823            if (refNodes == null)
824                return NONE;
825    
826            for (Node n : refNodes) {
827                if (n == null) {
828                    continue;
829                }
830                if (roundaboutType(k) != NONE) {
831                    for (Node nn : way.getNodes()) {
832                        if (n == nn)
833                            return roundaboutType(k);
834                    }
835                } else if(isOneway(m)) {
836                    if (n == RelationNodeMap.firstOnewayNode(m) && !reversed) {
837                        if(isBackward(m))
838                            return BACKWARD;
839                        else
840                            return FORWARD;
841                    }
842                    if (n == RelationNodeMap.lastOnewayNode(m) && reversed) {
843                        if(isBackward(m))
844                            return FORWARD;
845                        else
846                            return BACKWARD;
847                    }
848                } else {
849                    if (n == way.firstNode())
850                        return FORWARD;
851                    if (n == way.lastNode())
852                        return BACKWARD;
853                }
854            }
855            return NONE;
856        }
857    
858        /**
859         * determine, if the way i is a roundabout and if yes, what type of roundabout
860         */
861        private Direction roundaboutType(int i) {
862            RelationMember m = members.get(i);
863            if (m == null || !m.isWay()) return NONE;
864            Way w = m.getWay();
865            return roundaboutType(w);
866        }
867        static Direction roundaboutType(Way w) {
868            if (w != null &&
869                    "roundabout".equals(w.get("junction")) &&
870                    w.getNodesCount() < 200 &&
871                    w.getNodesCount() > 2 &&
872                    w.getNode(0) != null &&
873                    w.getNode(1) != null &&
874                    w.getNode(2) != null &&
875                    w.firstNode() == w.lastNode()) {
876                /** do some simple determinant / cross pruduct test on the first 3 nodes
877                    to see, if the roundabout goes clock wise or ccw */
878                EastNorth en1 = w.getNode(0).getEastNorth();
879                EastNorth en2 = w.getNode(1).getEastNorth();
880                EastNorth en3 = w.getNode(2).getEastNorth();
881                if (en1 != null && en2 != null && en3 != null) {
882                    en1 = en1.sub(en2);
883                    en2 = en2.sub(en3);
884                    return en1.north() * en2.east() - en2.north() * en1.east() > 0 ? ROUNDABOUT_LEFT : ROUNDABOUT_RIGHT;
885                }
886            }
887            return NONE;
888        }
889    
890        WayConnectionType getWayConnection(int i) {
891            if (connectionType == null) {
892                updateLinks();
893            }
894            return connectionType.get(i);
895        }
896    
897        public void tableChanged(TableModelEvent e) {
898            connectionType = null;
899        }
900    
901        /**
902         * Reverse the relation members.
903         */
904        void reverse() {
905            List<Integer> selectedIndices = getSelectedIndices();
906            List<Integer> selectedIndicesReversed = getSelectedIndices();
907    
908            if (selectedIndices.size() <= 1) {
909                Collections.reverse(members);
910                fireTableDataChanged();
911                setSelectedMembers(members);
912            } else {
913                Collections.reverse(selectedIndicesReversed);
914    
915                ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>(members);
916    
917                for (int i=0; i < selectedIndices.size(); i++) {
918                    newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i)));
919                }
920    
921                if (members.size() != newMembers.size()) throw new AssertionError();
922                members.clear();
923                members.addAll(newMembers);
924                fireTableDataChanged();
925                setSelectedMembersIdx(selectedIndices);
926            }
927        }
928    
929        /**
930         * refresh the cache of member WayConnectionTypes
931         */
932        public void updateLinks() {
933            connectionType = null;
934            final List<WayConnectionType> con = new ArrayList<WayConnectionType>();
935    
936            for (int i=0; i<members.size(); ++i) {
937                con.add(null);
938            }
939    
940            firstGroupIdx=0;
941    
942            lastForwardWay = UNCONNECTED;
943            lastBackwardWay = UNCONNECTED;
944            onewayBeginning = false;
945            WayConnectionType lastWct = null;
946    
947            for (int i=0; i<members.size(); ++i) {
948                final RelationMember m = members.get(i);
949                if (!m.isWay() || m.getWay() == null || m.getWay().isIncomplete()) {
950                    if(i > 0) {
951                        makeLoopIfNeeded(con, i-1);
952                    }
953                    con.set(i, new WayConnectionType());
954                    firstGroupIdx = i;
955                    continue;
956                }
957    
958                WayConnectionType wct = new WayConnectionType(false);
959                wct.linkPrev = i>0 && con.get(i-1) != null && con.get(i-1).isValid();
960                wct.direction = NONE;
961    
962                if(isOneway(m)){
963                    if(lastWct != null && lastWct.isOnewayTail) {
964                        wct.isOnewayHead = true;
965                    }
966                    if(lastBackwardWay == UNCONNECTED && lastForwardWay == UNCONNECTED){ //Beginning of new oneway
967                        wct.isOnewayHead = true;
968                        lastForwardWay = i-1;
969                        lastBackwardWay = i-1;
970                        onewayBeginning = true;
971                    }
972                }
973    
974                if (wct.linkPrev) {
975                    if(lastBackwardWay != UNCONNECTED && lastForwardWay != UNCONNECTED) {
976                        wct = determineOnewayConnectionType(con, m, i, wct);
977                        if(!wct.linkPrev) {
978                            firstGroupIdx = i;
979                        }
980                    }
981    
982                    if(!isOneway(m)) {
983                        wct.direction = determineDirection(i-1, lastWct.direction, i);
984                        wct.linkPrev = (wct.direction != NONE);
985                    }
986                }
987    
988                if (!wct.linkPrev) {
989                    wct.direction = determineDirectionOfFirst(i, m);
990                    if(isOneway(m)){
991                        wct.isOnewayLoopForwardPart = true;
992                        lastForwardWay = i;
993                    }
994                }
995    
996                wct.linkNext = false;
997                if(lastWct != null) {
998                    lastWct.linkNext = wct.linkPrev;
999                }
1000                con.set(i, wct);
1001                lastWct = wct;
1002    
1003                if(!wct.linkPrev) {
1004                    if(i > 0) {
1005                        makeLoopIfNeeded(con, i-1);
1006                    }
1007                    firstGroupIdx = i;
1008                }
1009            }
1010            makeLoopIfNeeded(con, members.size()-1);
1011            connectionType = con;
1012        }
1013    
1014        //    private static void unconnectPreviousLink(List<WayConnectionType> con, int beg, boolean backward){
1015        //        int i = beg;
1016        //        while(true){
1017        //            WayConnectionType t = con.get(i--);
1018        //            t.isOnewayOppositeConnected = false;
1019        //            if(backward && t.isOnewayLoopBackwardPart) break;
1020        //            if(!backward && t.isOnewayLoopForwardPart) break;
1021        //        }
1022        //    }
1023    
1024        private static Direction reverse(final Direction dir){
1025            if(dir == FORWARD) return BACKWARD;
1026            if(dir == BACKWARD) return FORWARD;
1027            return dir;
1028        }
1029    
1030        private static boolean isBackward(final RelationMember member){
1031            return member.getRole().equals("backward");
1032        }
1033    
1034        private static boolean isForward(final RelationMember member){
1035            return member.getRole().equals("forward");
1036        }
1037    
1038        public static boolean isOneway(final RelationMember member){
1039            return isForward(member) || isBackward(member);
1040        }
1041    
1042        int firstGroupIdx;
1043        private void makeLoopIfNeeded(final List<WayConnectionType> con, final int i) {
1044            boolean loop;
1045            if (i == firstGroupIdx) { //is primitive loop
1046                loop = determineDirection(i, FORWARD, i) == FORWARD;
1047            } else {
1048                loop = determineDirection(i, con.get(i).direction, firstGroupIdx) == con.get(firstGroupIdx).direction;
1049            }
1050            if (loop) {
1051                for (int j=firstGroupIdx; j <= i; ++j) {
1052                    con.get(j).isLoop = true;
1053                }
1054            }
1055        }
1056    
1057        private Direction determineDirectionOfFirst(final int i, final RelationMember m) {
1058            if (roundaboutType(i) != NONE)
1059                return roundaboutType(i);
1060    
1061            if (isOneway(m)){
1062                if(isBackward(m)) return BACKWARD;
1063                else return FORWARD;
1064            } else { /** guess the direction and see if it fits with the next member */
1065                if(determineDirection(i, FORWARD, i+1) != NONE) return FORWARD;
1066                if(determineDirection(i, BACKWARD, i+1) != NONE) return BACKWARD;
1067            }
1068            return NONE;
1069        }
1070    
1071        int lastForwardWay, lastBackwardWay;
1072        boolean onewayBeginning;
1073        private WayConnectionType determineOnewayConnectionType(final List<WayConnectionType> con,
1074                RelationMember m, int i, final WayConnectionType wct) {
1075            Direction dirFW = determineDirection(lastForwardWay, con.get(lastForwardWay).direction, i);
1076            Direction dirBW = NONE;
1077            if(onewayBeginning) {
1078                if(lastBackwardWay < 0) {
1079                    dirBW = determineDirection(firstGroupIdx, reverse(con.get(firstGroupIdx).direction), i, true);
1080                } else {
1081                    dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true);
1082                }
1083    
1084                if(dirBW != NONE) {
1085                    onewayBeginning = false;
1086                }
1087            } else {
1088                dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true);
1089            }
1090    
1091            if(isOneway(m)) {
1092                if(dirBW != NONE){
1093                    wct.direction = dirBW;
1094                    lastBackwardWay = i;
1095                    wct.isOnewayLoopBackwardPart = true;
1096                }
1097                if(dirFW != NONE){
1098                    wct.direction = dirFW;
1099                    lastForwardWay = i;
1100                    wct.isOnewayLoopForwardPart = true;
1101                }
1102                if(dirFW == NONE && dirBW == NONE) { //Not connected to previous
1103                    //                        unconnectPreviousLink(con, i, true);
1104                    //                        unconnectPreviousLink(con, i, false);
1105                    wct.linkPrev = false;
1106                    if(isOneway(m)){
1107                        wct.isOnewayHead = true;
1108                        lastForwardWay = i-1;
1109                        lastBackwardWay = i-1;
1110                    } else {
1111                        lastForwardWay = UNCONNECTED;
1112                        lastBackwardWay = UNCONNECTED;
1113                    }
1114                    onewayBeginning = true;
1115                }
1116    
1117                if(dirFW != NONE && dirBW != NONE) { //End of oneway loop
1118                    if(i+1<members.size() && determineDirection(i, dirFW, i+1) != NONE) {
1119                        wct.isOnewayLoopBackwardPart = false;
1120                        dirBW = NONE;
1121                        wct.direction = dirFW;
1122                    } else {
1123                        wct.isOnewayLoopForwardPart = false;
1124                        dirFW = NONE;
1125                        wct.direction = dirBW;
1126                    }
1127    
1128                    wct.isOnewayTail = true;
1129                }
1130    
1131            } else {
1132                lastForwardWay = UNCONNECTED;
1133                lastBackwardWay = UNCONNECTED;
1134                if(dirFW == NONE || dirBW == NONE) {
1135                    wct.linkPrev = false;
1136                }
1137            }
1138            return wct;
1139        }
1140    }