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.*;
6   
7   /**
8    * Base class for each element of Yaposh plan. It can register listeners on
9    * changes of this element and has link to parent element.
10   *
11   * @author HonzaH
12   * @param T Type of this. Needed for listeners, so Drive is Drive<Drive>
13   */
14  public abstract class PoshElement<T extends PoshElement, PARENT extends PoshElement> {
15  
16      /**
17       * PoshElement, that is parent of this DN. Root doesn't have one (is
18       * <code>null</code>), rest does have.
19       */
20      private PARENT parent;
21      /**
22       * Listeners on events of this DN.
23       */
24      private final Set<PoshElementListener<T>> elementListeners = new HashSet<PoshElementListener<T>>();
25      private final Set<PoshElementListener<T>> elementListenersUm = Collections.unmodifiableSet(elementListeners);
26  
27      /**
28       * Get list of listeners that listen for changes of this node (new child,
29       * node deletion, childMoved and change of properties)
30       *
31       * @return unmodifiable list of listeners, never null,
32       */
33      public final Set<PoshElementListener<T>> getElementListeners() {
34          return elementListenersUm;
35      }
36  
37      /**
38       * Add new listener for events of this node.
39       *
40       * @param listener listener to add
41       * @return <tt>true</tt> if listener was added. <tt>false</tt> if listener
42       * was already among listeners.
43       */
44      public final synchronized boolean addElementListener(PoshElementListener<T> listener) {
45          return elementListeners.add(listener);
46      }
47  
48      /**
49       * Remove listener from list of listeners of this node.
50       *
51       * @param listener listener to remove
52       * @return Did listeners of this element of this element contain passed
53       * listener?
54       */
55      public final synchronized boolean removeElementListener(PoshElementListener<T> listener) {
56          return elementListeners.remove(listener);
57      }
58  
59      /**
60       * Notify all listeners about change of a property.
61       *
62       * @param name name of property
63       * @param o old value, can be null, but reciever has to be able to handle it
64       * @param n new value, can be null, but reciever has to be able to handle it
65       */
66      protected final synchronized void firePropertyChange(String name, Object o, Object n) {
67          PoshElementListener[] listeners = elementListenersUm.toArray(new PoshElementListener[0]);
68          for (PoshElementListener listener : listeners) {
69              listener.propertyChange(new PropertyChangeEvent(this, name, o, n));
70          }
71      }
72  
73      /**
74       * Get data flavour of posh plan element,used during DnD from palette to
75       * PoshScene.
76       *
77       * @return dataFlavour of posh plan element, never null.
78       */
79      public abstract DataFlavor getDataFlavor();
80  
81      /**
82       * Get type of the element.
83       *
84       * @return Type of element.
85       */
86      public abstract LapType getType();
87  
88      /**
89       * Get list of children of this node. Most likely auto generated every time
90       * this method is called.
91       *
92       * @return List of all children of this node.
93       */
94      public abstract List<? extends PoshElement> getChildDataNodes();
95  
96      /**
97       * Notify all listeners (mostly associated Widgets) that this dataNode has a
98       * new child. After that emit recursivly children of added element in.
99       *
100      * @param childNode
101      */
102     protected final synchronized void emitChildNode(PoshElement emitedChild) {
103         // emit child
104         PoshElementListener<T>[] listenersArray = elementListenersUm.toArray(new PoshElementListener[]{});
105         for (PoshElementListener<T> listener : listenersArray) {
106             listener.childElementAdded((T) this, emitedChild);
107         }
108     }
109 
110     /**
111      * Notify all listeners (associated Widgets) that one child of this node has
112      * changed order(position).
113      *
114      * @param childNode
115      */
116     protected final synchronized void emitChildMove(PoshElement childNode, int oldIndex, int newIndex) {
117         PoshElementListener[] listenersArray = elementListenersUm.toArray(new PoshElementListener[]{});
118 
119         for (PoshElementListener<T> listener : listenersArray) {
120             listener.childElementMoved((T) this, childNode, oldIndex, newIndex);
121         }
122     }
123 
124     /**
125      * Tell all listeners that a child of this element has been deleted. Dont
126      * remove listeners, it is job of listener to remove itself.
127      */
128     protected final synchronized void emitChildDeleted(PoshElement child, int removedChildPosition) {
129         PoshElementListener[] listenersArray = elementListenersUm.toArray(new PoshElementListener[]{});
130 
131         for (PoshElementListener<T> listener : listenersArray) {
132             listener.childElementRemoved((T) this, child, removedChildPosition);
133         }
134     }
135 
136     /**
137      * Set parent
138      * <code>PoshElement</code> of the node.
139      *
140      * @param parent
141      */
142     protected void setParent(PARENT parent) {
143         this.parent = parent;
144     }
145 
146     /**
147      * Get parent of the node. Null, if the node is root.
148      *
149      * @return parent of the node, or null if node is root.
150      */
151     public PARENT getParent() {
152         return this.parent;
153     }
154 
155     /**
156      * Get root node of POSH plan this node belongs to.
157      *
158      * @return Root node of POSH plan or null if I am unable to reach it.
159      */
160     public final PoshPlan getRootNode() {
161         PoshElement cur = this;
162         while (cur.getParent() != null) {
163             cur = cur.getParent();
164         }
165         if (cur instanceof PoshPlan) {
166             return (PoshPlan) cur;
167         }
168         return null;
169     }
170 
171     /**
172      * Move child to the @newIndex. After child was moved (if it was moved),
173      * notify listeners.
174      *
175      * @return if node succesfully moved
176      */
177     public abstract boolean moveChild(int newIndex, PoshElement child);
178 
179     /**
180      * Is this element present as a a child in its parent? If element has no
181      * parent, return false. Used during adding of a new element as a sanity
182      * check (two elements could think that the node is their child).
183      *
184      * @return true if element is a child of parent, false if element is not a
185      * child of its parent or it has no parent at all (null).
186      */
187     protected final boolean isChildOfParent() {
188         if (parent != null) {
189             return parent.getChildDataNodes().contains(this);
190         }
191         return false;
192     }
193 
194     /**
195      * Get all children of this element that have @selectType.
196      *
197      * @param selectType Type of children we are looking for.
198      * @return Newly allocated list of all children of this node that have {@link #getType()
199      * } == @selectType.
200      */
201     public final List<? extends PoshElement> getChildren(LapType selectType) {
202         List<PoshElement> selectedChildren = new LinkedList<PoshElement>();
203         for (PoshElement node : this.getChildDataNodes()) {
204             if (node.getType() == selectType) {
205                 selectedChildren.add(node);
206             }
207         }
208         return selectedChildren;
209     }
210 
211     /**
212      * Get index of a @child. Id is determined as index of @child in all
213      * children of same type.
214      */
215     public final int getChildId(PoshElement child) {
216         assert child.getParent() == this;
217         List<? extends PoshElement> sameTypeChildren = getChildren(child.getType());
218         return sameTypeChildren.indexOf(child);
219     }
220     
221     /**
222      * Get Id of this element.
223      * @throws NullPointerException if parent is null.
224      */
225     public final int getId() {
226         return parent.getChildId(this);
227     }
228 }