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