001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: * Stefan Xenos, IBM - initial API and implementation
011: *******************************************************************************/package org.eclipse.jface.internal.databinding.provisional.viewers;
012:
013: import java.util.ArrayList;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.HashSet;
017: import java.util.Iterator;
018: import java.util.LinkedList;
019: import java.util.List;
020: import java.util.Set;
021:
022: import org.eclipse.core.databinding.observable.Diffs;
023: import org.eclipse.core.databinding.observable.set.AbstractObservableSet;
024: import org.eclipse.core.databinding.observable.set.IObservableSet;
025: import org.eclipse.core.databinding.observable.set.SetDiff;
026: import org.eclipse.core.internal.databinding.observable.tree.IUnorderedTreeProvider;
027: import org.eclipse.jface.viewers.ITreeContentProvider;
028: import org.eclipse.jface.viewers.ITreePathContentProvider;
029: import org.eclipse.jface.viewers.ITreeViewerListener;
030: import org.eclipse.jface.viewers.TreeExpansionEvent;
031: import org.eclipse.jface.viewers.TreePath;
032: import org.eclipse.jface.viewers.TreeViewer;
033: import org.eclipse.jface.viewers.Viewer;
034:
035: /**
036: * NON-API - Generic tree content provider to be used with an AbstractTreeViewer based on a IUnorderedTreeProvider.
037: * @since 1.1
038: *
039: */
040: public class UnorderedTreeContentProvider implements
041: ITreeContentProvider, ITreePathContentProvider {
042:
043: private HashMap mapElementToTreeNode = new HashMap();
044: private LinkedList enqueuedPrefetches = new LinkedList();
045: private IParentProvider rootParentProvider = null;
046: private boolean useTreePaths = false;
047:
048: class KnownElementsSet extends AbstractObservableSet {
049:
050: protected KnownElementsSet() {
051: super ();
052: }
053:
054: /* (non-Javadoc)
055: * @see org.eclipse.jface.internal.databinding.provisional.observable.set.AbstractObservableSet#getWrappedSet()
056: */
057: protected Set getWrappedSet() {
058: return mapElementToTreeNode.keySet();
059: }
060:
061: void doFireDiff(Set added, Set removed) {
062: fireSetChange(Diffs.createSetDiff(added, removed));
063: }
064:
065: public void fireSetChange(SetDiff diff) {
066: super .fireSetChange(diff);
067: }
068:
069: void doFireStale(boolean isStale) {
070: if (isStale) {
071: fireStale();
072: } else {
073: fireSetChange(Diffs.createSetDiff(
074: Collections.EMPTY_SET, Collections.EMPTY_SET));
075: }
076: }
077:
078: /* (non-Javadoc)
079: * @see org.eclipse.jface.internal.databinding.provisional.observable.set.IObservableSet#getElementType()
080: */
081: public Object getElementType() {
082: return new Object();
083: }
084: }
085:
086: KnownElementsSet elements = new KnownElementsSet();
087:
088: private ITreeViewerListener expandListener = new ITreeViewerListener() {
089: public void treeCollapsed(TreeExpansionEvent event) {
090: }
091:
092: public void treeExpanded(TreeExpansionEvent event) {
093: }
094: };
095:
096: private IUnorderedTreeProvider provider;
097: private Object pendingNode;
098:
099: private int avoidViewerUpdates;
100:
101: private TreeViewer treeViewer;
102:
103: private int staleCount = 0;
104: private boolean useRefresh;
105: private int maxPrefetches = 0;
106:
107: /**
108: * Constructs a content provider that will render the given tree in a TreeViewer.
109: *
110: * @param provider IObservableTree that provides the contents of the tree
111: * @param pendingNode element to insert whenever a node is being fetched in the background
112: * @param useRefresh true = notify the viewer of changes by calling refresh(...), false =
113: * notify the viewer of changes by calling add(...) and remove(...). Using false
114: * is more efficient, but may not work with TreeViewer subclasses.
115: */
116: public UnorderedTreeContentProvider(
117: IUnorderedTreeProvider provider, Object pendingNode,
118: boolean useRefresh) {
119: this .provider = provider;
120: this .pendingNode = pendingNode;
121: this .useRefresh = useRefresh;
122: }
123:
124: /**
125: * Sets whether this content provider should add/remove elements using
126: * TreePaths (true) or elements (false).
127: *
128: * <p></p>
129: * <p>When using elements:</p>
130: *
131: * <ul>
132: * <li>Cycles are permitted (elements can be their own ancestor)</li>
133: * <li>Addition, removal, and refresh are slightly faster</li>
134: * <li>It is not possible to have more than one content provider per tree</li>
135: * <li>The setRootPath(...) method is ignored</li>
136: * </ul>
137: *
138: * <p></p>
139: * <p>When using TreePaths:</p>
140: *
141: * <ul>
142: * <li>Cycles are not permitted (elements cannot be their own parent)</li>
143: * <li>Addition, removal, and refresh are slightly slower</li>
144: * <li>It is possible to use more than one content provider in the same tree</li>
145: * <li>The setRootPath(...) method can be used to direct the output to a particular
146: * subtree</li>
147: * </ul>
148: *
149: * @param usePaths
150: */
151: public void useTreePaths(boolean usePaths) {
152: this .useTreePaths = usePaths;
153: }
154:
155: /**
156: * @param rootParentProvider
157: */
158: public void setRootPath(IParentProvider rootParentProvider) {
159: this .rootParentProvider = rootParentProvider;
160: }
161:
162: /**
163: * @param maxPrefetches
164: */
165: public void setMaxPrefetches(int maxPrefetches) {
166: this .maxPrefetches = maxPrefetches;
167: }
168:
169: /* package */IObservableSet createChildSet(Object element) {
170: return provider.createChildSet(element);
171: }
172:
173: /* package */void remove(Object element, Set removals,
174: boolean lastElement) {
175: if (removals.isEmpty()) {
176: return;
177: }
178: if (avoidViewerUpdates == 0) {
179: if (lastElement || useRefresh) {
180: doRefresh(element);
181: } else {
182: if (useTreePaths) {
183: List toRemove = new ArrayList();
184: TreePath[] parents = getParents(element);
185: for (int i = 0; i < parents.length; i++) {
186: TreePath parent = parents[i];
187:
188: for (Iterator iter = removals.iterator(); iter
189: .hasNext();) {
190: Object elementToRemove = (Object) iter
191: .next();
192:
193: toRemove.add(parent
194: .createChildPath(element)
195: .createChildPath(elementToRemove));
196: }
197: }
198:
199: treeViewer.remove((TreePath[]) toRemove
200: .toArray(new TreePath[toRemove.size()]));
201: } else {
202: treeViewer.remove(element, removals.toArray());
203: }
204: }
205: for (Iterator iter = removals.iterator(); iter.hasNext();) {
206: Object next = (Object) iter.next();
207:
208: TreeNode nextNode = (TreeNode) mapElementToTreeNode
209: .get(next);
210: if (nextNode != null) {
211: nextNode.removeParent(element);
212: removeIfUnused(nextNode);
213: }
214: }
215: }
216: }
217:
218: /* package */void add(Object element, Set additions) {
219: if (additions.isEmpty()) {
220: return;
221: }
222: if (avoidViewerUpdates == 0) {
223: // Handle new parents
224: addParent(element, additions);
225: if (useRefresh) {
226: doRefresh(element);
227: } else {
228: if (useTreePaths) {
229: TreePath[] parents = getParents(element);
230: for (int i = 0; i < parents.length; i++) {
231: TreePath parent = parents[i];
232:
233: treeViewer.add(parent.createChildPath(element),
234: additions.toArray());
235: }
236: } else {
237: treeViewer.add(element, additions.toArray());
238: }
239: }
240: }
241: }
242:
243: private void doRefresh(Object element) {
244: treeViewer.refresh(element);
245: }
246:
247: /**
248: * Ensures that the given set of children have the given parent as
249: * one of their parents.
250: *
251: * @param parent
252: * @param children
253: */
254: private void addParent(Object parent, Set children) {
255: for (Iterator iter = children.iterator(); iter.hasNext();) {
256: Object next = (Object) iter.next();
257:
258: TreeNode nextNode = getNode(next);
259: nextNode.addParent(parent);
260: }
261: }
262:
263: /**
264: * @return saouesnth
265: */
266: public final Object getPendingNode() {
267: return pendingNode;
268: }
269:
270: /**
271: * @param parent
272: * @return aueosnht
273: */
274: public IObservableSet getChildrenSet(Object parent) {
275: IObservableSet result = getNode(parent).getChildrenSet();
276:
277: return result;
278: }
279:
280: public void dispose() {
281: if (treeViewer != null) {
282: try {
283: avoidViewerUpdates++;
284: enqueuedPrefetches.clear();
285: Object[] keys = mapElementToTreeNode.keySet().toArray();
286:
287: for (int i = 0; i < keys.length; i++) {
288: Object key = keys[i];
289:
290: TreeNode result = (TreeNode) mapElementToTreeNode
291: .get(key);
292: if (result != null) {
293: result.dispose();
294: }
295: }
296: setViewer(null);
297: } finally {
298: avoidViewerUpdates--;
299: }
300: }
301: }
302:
303: public void inputChanged(Viewer viewer, Object oldInput,
304: Object newInput) {
305: // This should only ever be called for a single viewer
306: setViewer(viewer);
307:
308: if (oldInput != null && newInput != null
309: && oldInput.equals(newInput)) {
310: return;
311: }
312:
313: try {
314: avoidViewerUpdates++;
315: TreeNode oldNode = (TreeNode) mapElementToTreeNode
316: .get(oldInput);
317: if (oldNode != null) {
318: removeIfUnused(oldNode);
319: }
320: } finally {
321: avoidViewerUpdates--;
322: }
323: }
324:
325: private void removeIfUnused(TreeNode toRemove) {
326: //TreeNode result = (TreeNode)mapElementToTreeNode.get(element);
327: Object element = toRemove.getElement();
328: if (toRemove.getParent() == null) {
329: mapElementToTreeNode.remove(element);
330: elements.doFireDiff(Collections.EMPTY_SET, Collections
331: .singleton(element));
332: toRemove.dispose();
333: }
334: }
335:
336: private void setViewer(Viewer viewer) {
337: if (viewer != null && !(viewer instanceof TreeViewer)) {
338: throw new IllegalArgumentException(
339: "This content provider can only be used with TreeViewers"); //$NON-NLS-1$
340: }
341: TreeViewer newTreeViewer = (TreeViewer) viewer;
342:
343: if (newTreeViewer != treeViewer) {
344: if (treeViewer != null) {
345: treeViewer.removeTreeListener(expandListener);
346: }
347:
348: this .treeViewer = newTreeViewer;
349: if (newTreeViewer != null) {
350: newTreeViewer.addTreeListener(expandListener);
351: }
352: }
353: }
354:
355: public Object[] getChildren(Object parentElement) {
356: Set result = getNode(parentElement).getChildren();
357:
358: addParent(parentElement, result);
359:
360: return result.toArray();
361: }
362:
363: private TreeNode getNode(Object parentElement) {
364: TreeNode result = (TreeNode) mapElementToTreeNode
365: .get(parentElement);
366: if (result == null) {
367: result = new TreeNode(parentElement, this );
368: mapElementToTreeNode.put(parentElement, result);
369: elements.fireSetChange(Diffs.createSetDiff(Collections
370: .singleton(parentElement), Collections.EMPTY_SET));
371: }
372: return result;
373: }
374:
375: public Object getParent(Object element) {
376: Object result = getNode(element).getParent();
377: if (result == null && rootParentProvider != null) {
378: result = rootParentProvider.getParent(element);
379: }
380: return result;
381: }
382:
383: public boolean hasChildren(Object element) {
384: return getNode(element).shouldShowPlus();
385: }
386:
387: public Object[] getElements(Object inputElement) {
388: return getChildren(inputElement);
389: }
390:
391: /**
392: * @return aouesnth
393: */
394: public IObservableSet getKnownElements() {
395: return elements;
396: }
397:
398: /* package */void changeStale(int staleDelta) {
399: staleCount += staleDelta;
400: processPrefetches();
401: elements.setStale(staleCount != 0);
402: }
403:
404: /**
405: * @return aoueesnth
406: */
407: public TreeViewer getViewer() {
408: return treeViewer;
409: }
410:
411: /**
412: * @param element
413: * @return aoeusnth
414: */
415: public boolean isDirty(Object element) {
416: return false;
417: }
418:
419: /* package */void enqueuePrefetch(TreeNode node) {
420: if (maxPrefetches > 0 || maxPrefetches == -1) {
421: if (staleCount == 0) {
422: // Call node.getChildren()... this will cause us to start listening to the
423: // node and will trigger prefetching. Don't call prefetch since this method
424: // is intended to be called inside getters (which will simply return the
425: // fetched nodes) and prefetch() is intended to be called inside an asyncExec,
426: // which will notify the viewer directly of the newly discovered nodes.
427: node.getChildren();
428: } else {
429: enqueuedPrefetches.add(node);
430: while (maxPrefetches >= 0
431: && enqueuedPrefetches.size() > maxPrefetches) {
432: enqueuedPrefetches.removeFirst();
433: }
434: }
435: }
436: }
437:
438: private void processPrefetches() {
439: while (staleCount == 0 && !enqueuedPrefetches.isEmpty()) {
440: TreeNode next = (TreeNode) enqueuedPrefetches.removeLast();
441:
442: // Note that we don't remove nodes from the prefetch queue when they are disposed,
443: // so we may encounter disposed nodes at this time.
444: if (!next.isDisposed()) {
445: next.prefetch();
446: }
447: }
448: }
449:
450: public Object[] getChildren(TreePath parentPath) {
451: return getChildren(parentPath.getLastSegment());
452: }
453:
454: public TreePath[] getParents(Object element) {
455: // Compute all paths that do not contain cycles
456: /**
457: * List of Lists
458: */
459: List parentPaths = computeParents(element, new HashSet());
460:
461: /**
462: * List of TreePath
463: */
464: List result = new ArrayList();
465:
466: for (Iterator iterator = parentPaths.iterator(); iterator
467: .hasNext();) {
468: List nextPath = (List) iterator.next();
469:
470: LinkedList resultPath = new LinkedList();
471: resultPath.addAll(nextPath);
472: Object nextParent = resultPath.isEmpty() ? element
473: : resultPath.getFirst();
474: for (; nextParent != null;) {
475: if (rootParentProvider != null) {
476: nextParent = rootParentProvider
477: .getParent(nextParent);
478: if (nextParent != null) {
479: resultPath.addFirst(nextParent);
480: }
481: } else {
482: nextParent = null;
483: }
484: }
485:
486: result.add(new TreePath(resultPath.toArray()));
487: }
488:
489: if (result.isEmpty() && rootParentProvider != null) {
490: Object nextParent = rootParentProvider.getParent(element);
491: if (nextParent != null) {
492: LinkedList resultPath = new LinkedList();
493: while (nextParent != null) {
494: resultPath.addFirst(nextParent);
495: nextParent = rootParentProvider
496: .getParent(nextParent);
497: }
498:
499: result.add(new TreePath(resultPath.toArray()));
500: }
501:
502: }
503:
504: return (TreePath[]) result.toArray(new TreePath[result.size()]);
505: }
506:
507: /**
508: *
509: * @param node
510: * @param toIgnore
511: * @return a list of Lists, indicating all known paths to the given node
512: */
513: private List computeParents(Object node, HashSet toIgnore) {
514: List result = new ArrayList();
515: boolean containedNode = toIgnore.add(node);
516:
517: TreeNode tn = getNode(node);
518:
519: HashSet parents = new HashSet();
520: parents.addAll(tn.getParents());
521: parents.removeAll(toIgnore);
522: if (parents.isEmpty()) {
523: ArrayList newPath = new ArrayList();
524: result.add(newPath);
525: } else {
526: for (Iterator iterator = parents.iterator(); iterator
527: .hasNext();) {
528: Object parent = iterator.next();
529:
530: List parentPaths = computeParents(parent, toIgnore);
531:
532: for (Iterator iterator2 = parentPaths.iterator(); iterator2
533: .hasNext();) {
534: List parentPath = (List) iterator2.next();
535:
536: parentPath.add(parent);
537: result.add(parentPath);
538: }
539: }
540: }
541:
542: if (containedNode) {
543: toIgnore.remove(node);
544: }
545: return result;
546: }
547:
548: public boolean hasChildren(TreePath path) {
549: return hasChildren(path.getLastSegment());
550: }
551: }
|