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 return (int) parent.getMembers().stream().filter(RelationMember::isRelation).count(); 049 } 050 051 /** 052 * Replies the i-th child of type relation for a particular relation 053 * <code>parent</code>. 054 * 055 * @param parent the parent relation 056 * @param idx the index 057 * @return the i-th child of type relation for a particular relation 058 * <code>parent</code>; null, if no such child exists 059 */ 060 protected Relation getRelationChildByIdx(Relation parent, int idx) { 061 if (parent == null) return null; 062 int count = 0; 063 for (RelationMember member : parent.getMembers()) { 064 if (!member.isRelation()) { 065 continue; 066 } 067 if (count == idx) 068 return member.getRelation(); 069 count++; 070 } 071 return null; 072 } 073 074 /** 075 * Replies the index of a particular <code>child</code> with respect to its 076 * <code>parent</code>. 077 * 078 * @param parent the parent relation 079 * @param child the child relation 080 * @return the index of a particular <code>child</code> with respect to its 081 * <code>parent</code>; -1 if either parent or child are null or if <code>child</code> 082 * isn't a child of <code>parent</code>. 083 * 084 */ 085 protected int getIndexForRelationChild(Relation parent, Relation child) { 086 if (parent == null || child == null) return -1; 087 int idx = 0; 088 for (RelationMember member : parent.getMembers()) { 089 if (!member.isRelation()) { 090 continue; 091 } 092 if (member.getMember() == child) return idx; 093 idx++; 094 } 095 return -1; 096 } 097 098 /** 099 * Populates the model with a root relation 100 * 101 * @param root the root relation 102 * @see #populate(List) 103 * 104 */ 105 public void populate(Relation root) { 106 if (root == null) { 107 root = new Relation(); 108 } 109 this.root = root; 110 fireRootReplacedEvent(); 111 } 112 113 /** 114 * Populates the model with a list of relation members 115 * 116 * @param members the relation members 117 */ 118 public void populate(List<RelationMember> members) { 119 if (members == null) return; 120 Relation r = new Relation(); 121 r.setMembers(members); 122 this.root = r; 123 fireRootReplacedEvent(); 124 } 125 126 /** 127 * Notifies tree model listeners about a replacement of the 128 * root. 129 */ 130 protected void fireRootReplacedEvent() { 131 TreeModelEvent e = new TreeModelEvent(this, new TreePath(root)); 132 for (TreeModelListener l : listeners) { 133 l.treeStructureChanged(e); 134 } 135 } 136 137 /** 138 * Notifies tree model listeners about an update of the 139 * trees nodes. 140 * 141 * @param path the tree path to the node 142 */ 143 protected void fireRefreshNode(TreePath path) { 144 TreeModelEvent e = new TreeModelEvent(this, path); 145 for (TreeModelListener l : listeners) { 146 l.treeStructureChanged(e); 147 } 148 149 } 150 151 /** 152 * Invoke to notify all listeners about an update of a particular node 153 * 154 * @param pathToNode the tree path to the node 155 */ 156 public void refreshNode(TreePath pathToNode) { 157 fireRefreshNode(pathToNode); 158 } 159 160 /* ----------------------------------------------------------------------- */ 161 /* interface TreeModel */ 162 /* ----------------------------------------------------------------------- */ 163 @Override 164 public Object getChild(Object parent, int index) { 165 return getRelationChildByIdx((Relation) parent, index); 166 } 167 168 @Override 169 public int getChildCount(Object parent) { 170 return getNumRelationChildren((Relation) parent); 171 } 172 173 @Override 174 public int getIndexOfChild(Object parent, Object child) { 175 return getIndexForRelationChild((Relation) parent, (Relation) child); 176 } 177 178 @Override 179 public Object getRoot() { 180 return root; 181 } 182 183 @Override 184 public boolean isLeaf(Object node) { 185 Relation r = (Relation) node; 186 if (r.isIncomplete()) return false; 187 return getNumRelationChildren(r) == 0; 188 } 189 190 @Override 191 public void addTreeModelListener(TreeModelListener l) { 192 if (l != null) { 193 listeners.addIfAbsent(l); 194 } 195 } 196 197 @Override 198 public void removeTreeModelListener(TreeModelListener l) { 199 listeners.remove(l); 200 } 201 202 @Override 203 public void valueForPathChanged(TreePath path, Object newValue) { 204 // do nothing 205 } 206}