001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.relation;
003
004import java.util.List;
005import java.util.concurrent.CopyOnWriteArrayList;
006
007import javax.swing.event.TreeModelEvent;
008import javax.swing.event.TreeModelListener;
009import javax.swing.tree.TreeModel;
010import javax.swing.tree.TreePath;
011
012import org.openstreetmap.josm.data.osm.Relation;
013import org.openstreetmap.josm.data.osm.RelationMember;
014
015/**
016 * This is a {@link TreeModel} which provides the hierarchical structure of {@link Relation}s
017 * to a {@link javax.swing.JTree}.
018 *
019 * The model is initialized with a root relation or with a list of {@link RelationMember}s, see
020 * {@link #populate(Relation)} and {@link #populate(List)} respectively.
021 *
022 * @since 1828
023 */
024public class RelationTreeModel implements TreeModel {
025    /** the root relation */
026    private Relation root;
027
028    /** the tree model listeners */
029    private final CopyOnWriteArrayList<TreeModelListener> listeners;
030
031    /**
032     * constructor
033     */
034    public RelationTreeModel() {
035        this.root = null;
036        listeners = new CopyOnWriteArrayList<>();
037    }
038
039    /**
040     * Replies the number of children of type relation for a particular
041     * relation <code>parent</code>
042     *
043     * @param parent the parent relation
044     * @return the number of children of type relation
045     */
046    protected int getNumRelationChildren(Relation parent) {
047        if (parent == null) return 0;
048        int count = 0;
049        for (RelationMember member : parent.getMembers()) {
050            if (member.isRelation()) {
051                count++;
052            }
053        }
054        return count;
055    }
056
057    /**
058     * Replies the i-th child of type relation for a particular relation
059     * <code>parent</code>.
060     *
061     * @param parent the parent relation
062     * @param idx the index
063     * @return the i-th child of type relation for a particular relation
064     * <code>parent</code>; null, if no such child exists
065     */
066    protected Relation getRelationChildByIdx(Relation parent, int idx) {
067        if (parent == null) return null;
068        int count = 0;
069        for (RelationMember member : parent.getMembers()) {
070            if (!(member.isRelation())) {
071                continue;
072            }
073            if (count == idx)
074                return member.getRelation();
075            count++;
076        }
077        return null;
078    }
079
080    /**
081     * Replies the index of a particular <code>child</code> with respect to its
082     * <code>parent</code>.
083     *
084     * @param parent  the parent relation
085     * @param child the child relation
086     * @return the index of a particular <code>child</code> with respect to its
087     * <code>parent</code>; -1 if either parent or child are null or if <code>child</code>
088     * isn't a child of <code>parent</code>.
089     *
090     */
091    protected int getIndexForRelationChild(Relation parent, Relation child) {
092        if (parent == null || child == null) return -1;
093        int idx = 0;
094        for (RelationMember member : parent.getMembers()) {
095            if (!(member.isRelation())) {
096                continue;
097            }
098            if (member.getMember() == child) return idx;
099            idx++;
100        }
101        return -1;
102    }
103
104    /**
105     * Populates the model with a root relation
106     *
107     * @param root the root relation
108     * @see #populate(List)
109     *
110     */
111    public void populate(Relation root) {
112        if (root == null) {
113            root = new Relation();
114        }
115        this.root = root;
116        fireRootReplacedEvent();
117    }
118
119    /**
120     * Populates the model with a list of relation members
121     *
122     * @param members the relation members
123     */
124    public void populate(List<RelationMember> members) {
125        if (members == null) return;
126        Relation r = new Relation();
127        r.setMembers(members);
128        this.root = r;
129        fireRootReplacedEvent();
130    }
131
132    /**
133     * Notifies tree model listeners about a replacement of the
134     * root.
135     */
136    protected void fireRootReplacedEvent() {
137        TreeModelEvent e = new TreeModelEvent(this, new TreePath(root));
138        for (TreeModelListener l : listeners) {
139            l.treeStructureChanged(e);
140        }
141    }
142
143    /**
144     * Notifies tree model listeners about an update of the
145     * trees nodes.
146     *
147     * @param path the tree path to the node
148     */
149    protected void fireRefreshNode(TreePath path) {
150        TreeModelEvent e = new TreeModelEvent(this, path);
151        for (TreeModelListener l : listeners) {
152            l.treeStructureChanged(e);
153        }
154
155    }
156
157    /**
158     * Invoke to notify all listeners about an update of a particular node
159     *
160     * @param pathToNode the tree path to the node
161     */
162    public void refreshNode(TreePath pathToNode) {
163        fireRefreshNode(pathToNode);
164    }
165
166    /* ----------------------------------------------------------------------- */
167    /* interface TreeModel                                                     */
168    /* ----------------------------------------------------------------------- */
169    @Override
170    public Object getChild(Object parent, int index) {
171        return getRelationChildByIdx((Relation) parent, index);
172    }
173
174    @Override
175    public int getChildCount(Object parent) {
176        return getNumRelationChildren((Relation) parent);
177    }
178
179    @Override
180    public int getIndexOfChild(Object parent, Object child) {
181        return getIndexForRelationChild((Relation) parent, (Relation) child);
182    }
183
184    @Override
185    public Object getRoot() {
186        return root;
187    }
188
189    @Override
190    public boolean isLeaf(Object node) {
191        Relation r = (Relation) node;
192        if (r.isIncomplete()) return false;
193        return getNumRelationChildren(r) == 0;
194    }
195
196    @Override
197    public void addTreeModelListener(TreeModelListener l) {
198        if (l != null) {
199            listeners.addIfAbsent(l);
200        }
201    }
202
203    @Override
204    public void removeTreeModelListener(TreeModelListener l) {
205        listeners.remove(l);
206    }
207
208    @Override
209    public void valueForPathChanged(TreePath path, Object newValue) {
210        // do nothing
211    }
212}