View Javadoc

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&lt;Drive&gt;
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 }