001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.grouping.core.hierarchy;
051:
052: import java.util.ArrayList;
053: import java.util.Collection;
054: import java.util.Enumeration;
055: import java.util.EventListener;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.ListIterator;
059: import java.util.Stack;
060: import java.util.Vector;
061:
062: import javax.swing.event.EventListenerList;
063: import javax.swing.tree.TreeNode;
064:
065: import org.apache.commons.collections.Closure;
066: import org.apache.commons.collections.IteratorUtils;
067: import org.apache.commons.collections.Predicate;
068:
069: import com.projity.grouping.core.LazyParent;
070: import com.projity.grouping.core.Node;
071: import com.projity.grouping.core.NodeBridge;
072: import com.projity.grouping.core.event.HierarchyEvent;
073: import com.projity.grouping.core.event.HierarchyListener;
074: import com.projity.pm.key.HasKey;
075:
076: /**
077: *
078: */
079: public abstract class AbstractMutableNodeHierarchy implements
080: NodeHierarchy {
081: public abstract Object getRoot();
082:
083: public Object getChild(Object parent, int index) {
084: return ((Node) parent).getChildAt(index);
085: }
086:
087: public int getChildCount(Object parent) {
088: return ((Node) parent).getChildCount();
089: }
090:
091: public int getIndexOfChild(Object parent, Object child) {
092: return ((Node) parent).getIndex((Node) child);
093: }
094:
095: public int getIndexOfNode(Node key, boolean skipVoid) {
096: return getIndexOfNode((Node) getRoot(), key, new Counter(),
097: skipVoid);
098: }
099:
100: public Iterator iterator(Node rootNode) {
101: NodeBridge r;
102: if (rootNode != null && rootNode instanceof NodeBridge)
103: r = (NodeBridge) rootNode;
104: else
105: r = (NodeBridge) getRoot();
106: return IteratorUtils.asIterator(r.preorderEnumeration());
107: }
108:
109: public Iterator iterator() {
110: return iterator(null);
111: }
112:
113: final class ShallowPreorderInterator implements Iterator<TreeNode> {
114: protected Stack stack;
115: protected int maxLevel;
116: protected Stack<Integer> levelStack;
117:
118: public ShallowPreorderInterator(TreeNode rootNode,
119: int maxLevel, boolean returnRoot) {
120: super ();
121: Vector v = new Vector(1);
122: v.addElement(rootNode); // PENDING: don't really need a vector
123: stack = new Stack();
124: stack.push(v.elements());
125: levelStack = new Stack<Integer>();
126: levelStack.push(0);
127: this .maxLevel = maxLevel;
128: if (!returnRoot && hasNext())
129: next();
130: }
131:
132: public boolean hasNext() {
133: return (!stack.empty() && ((Enumeration) stack.peek())
134: .hasMoreElements());
135: }
136:
137: public TreeNode next() {
138: Enumeration enumer = (Enumeration) stack.peek();
139: int level = levelStack.peek();
140: TreeNode node = (TreeNode) enumer.nextElement();
141: Enumeration children = level == maxLevel ? null : node
142: .children();
143:
144: if (!enumer.hasMoreElements()) {
145: stack.pop();
146: levelStack.pop();
147: }
148: if (children != null && children.hasMoreElements()) {
149: stack.push(children);
150: levelStack.push(level + 1);
151: }
152: return node;
153: }
154:
155: public void remove() {
156: throw new UnsupportedOperationException(
157: "Remove not supported");
158: }
159: }
160:
161: public Iterator shallowIterator(int maxLevel, boolean returnRoot) {
162: return new ShallowPreorderInterator((TreeNode) getRoot(),
163: maxLevel, returnRoot);
164: }
165:
166: private class Counter { // an int object that is mutable
167: int count = 0;
168: }
169:
170: private int getIndexOfNode(Node node, Node key, Counter counter,
171: boolean skipVoid) {
172: if (key == node)
173: return counter.count;
174: if (!skipVoid || !node.isVirtual())
175: counter.count++;
176: Collection children = getChildren(node);
177: if (children == null)
178: return -1;
179: Iterator i = children.iterator();
180: int found = -1;
181: while (i.hasNext()) {
182: if ((found = getIndexOfNode(key, (Node) i.next(), counter,
183: skipVoid)) != -1)
184: break;
185: }
186: return found;
187:
188: }
189:
190: public void visitAll(Closure visitor) {
191: visitAll(null, visitor);
192: }
193:
194: public void visitAll(Node parent, Closure visitor) {
195: if (parent != null)
196: visitor.execute(parent);
197: Collection children = getChildren(parent);
198: if (children != null) {
199: Iterator i = children.iterator();
200: while (i.hasNext()) {
201: visitAll((Node) i.next(), visitor);
202: }
203: }
204: }
205:
206: //doesn't visit parent
207: public void visitAllLevelOrder(Node parent,
208: boolean skipLazyParents, Closure visitor) {
209: visitAllLevelOrder(true, parent, skipLazyParents, visitor);
210: }
211:
212: public void visitAllLevelOrder(boolean first, Node parent,
213: boolean skipLazyParents, Closure visitor) {
214: // when saving a project, we do not want to save the children of subproject tasks, except when
215: // saving a suproject itself, in which case, the root element will be a subproject task and first will be true
216: if (!first && skipLazyParents && parent != null
217: && parent.getImpl() instanceof LazyParent)
218: return;
219: Collection children = getChildren(parent);
220: if (children != null) {
221: Iterator i = children.iterator();
222: while (i.hasNext()) {
223: visitor.execute(i.next());
224: }
225: i = children.iterator();
226: while (i.hasNext()) {
227: visitAllLevelOrder(false, (Node) i.next(),
228: skipLazyParents, visitor);
229: }
230: }
231: }
232:
233: public void visitAll(Node parent, boolean skipLazyParents,
234: Closure visitor) {
235: visitAll(true, parent, skipLazyParents, visitor);
236: }
237:
238: public void visitAll(boolean first, Node parent,
239: boolean skipLazyParents, Closure visitor) {
240: // when saving a project, we do not want to save the children of subproject tasks, except when
241: // saving a suproject itself, in which case, the root element will be a subproject task and first will be true
242: if (!first && skipLazyParents && parent != null
243: && parent.getImpl() instanceof LazyParent)
244: return;
245: Collection children = getChildren(parent);
246: if (children != null) {
247: Iterator i = children.iterator();
248: i = children.iterator();
249: while (i.hasNext()) {
250: Node node = (Node) i.next();
251: visitor.execute(node);
252: visitAll(false, node, skipLazyParents, visitor);
253: }
254: }
255: }
256:
257: public void visitLeaves(Node node, Closure visitor) {
258: if (node.isLeaf())
259: visitor.execute(node);
260: else
261: for (Enumeration e = node.children(); e.hasMoreElements();) {
262: visitLeaves((Node) e.nextElement(), visitor);
263: }
264: }
265:
266: /**
267: * Get next non virtual node
268: */
269: public Node getNext(Node current) {
270: Node node = current;
271: while (true) {
272: node = getNext(node, true);
273: if (node == null || !node.isVirtual())
274: break;
275: }
276: return node;
277: }
278:
279: private Node getNext(Node current, boolean doChildren) {
280: List children;
281: if (doChildren) { // if haven't visited children yet
282: children = getChildren(current);
283: if (children != null && children.size() > 0) // if parent, next is first child
284: return (Node) children.get(0);
285: }
286: if (current == null) // null parent has no parent
287: return null;
288: Node parent = getParent(current);
289: children = getChildren(parent);
290: Iterator i = children.iterator();
291: while (i.hasNext()) { // get next element after this one. If it is the last then try its parent
292: if (i.next() == current) {
293: if (i.hasNext())
294: return (Node) i.next();
295: else
296: return getNext(parent, false);
297: }
298: }
299: return null;
300: }
301:
302: public Node getPrevious(Node current) {
303: Node node = current;
304: while (true) {
305: node = getPrevious(node, true);
306: if (node == null || !node.isVirtual())
307: break;
308: }
309: return node;
310: }
311:
312: private Node getPrevious(Node current, boolean doChildren) {
313: if (current == null) // null parent has no parent
314: return null;
315: List children;
316:
317: Node parent = getParent(current);
318: children = getChildren(parent);
319: if (doChildren) { // if haven't visited children yet
320: ListIterator i = children.listIterator(children.size()); // reverse iterator
321: while (i.hasPrevious()) { // get next element after this one. If it is the last then try its parent
322: if (i.previous() == current) {
323: if (i.hasPrevious())
324: return getPrevious((Node) i.previous(), false);
325: else
326: return parent;
327: }
328: }
329: }
330:
331: children = getChildren(current);
332: if (children != null && children.size() > 0) // if parent, previous is last child
333: return getPrevious(
334: (Node) children.get(children.size() - 1),
335: doChildren);
336:
337: return current;
338: }
339:
340: public void dump() {
341: dump(null, "", new Closure() {
342: public void execute(Object obj) {
343: System.out.println((String) obj);
344: }
345: });
346: }
347:
348: public void dump(final StringBuffer buf) {
349: dump(null, "", new Closure() {
350: public void execute(Object obj) {
351: buf.append((String) obj).append('\n');
352: }
353: });
354: }
355:
356: private void dump(Node parent, String indent, Closure c) {
357: if (parent != null)
358: c.execute(indent + ">" + parent.toString());
359: Collection children = getChildren(parent);
360: if (children != null) {
361: Iterator i = children.iterator();
362: while (i.hasNext()) {
363: dump((Node) i.next(), indent + "--", c);
364: }
365: }
366: }
367:
368: public abstract Object clone();
369:
370: protected EventListenerList hierarchyListenerList = new EventListenerList();
371:
372: public void addHierarchyListener(HierarchyListener l) {
373: hierarchyListenerList.add(HierarchyListener.class, l);
374: }
375:
376: public void removeHierarchyListener(HierarchyListener l) {
377: hierarchyListenerList.remove(HierarchyListener.class, l);
378: }
379:
380: public HierarchyListener[] getHierarchyListeners() {
381: return (HierarchyListener[]) hierarchyListenerList
382: .getListeners(HierarchyListener.class);
383: }
384:
385: public EventListener[] getHierarchyListeners(Class listenerType) {
386: return hierarchyListenerList.getListeners(listenerType);
387: }
388:
389: protected void fireStructureChanged(Object source) {
390: Object[] listeners = hierarchyListenerList.getListenerList();
391: HierarchyEvent e = null;
392: // for (int i = listeners.length - 2; i >= 0; i -= 2) {
393: for (int i = 0; i < listeners.length; i += 2) {
394: if (listeners[i] == HierarchyListener.class) {
395: if (e == null) {
396: e = new HierarchyEvent(source,
397: HierarchyEvent.STRUCTURE_CHANGED, null);
398: }
399: ((HierarchyListener) listeners[i + 1])
400: .structureChanged(e);
401:
402: }
403: }
404: }
405:
406: protected void fireNodesChanged(Object source, Object[] nodes,
407: Object[] oldNodes, Object flag) {
408: Object[] listeners = hierarchyListenerList.getListenerList();
409: HierarchyEvent e = null;
410: for (int i = 0; i < listeners.length; i += 2) {
411: if (listeners[i] == HierarchyListener.class) {
412: if (e == null) {
413: e = new HierarchyEvent(source,
414: HierarchyEvent.NODES_CHANGED, nodes,
415: oldNodes, flag);
416: }
417: ((HierarchyListener) listeners[i + 1]).nodesChanged(e);
418:
419: }
420: }
421: }
422:
423: protected void fireNodesInserted(Object source, Object[] nodes,
424: Object[] oldNodes, Object flag) {
425: Object[] listeners = hierarchyListenerList.getListenerList();
426: HierarchyEvent e = null;
427: for (int i = 0; i < listeners.length; i += 2) {
428: if (listeners[i] == HierarchyListener.class) {
429: if (e == null) {
430: e = new HierarchyEvent(source,
431: HierarchyEvent.NODES_INSERTED, nodes,
432: oldNodes, flag);
433: }
434: ((HierarchyListener) listeners[i + 1]).nodesInserted(e);
435:
436: }
437: }
438: }
439:
440: protected void fireNodesRemoved(Object source, Object[] nodes,
441: Object[] oldNodes, Object flag) {
442: Object[] listeners = hierarchyListenerList.getListenerList();
443: HierarchyEvent e = null;
444: for (int i = 0; i < listeners.length; i += 2) {
445: if (listeners[i] == HierarchyListener.class) {
446: if (e == null) {
447: e = new HierarchyEvent(source,
448: HierarchyEvent.NODES_REMOVED, nodes,
449: oldNodes, flag);
450: }
451: ((HierarchyListener) listeners[i + 1]).nodesRemoved(e);
452:
453: }
454: }
455: }
456:
457: protected void fireNodesChanged(Object source, Object[] nodes) {
458: // System.out.println("Hierarchy="+hashCode()+", fireNodesChanged, nodes="+nodes);
459: // dump();
460: fireNodesChanged(source, nodes, null, null);
461: }
462:
463: protected void fireNodesInserted(Object source, Object[] nodes) {
464: // System.out.println("Hierarchy="+hashCode()+", fireNodesInserted, nodes="+nodes);
465: // dump();
466: fireNodesInserted(source, nodes, null, null);
467: }
468:
469: protected void fireNodesRemoved(Object source, Object[] nodes) {
470: // System.out.println("Hierarchy="+hashCode()+", fireNodesRemoved, nodes="+nodes);
471: // dump();
472: fireNodesRemoved(source, nodes, null, null);
473: }
474:
475: /**
476: * Convenience method to convert hierarchy to a list of nodes in depth-first order.
477: * @return
478: */
479: public List toList(final boolean isNode, final Predicate filter) {
480: final ArrayList list = new ArrayList();
481: visitAll(new Closure() {
482: public void execute(Object node) {
483: if (filter != null
484: && !filter.evaluate(((Node) node).getImpl()))
485: return;
486: if (isNode)
487: list.add(node);
488: else
489: list.add(((Node) node).getImpl());
490: }
491: });
492: return list;
493: }
494:
495: public void renumber() {
496: visitAll(new Closure() {
497: private int index = 0;
498:
499: public void execute(Object o) {
500: Node node = (Node) o;
501: if (node.hasNumber()) {
502: HasKey impl = (HasKey) node.getImpl();
503: if (impl.getId() != ++index) {
504: impl.setId(index);
505: }
506: }
507: }
508: });
509: }
510:
511: protected int updateLevel = 0;
512:
513: protected synchronized void beginUpdate() {
514: updateLevel++;
515: }
516:
517: protected synchronized void endUpdate() {
518: updateLevel--;
519: }
520:
521: protected synchronized int getUpdateLevel() {
522: return updateLevel;
523: }
524:
525: }
|