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