1 package cz.cuni.amis.pogamut.sposh.elements; 2 3 import java.awt.datatransfer.DataFlavor; 4 import java.beans.PropertyChangeEvent; 5 import java.util.Collections; 6 import java.util.HashSet; 7 import java.util.LinkedList; 8 import java.util.List; 9 import java.util.Set; 10 11 /** 12 * Ancestor of representative of one element in Posh plan. 13 * 14 * Listens for various modifications in POSH tree structure. Listener is 15 * connected on one particular PoshElement, so many events are neighbours and so 16 * on. 17 * 18 * Various uses, manily during construction of new tree from file. 19 * 20 * BASIC LEMMA IS: Reality DataNode, Representation PoshWidget (Model, view, 21 * controller) <p> Add new node: <ul> <li> Context menu/other stuff, like during 22 * load of file</li> <li> get PoshElement(N) that should add the new node</li> 23 * <li> add new child PoshElement(C) to the N</li> <li> N calls all connected 24 * PoshWidgets and tells them that it has new child(newChild(N, C) listener or 25 * something).</li> <li> Every PoshWidget PW will:</li> <ul> <li> Create new 26 * PoshWidget CW for C</li> <li> connect CW to C</li> <li> make a line from PW 27 * to CW</li> <li> position is not important, will be done later</li> </ul> <li> 28 * After everything is added and DATAs and WIDGETs are in plase, reconsolidate 29 * positions of everything.</li> </ul> 30 * 31 * <p> Delete node: Result: PoshElement DN will be deleted along with all 32 * associated PoshWidgets <ul> <li>context menu(something else) will notify that 33 * it should be destroyed</li> <li>DN will call all children to delete 34 * themself(recursive process)</li> <li>call all PW that are connected to the DN 35 * to delete themself</li> <UL> <li>PW will remove all children(do not delete 36 * underlying data node)</li> <li>PW will call parent to delete themself</li> 37 * </ul> </ul> 38 * 39 * @author HonzaH 40 * @param T Type of this. Needed for listeners, so Drive is Drive<Drive> 41 */ 42 public abstract class PoshElement<T extends PoshElement, PARENT extends PoshElement> { 43 44 /** 45 * PoshElement, that is parent of this DN. Root doesn't have one (is 46 * <code>null</code>), rest does have. 47 */ 48 private PARENT parent; 49 /** 50 * Listeners on events of this DN. 51 */ 52 private final Set<PoshElementListener<T>> elementListeners = new HashSet<PoshElementListener<T>>(); 53 private final Set<PoshElementListener<T>> elementListenersUm = Collections.unmodifiableSet(elementListeners); 54 55 /** 56 * Get list of listeners that listen for changes of this node (new child, 57 * node deletion, childMoved and change of properties) 58 * 59 * @return unmodifiable list of listeners, never null, 60 */ 61 public final Set<PoshElementListener<T>> getElementListeners() { 62 return elementListenersUm; 63 } 64 65 /** 66 * Add new listener for events of this node. 67 * 68 * @param listener listener to add 69 * @return <tt>true</tt> if listener was added. <tt>false</tt> if listener 70 * was already among listeners. 71 */ 72 public final synchronized boolean addElementListener(PoshElementListener<T> listener) { 73 return elementListeners.add(listener); 74 } 75 76 /** 77 * Remove listener from list of listeners of this node. 78 * 79 * @param listener listener to remove 80 * @return Did listeners of this element of this element contain passed 81 * listener? 82 */ 83 public final synchronized boolean removeElementListener(PoshElementListener<T> listener) { 84 return elementListeners.remove(listener); 85 } 86 87 /** 88 * Notify all listeners about change of a property. 89 * 90 * @param name name of property 91 * @param o old value, can be null, but reciever has to be able to handle it 92 * @param n new value, can be null, but reciever has to be able to handle it 93 */ 94 protected final synchronized void firePropertyChange(String name, Object o, Object n) { 95 PoshElementListener[] listeners = elementListenersUm.toArray(new PoshElementListener[0]); 96 for (PoshElementListener listener : listeners) { 97 listener.propertyChange(new PropertyChangeEvent(this, name, o, n)); 98 } 99 } 100 101 /** 102 * Get data flavour of posh plan element,used during DnD from palette to 103 * PoshScene. 104 * 105 * @return dataFlavour of posh plan element, never null. 106 */ 107 public abstract DataFlavor getDataFlavor(); 108 109 /** 110 * Get type of the element. 111 * 112 * @return Type of element. 113 */ 114 public abstract LapType getType(); 115 116 /** 117 * Get list of children of this node. Most likely auto generated every time 118 * this method is called. 119 * 120 * @return List of all children of this node. 121 */ 122 public abstract List<? extends PoshElement> getChildDataNodes(); 123 124 /** 125 * Notify all listeners (mostly associated Widgets) that this dataNode has a 126 * new child. After that emit recursivly children of added element in. 127 * 128 * @param childNode 129 */ 130 protected final synchronized void emitChildNode(PoshElement emitedChild) { 131 // emit child 132 PoshElementListener<T>[] listenersArray = elementListenersUm.toArray(new PoshElementListener[]{}); 133 for (PoshElementListener<T> listener : listenersArray) { 134 listener.childElementAdded((T) this, emitedChild); 135 } 136 } 137 138 /** 139 * Notify all listeners (associated Widgets) that one child of this node has 140 * changed order(position). 141 * 142 * @param childNode 143 */ 144 protected final synchronized void emitChildMove(PoshElement childNode, int oldIndex, int newIndex) { 145 PoshElementListener[] listenersArray = elementListenersUm.toArray(new PoshElementListener[]{}); 146 147 for (PoshElementListener<T> listener : listenersArray) { 148 listener.childElementMoved((T) this, childNode, oldIndex, newIndex); 149 } 150 } 151 152 /** 153 * Tell all listeners that a child of this element has been deleted. Dont 154 * remove listeners, it is job of listener to remove itself. 155 */ 156 protected final synchronized void emitChildDeleted(PoshElement child, int removedChildPosition) { 157 PoshElementListener[] listenersArray = elementListenersUm.toArray(new PoshElementListener[]{}); 158 159 for (PoshElementListener<T> listener : listenersArray) { 160 listener.childElementRemoved((T) this, child, removedChildPosition); 161 } 162 } 163 164 /** 165 * Set parent 166 * <code>PoshElement</code> of the node. 167 * 168 * @param parent 169 */ 170 protected void setParent(PARENT parent) { 171 this.parent = parent; 172 } 173 174 /** 175 * Get parent of the node. Null, if the node is root. 176 * 177 * @return parent of the node, or null if node is root. 178 */ 179 public PARENT getParent() { 180 return this.parent; 181 } 182 183 /** 184 * Get root node of POSH plan this node belongs to. 185 * 186 * @return Root node of POSH plan or null if I am unable to reach it. 187 */ 188 public final PoshPlan getRootNode() { 189 PoshElement cur = this; 190 while (cur.getParent() != null) { 191 cur = cur.getParent(); 192 } 193 if (cur instanceof PoshPlan) { 194 return (PoshPlan) cur; 195 } 196 return null; 197 } 198 199 /** 200 * Move child to the @newIndex. After child was moved (if it was moved), 201 * notify listeners. 202 * 203 * @return if node succesfully moved 204 */ 205 public abstract boolean moveChild(int newIndex, PoshElement child); 206 207 /** 208 * Is this element present as a a child in its parent? If element has no 209 * parent, return false. Used during adding of a new element as a sanity 210 * check (two elements could think that the node is their child). 211 * 212 * @return true if element is a child of parent, false if element is not a 213 * child of its parent or it has no parent at all (null). 214 */ 215 protected final boolean isChildOfParent() { 216 if (parent != null) { 217 return parent.getChildDataNodes().contains(this); 218 } 219 return false; 220 } 221 222 /** 223 * Get all children of this element that have @selectType. 224 * 225 * @param selectType Type of children we are looking for. 226 * @return Newly allocated list of all children of this node that have {@link #getType() 227 * } == @selectType. 228 */ 229 public final List<? extends PoshElement> getChildren(LapType selectType) { 230 List<PoshElement> selectedChildren = new LinkedList<PoshElement>(); 231 for (PoshElement node : this.getChildDataNodes()) { 232 if (node.getType() == selectType) { 233 selectedChildren.add(node); 234 } 235 } 236 return selectedChildren; 237 } 238 239 /** 240 * Get index of a @child. Id is determined as index of @child in all 241 * children of same type. 242 */ 243 public final int getChildId(PoshElement child) { 244 assert child.getParent() == this; 245 List<? extends PoshElement> sameTypeChildren = getChildren(child.getType()); 246 return sameTypeChildren.indexOf(child); 247 } 248 249 /** 250 * Get Id of this element. 251 * @throws NullPointerException if parent is null. 252 */ 253 public final int getId() { 254 return parent.getChildId(this); 255 } 256 }