1 /*******************************************************************************
2  * Copyright (c) 2003, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Anton Leherbauer, Wind River
14  *       bug 261031 [CommonNavigator] IPipelinedContentProvider getParent() returning the suggested parent is not ignored
15  *     William Chen, chenwmw@gmail.com
16  *       bug 343721 getParent of NavigatorContentServiceContentProvider does not return expected node.
17  *******************************************************************************/
18 package org.eclipse.ui.internal.navigator;
19 
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.LinkedHashSet;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Set;
27 
28 import org.eclipse.core.runtime.SafeRunner;
29 import org.eclipse.jface.viewers.ITreeContentProvider;
30 import org.eclipse.jface.viewers.ITreePathContentProvider;
31 import org.eclipse.jface.viewers.TreePath;
32 import org.eclipse.jface.viewers.Viewer;
33 import org.eclipse.osgi.util.NLS;
34 import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptor;
35 import org.eclipse.ui.internal.navigator.extensions.NavigatorContentExtension;
36 import org.eclipse.ui.internal.navigator.extensions.NavigatorViewerDescriptor;
37 import org.eclipse.ui.internal.navigator.extensions.SafeDelegateTreeContentProvider;
38 import org.eclipse.ui.navigator.CommonViewer;
39 import org.eclipse.ui.navigator.INavigatorContentDescriptor;
40 import org.eclipse.ui.navigator.INavigatorViewerDescriptor;
41 import org.eclipse.ui.navigator.IPipelinedTreeContentProvider;
42 import org.eclipse.ui.navigator.OverridePolicy;
43 
44 /**
45  * <p>
46  * Provides relevant content based on the associated
47  * {@link org.eclipse.ui.internal.navigator.NavigatorContentService}&nbsp; for a
48  * TreeViewer .
49  * </p>
50  * <p>
51  * Except for the dependency on
52  * {@link org.eclipse.ui.internal.navigator.NavigatorContentService}, this class
53  * has no dependencies on the rest of the Common Navigator framework. Tree
54  * viewers that would like to use the extensions defined by the Common
55  * Navigator, without using the actual view part or other pieces of
56  * functionality (filters, sorting, etc) may choose to use this class, in effect
57  * using an extensible, aggregating, delegate content provider.
58  * </p>
59  *
60  * @see org.eclipse.ui.internal.navigator.NavigatorContentService
61  * @see org.eclipse.ui.internal.navigator.NavigatorContentServiceLabelProvider
62  *
63  * @since 3.2
64  *
65  */
66 public class NavigatorContentServiceContentProvider implements ITreeContentProvider, ITreePathContentProvider {
67 
68 	private static final Object[] NO_CHILDREN = new Object[0];
69 
70 	private final NavigatorContentService contentService;
71 
72 	private boolean disposeContentService;
73 
74 	private final boolean enforceHasChildren;
75 
76 	private Viewer viewer;
77 
78 	/**
79 	 * <p>
80 	 * Creates a cached {@link NavigatorContentService}&nbsp;from the given
81 	 * viewer Id.
82 	 * </p>
83 	 *
84 	 * @param aViewerId
85 	 *            The associated viewer id that this
86 	 *            NavigatorContentServiceContentProvider will provide content
87 	 *            for
88 	 */
NavigatorContentServiceContentProvider(String aViewerId)89 	public NavigatorContentServiceContentProvider(String aViewerId) {
90 		this(new NavigatorContentService(aViewerId));
91 		disposeContentService = true;
92 	}
93 
94 	/**
95 	 * <p>
96 	 * Uses the supplied content service to acquire the available extensions.
97 	 * </p>
98 	 *
99 	 * @param aContentService
100 	 *            The associated NavigatorContentService that should be used to
101 	 *            acquire information.
102 	 */
NavigatorContentServiceContentProvider(NavigatorContentService aContentService)103 	public NavigatorContentServiceContentProvider(NavigatorContentService aContentService) {
104 		super();
105 		contentService = aContentService;
106 		INavigatorViewerDescriptor vDesc = contentService.getViewerDescriptor();
107 		enforceHasChildren = vDesc.getBooleanConfigProperty(NavigatorViewerDescriptor.PROP_ENFORCE_HAS_CHILDREN);
108 	}
109 
110 	@Override
inputChanged(Viewer aViewer, Object anOldInput, Object aNewInput)111 	public void inputChanged(Viewer aViewer, Object anOldInput, Object aNewInput) {
112 		viewer = aViewer;
113 		contentService.updateService(aViewer, anOldInput, aNewInput);
114 	}
115 
116 	@Override
getElements(Object anInputElement)117 	public Object[] getElements(Object anInputElement) {
118 		Set rootContentExtensions = contentService.findRootContentExtensions(anInputElement);
119 		return internalGetChildren(anInputElement, anInputElement, rootContentExtensions, ELEMENTS);
120 	}
121 
122 	@Override
getChildren(Object aParentElement)123 	public Object[] getChildren(Object aParentElement) {
124 		Set enabledExtensions = contentService.findContentExtensionsByTriggerPoint(aParentElement);
125 		return internalGetChildren(aParentElement, aParentElement, enabledExtensions, !ELEMENTS);
126 	}
127 
128 	@Override
getChildren(TreePath parentPath)129 	public Object[] getChildren(TreePath parentPath) {
130 		Object aParentElement = internalAsElement(parentPath);
131 		Set enabledExtensions = contentService.findContentExtensionsByTriggerPoint(aParentElement);
132 		return internalGetChildren(aParentElement, parentPath, enabledExtensions, !ELEMENTS);
133 	}
134 
135 	private static final boolean ELEMENTS = true;
136 
internalGetChildren(final Object aParentElement, final Object aParentElementOrPath, final Set enabledExtensions, final boolean elements)137 	private Object[] internalGetChildren(final Object aParentElement,
138 			final Object aParentElementOrPath, final Set enabledExtensions, final boolean elements) {
139 		if (enabledExtensions.isEmpty()) {
140 			return NO_CHILDREN;
141 		}
142 		final Set finalSet = new LinkedHashSet();
143 		final ContributorTrackingSet localSet = new ContributorTrackingSet(contentService);
144 
145 		for (final Iterator itr = enabledExtensions.iterator(); itr.hasNext();) {
146 			SafeRunner.run(new NavigatorSafeRunnable() {
147 				NavigatorContentExtension foundExtension = (NavigatorContentExtension) itr.next();
148 				Object[] contributedChildren = null;
149 				NavigatorContentExtension[] overridingExtensions;
150 
151 				@Override
152 				public void run() throws Exception {
153 					if (!isOverridingExtensionInSet(foundExtension.getDescriptor(),
154 							enabledExtensions)) {
155 						if (elements)
156 							contributedChildren = foundExtension.internalGetContentProvider()
157 									.getElements(aParentElementOrPath);
158 						else
159 							contributedChildren = foundExtension.internalGetContentProvider()
160 									.getChildren(aParentElementOrPath);
161 						overridingExtensions = foundExtension
162 								.getOverridingExtensionsForTriggerPoint(aParentElement);
163 						INavigatorContentDescriptor foundDescriptor = foundExtension
164 								.getDescriptor();
165 						localSet.setContributor(foundDescriptor, foundDescriptor);
166 						localSet.setContents(contributedChildren);
167 
168 						if (overridingExtensions.length > 0) {
169 							pipelineChildren(aParentElement, overridingExtensions, foundDescriptor,
170 									localSet, elements);
171 						}
172 						finalSet.addAll(localSet);
173 					}
174 				}
175 
176 				@Override
177 				public void handleException(Throwable e) {
178 					NavigatorPlugin.logError(0, NLS.bind(
179 							CommonNavigatorMessages.Exception_Invoking_Extension, new Object[] {
180 									foundExtension.getDescriptor().getId(), aParentElement }), e);
181 				}
182 			});
183 		}
184 
185 		return finalSet.toArray();
186 	}
187 
188 	/**
189 	 * Query each of <code>theOverridingExtensions</code> for children, and then
190 	 * pipe them through the Pipeline content provider.
191 	 *
192 	 * @param aParent
193 	 *            The parent element in the tree
194 	 * @param theOverridingExtensions
195 	 *            The set of overriding extensions that should participate in
196 	 *            the pipeline chain
197 	 * @param pipelinedChildren
198 	 *            The current children to return to the viewer (should be
199 	 *            modifiable)
200 	 */
pipelineChildren(Object aParent, NavigatorContentExtension[] theOverridingExtensions, INavigatorContentDescriptor firstClassDescriptor, ContributorTrackingSet pipelinedChildren, boolean elements)201 	private void pipelineChildren(Object aParent, NavigatorContentExtension[] theOverridingExtensions,
202 			INavigatorContentDescriptor firstClassDescriptor, ContributorTrackingSet pipelinedChildren, boolean elements) {
203 		IPipelinedTreeContentProvider pipelinedContentProvider;
204 		NavigatorContentExtension[] overridingExtensions;
205 		for (NavigatorContentExtension overridingExtension : theOverridingExtensions) {
206 
207 			if (overridingExtension.internalGetContentProvider().isPipelined()) {
208 				pipelinedContentProvider = overridingExtension.internalGetContentProvider();
209 				pipelinedChildren.setContributor(overridingExtension.getDescriptor(), firstClassDescriptor);
210 				if (elements) {
211 					pipelinedContentProvider.getPipelinedElements(aParent, pipelinedChildren);
212 				} else {
213 					pipelinedContentProvider.getPipelinedChildren(aParent, pipelinedChildren);
214 				}
215 				overridingExtensions = overridingExtension.getOverridingExtensionsForTriggerPoint(aParent);
216 				if (overridingExtensions.length > 0) {
217 					pipelineChildren(aParent, overridingExtensions, firstClassDescriptor, pipelinedChildren, elements);
218 				}
219 			}
220 		}
221 	}
222 
223 
224 	/**
225 	 * Currently this method only checks one level deep. If the suppressed
226 	 * extension of the given descriptor is contained lower in the tree, then
227 	 * the extension could still be invoked twice.
228 	 *
229 	 * @param aDescriptor
230 	 *            The descriptor which may be overriding other extensions.
231 	 * @param theEnabledExtensions
232 	 *            The other available extensions.
233 	 * @return True if the results should be pipelined through the downstream
234 	 *         extensions.
235 	 */
isOverridingExtensionInSet(INavigatorContentDescriptor aDescriptor, Set theEnabledExtensions)236 	private boolean isOverridingExtensionInSet(INavigatorContentDescriptor aDescriptor, Set theEnabledExtensions) {
237 
238 		if (aDescriptor.getSuppressedExtensionId() != null /*
239 															 * The descriptor is
240 															 * an override
241 															 * descriptor
242 															 */
243 				&& aDescriptor.getOverridePolicy() == OverridePolicy.InvokeAlwaysRegardlessOfSuppressedExt) {
244 			/*
245 			 * if the policy is set as such, it can lead to this extension being
246 			 * invoked twice; once as a first class extension, and once an
247 			 * overriding extension.
248 			 */
249 			if (theEnabledExtensions.contains(contentService.getExtension(aDescriptor.getOverriddenDescriptor()))) {
250 				return true;
251 			}
252 		}
253 		return false;
254 	}
255 
256 	/**
257 	 * Currently this method only checks one level deep. If the suppressed
258 	 * extension of the given descriptor is contained lower in the tree, then
259 	 * the extension could still be invoked twice.
260 	 *
261 	 * @param aDescriptor
262 	 *            The descriptor which may be overriding other extensions.
263 	 * @param theEnabledDescriptors
264 	 *            The other available descriptors.
265 	 * @return True if the results should be pipelined through the downstream
266 	 *         extensions.
267 	 */
isOverridingDescriptorInSet(INavigatorContentDescriptor aDescriptor, Set theEnabledDescriptors)268 	private boolean isOverridingDescriptorInSet(INavigatorContentDescriptor aDescriptor, Set theEnabledDescriptors) {
269 
270 		if (aDescriptor.getSuppressedExtensionId() != null /*
271 															 * The descriptor is
272 															 * an override
273 															 * descriptor
274 															 */
275 				&& aDescriptor.getOverridePolicy() == OverridePolicy.InvokeAlwaysRegardlessOfSuppressedExt) {
276 			/*
277 			 * if the policy is set as such, it can lead to this extension being
278 			 * invoked twice; once as a first class extension, and once an
279 			 * overriding extension.
280 			 */
281 			if (theEnabledDescriptors.contains(aDescriptor.getOverriddenDescriptor())) {
282 				return true;
283 			}
284 		}
285 		return false;
286 	}
287 
288 	@Override
getParent(final Object anElement)289 	public Object getParent(final Object anElement) {
290 		final Set extensions = contentService.findContentExtensionsWithPossibleChild(anElement);
291 		final Object[] parent = new Object[1];
292 
293 		for (Iterator itr = extensions.iterator(); itr.hasNext();) {
294 			final NavigatorContentExtension foundExtension = (NavigatorContentExtension) itr.next();
295 
296 			SafeRunner.run(new NavigatorSafeRunnable() {
297 				NavigatorContentExtension[] overridingExtensions;
298 
299 				@Override
300 				public void run() throws Exception {
301 					if (!isOverridingExtensionInSet(foundExtension.getDescriptor(), extensions)) {
302 						parent[0] = foundExtension.internalGetContentProvider()
303 								.getParent(anElement);
304 						overridingExtensions = foundExtension
305 								.getOverridingExtensionsForPossibleChild(anElement);
306 						if (overridingExtensions.length > 0) {
307 							parent[0] = pipelineParent(anElement, overridingExtensions, parent);
308 						}
309 					}
310 				}
311 
312 				@Override
313 				public void handleException(Throwable e) {
314 					NavigatorPlugin.logError(0, NLS.bind(
315 							CommonNavigatorMessages.Exception_Invoking_Extension, new Object[] {
316 									foundExtension.getDescriptor().getId(), anElement }), e);
317 				}
318 			});
319 
320 			if (parent[0] != null) {
321 				return parent[0];
322 			}
323 		}
324 		return parent[0];
325 	}
326 
327 	@Override
getParents(Object anElement)328 	public TreePath[] getParents(Object anElement) {
329 		List paths = new ArrayList();
330 		TreePathCompiler compiler = new TreePathCompiler(anElement);
331 		Set compilers = findPaths(compiler);
332 		for (Iterator iter = compilers.iterator(); iter.hasNext();) {
333 			TreePathCompiler c = (TreePathCompiler) iter.next();
334 			paths.add(c.createParentPath());
335 		}
336 		return (TreePath[]) paths.toArray(new TreePath[paths.size()]);
337 
338 	}
339 	/**
340 	 * Query each of <code>theOverridingExtensions</code> for elements, and then
341 	 * pipe them through the Pipeline content provider.
342 	 *
343 	 * @param anInputElement
344 	 *            The input element in the tree
345 	 * @param theOverridingExtensions
346 	 *            The set of overriding extensions that should participate in
347 	 *            the pipeline chain
348 	 * @param theCurrentParent
349 	 *            The current elements to return to the viewer (should be
350 	 *            modifiable)
351 	 * @return The set of elements to return to the viewer
352 	 */
pipelineParent(Object anInputElement, NavigatorContentExtension[] theOverridingExtensions, Object theCurrentParent)353 	private Object pipelineParent(Object anInputElement, NavigatorContentExtension[] theOverridingExtensions,
354 			Object theCurrentParent) {
355 		IPipelinedTreeContentProvider pipelinedContentProvider;
356 		NavigatorContentExtension[] overridingExtensions;
357 		Object aSuggestedParent = null;
358 		for (NavigatorContentExtension theOverridingExtension : theOverridingExtensions) {
359 
360 			if (theOverridingExtension.internalGetContentProvider().isPipelined()) {
361 				pipelinedContentProvider = theOverridingExtension
362 						.internalGetContentProvider();
363 
364 				aSuggestedParent = pipelinedContentProvider.getPipelinedParent(anInputElement, aSuggestedParent);
365 
366 				overridingExtensions = theOverridingExtension
367 						.getOverridingExtensionsForTriggerPoint(anInputElement);
368 				if (overridingExtensions.length > 0) {
369 					aSuggestedParent = pipelineParent(anInputElement, overridingExtensions, aSuggestedParent);
370 				}
371 			}
372 		}
373 		return aSuggestedParent != null ? aSuggestedParent : theCurrentParent;
374 	}
375 
376 	/**
377 	 * Calculate hasChildren for both an element or a path.
378 	 *
379 	 *  If any of the first class NCEs don't implement the IPipelinedTreeContentProviderHasChildren
380 	 *  and they return true, then we have to use that as the value, as we are obliged to take
381 	 *  the union of the non-pipelined calls. This may result in a false positive hasChildren
382 	 *  indication if the pipeline mechanism does not actually contribute children later.
383 	 *  For pipelined calls, we simply ask the pipelined content provider about the children
384 	 *  and they can override this as they would in the case where they are providing the objects.
385 	 */
386 	@Override
hasChildren(final Object anElementOrPath)387 	public boolean hasChildren(final Object anElementOrPath) {
388 		final Object anElement = internalAsElement(anElementOrPath);
389 		final Set enabledExtensions = contentService.findContentExtensionsByTriggerPoint(anElement);
390 		final boolean suggestedHasChildren[] = new boolean[1];
391 
392 		for (final Iterator itr = enabledExtensions.iterator(); itr.hasNext();) {
393 			SafeRunner.run(new NavigatorSafeRunnable() {
394 				NavigatorContentExtension ext;
395 
396 				@Override
397 				public void run() throws Exception {
398 					ext = (NavigatorContentExtension) itr.next();
399 
400 					if (!ext.isLoaded() && !enforceHasChildren) {
401 						suggestedHasChildren[0] = true;
402 						return;
403 					}
404 
405 					NavigatorContentExtension[] overridingExtensions;
406 					if (!isOverridingExtensionInSet(ext.getDescriptor(), enabledExtensions)) {
407 						SafeDelegateTreeContentProvider cp = ext.internalGetContentProvider();
408 						suggestedHasChildren[0] |= callNormalHasChildren(anElementOrPath,
409 								anElement, cp);
410 						overridingExtensions = ext
411 								.getOverridingExtensionsForTriggerPoint(anElement);
412 
413 						if (overridingExtensions.length > 0) {
414 							suggestedHasChildren[0] = pipelineHasChildren(anElementOrPath,
415 									anElement, overridingExtensions, suggestedHasChildren[0]);
416 						}
417 						if (suggestedHasChildren[0]) {
418 							return;
419 
420 						}
421 					}
422 				}
423 
424 				@Override
425 				public void handleException(Throwable e) {
426 					NavigatorPlugin.logError(0, NLS.bind(
427 							CommonNavigatorMessages.Exception_Invoking_Extension, new Object[] {
428 									ext.getDescriptor().getId(), anElementOrPath }), e);
429 				}
430 			});
431 		}
432 		return suggestedHasChildren[0];
433 	}
434 
435 	@Override
hasChildren(TreePath path)436 	public boolean hasChildren(TreePath path) {
437 		return hasChildren((Object)path);
438 	}
439 
callNormalHasChildren(Object anElementOrPath, Object anElement, SafeDelegateTreeContentProvider cp)440 	private boolean callNormalHasChildren(Object anElementOrPath, Object anElement, SafeDelegateTreeContentProvider cp) {
441 		if (cp.isTreePath() && anElementOrPath instanceof TreePath) {
442 			ITreePathContentProvider tpcp = cp;
443 			return tpcp.hasChildren((TreePath) anElementOrPath);
444 		}
445 		return ((ITreeContentProvider) cp).hasChildren(anElement);
446 	}
447 
pipelineHasChildren(Object anElementOrPath, Object anElement, NavigatorContentExtension[] theOverridingExtensions, boolean suggestedHasChildren)448 	private boolean pipelineHasChildren(Object anElementOrPath, Object anElement,
449 			NavigatorContentExtension[] theOverridingExtensions, boolean suggestedHasChildren) {
450 		NavigatorContentExtension[] overridingExtensions;
451 		for (NavigatorContentExtension theOverridingExtension : theOverridingExtensions) {
452 
453 			SafeDelegateTreeContentProvider cp = theOverridingExtension.internalGetContentProvider();
454 			if (cp.isPipelinedHasChildren()) {
455 				suggestedHasChildren = cp.hasPipelinedChildren(
456 						anElement, suggestedHasChildren);
457 				overridingExtensions = theOverridingExtension
458 						.getOverridingExtensionsForTriggerPoint(anElement);
459 				if (overridingExtensions.length > 0) {
460 					suggestedHasChildren = pipelineHasChildren(anElementOrPath, anElement,
461 							overridingExtensions, suggestedHasChildren);
462 				}
463 			} else  {
464 				suggestedHasChildren |= callNormalHasChildren(anElementOrPath, anElement, cp);
465 			}
466 		}
467 		return suggestedHasChildren;
468 	}
469 
470 	/**
471 	 * <p>
472 	 * Handles any necessary clean up of the {@link NavigatorContentService}
473 	 * </p>
474 	 *
475 	 * <p>
476 	 * <b>If a client uses this class outside of the framework of
477 	 * {@link CommonViewer}, the client must ensure that this method is called
478 	 * when finished. </b>
479 	 * </p>
480 	 *
481 	 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
482 	 */
483 	@Override
dispose()484 	public void dispose() {
485 		if (disposeContentService) {
486 			contentService.dispose();
487 		}
488 	}
489 
490 	/**
491 	 * Get the element from an element or tree path argument.
492 	 *
493 	 * @param parentElementOrPath
494 	 *            the element or tree path
495 	 * @return the element
496 	 */
internalAsElement(Object parentElementOrPath)497 	private Object internalAsElement(Object parentElementOrPath) {
498 		if (parentElementOrPath instanceof TreePath) {
499 			TreePath tp = (TreePath) parentElementOrPath;
500 			if (tp.getSegmentCount() > 0) {
501 				return tp.getLastSegment();
502 			}
503 			// If the path is empty, the parent element is the root
504 			return viewer.getInput();
505 		}
506 		return parentElementOrPath;
507 	}
508 
509 	static class CyclicPathException extends Exception {
510 
511 		private static final long serialVersionUID = 2111962579612444989L;
512 
CyclicPathException(TreePathCompiler compiler, Object invalidSegment, boolean asChild)513 		protected CyclicPathException(TreePathCompiler compiler, Object invalidSegment, boolean asChild) {
514 			super("Cannot add " + invalidSegment + //$NON-NLS-1$
515 					" to the list of segments in " + compiler + //$NON-NLS-1$
516 					(asChild ? " as a child." : " as a parent.")); //$NON-NLS-1$ //$NON-NLS-2$
517 		}
518 	}
519 
520 	class TreePathCompiler {
521 
522 		private final LinkedList segments = new LinkedList();
523 
TreePathCompiler(Object segment)524 		protected TreePathCompiler(Object segment) {
525 			segments.add(segment);
526 		}
527 
TreePathCompiler(TreePathCompiler aCompiler)528 		protected TreePathCompiler(TreePathCompiler aCompiler) {
529 			segments.addAll(aCompiler.segments);
530 		}
531 
TreePathCompiler(TreePath aPath)532 		protected TreePathCompiler(TreePath aPath) {
533 			for (int i = 0; i < aPath.getSegmentCount(); i++) {
534 				segments.addLast(aPath.getSegment(i));
535 			}
536 		}
537 
addParent(Object segment)538 		protected void addParent(Object segment) throws CyclicPathException {
539 			if (segments.contains(segment)) {
540 				throw new CyclicPathException(this, segment, false);
541 			}
542 			segments.addFirst(segment);
543 		}
544 
addChild(Object segment)545 		protected void addChild(Object segment) throws CyclicPathException {
546 			if (segments.contains(segment)) {
547 				throw new CyclicPathException(this, segment, false);
548 			}
549 			segments.addLast(segment);
550 		}
551 
552 		/**
553 		 * Create the full tree path.
554 		 *
555 		 * @return A TreePath with all segments from the compiler.
556 		 */
createPath()557 		public TreePath createPath() {
558 			return new TreePath(segments.toArray());
559 		}
560 
561 		/**
562 		 * Create parent tree path.
563 		 *
564 		 * @return A TreePath with all segments but the last from the compiler
565 		 */
createParentPath()566 		public TreePath createParentPath() {
567 			LinkedList parentSegments = new LinkedList(segments);
568 			parentSegments.removeLast();
569 			return new TreePath(parentSegments.toArray());
570 		}
571 
getLastSegment()572 		public Object getLastSegment() {
573 			return segments.getLast();
574 		}
575 
getFirstSegment()576 		public Object getFirstSegment() {
577 			return segments.getFirst();
578 		}
579 
580 		@Override
toString()581 		public String toString() {
582 
583 			StringBuilder buffer = new StringBuilder();
584 			for (Iterator iter = segments.iterator(); iter.hasNext();) {
585 				Object segment = iter.next();
586 				buffer.append(segment).append("::"); //$NON-NLS-1$
587 			}
588 			return buffer.toString();
589 		}
590 
591 	}
592 
findPaths(TreePathCompiler aPathCompiler)593 	private Set findPaths(TreePathCompiler aPathCompiler) {
594 
595 		Set/* <Object> */parents = findParents(aPathCompiler.getFirstSegment());
596 		Set/* <TreePathCompiler> */parentPaths = new LinkedHashSet();
597 		Set/* <TreePathCompiler> */foundPaths = Collections.EMPTY_SET;
598 		if (parents.size() > 0) {
599 			for (Iterator parentIter = parents.iterator(); parentIter.hasNext();) {
600 				Object parent = parentIter.next();
601 				TreePathCompiler c = new TreePathCompiler(aPathCompiler);
602 				try {
603 					c.addParent(parent);
604 					foundPaths = findPaths(c);
605 				} catch (CyclicPathException cpe) {
606 					String msg = cpe.getMessage() != null ? cpe.getMessage() : cpe.toString();
607 					NavigatorPlugin.logError(0, msg, cpe);
608 				}
609 				if (foundPaths.isEmpty())
610 					parentPaths.add(c);
611 				else
612 					parentPaths.addAll(foundPaths);
613 			}
614 		}
615 		return parentPaths;
616 
617 	}
618 
findParents(final Object anElement)619 	private Set findParents(final Object anElement) {
620 		final Set descriptors = contentService.findDescriptorsWithPossibleChild(anElement, false);
621 		final Set parents = new LinkedHashSet();
622 
623 		for (final Iterator itr = descriptors.iterator(); itr.hasNext();) {
624 			SafeRunner.run(new NavigatorSafeRunnable() {
625 				NavigatorContentDescriptor foundDescriptor;
626 				NavigatorContentExtension foundExtension;
627 				Object parent = null;
628 
629 				@Override
630 				public void run() throws Exception {
631 					foundDescriptor = (NavigatorContentDescriptor) itr.next();
632 					foundExtension = contentService.getExtension(foundDescriptor);
633 
634 					if (!isOverridingDescriptorInSet(foundExtension.getDescriptor(), descriptors)) {
635 						if (foundExtension.internalGetContentProvider().isTreePath()) {
636 							TreePath[] parentTreePaths = ((ITreePathContentProvider) foundExtension
637 									.internalGetContentProvider()).getParents(anElement);
638 
639 							for (TreePath parentTreePath : parentTreePaths) {
640 
641 								parent = parentTreePath.getLastSegment();
642 								if ((parent = findParent(foundExtension, anElement, parent)) != null)
643 									parents.add(parent);
644 							}
645 
646 						} else {
647 							parent = foundExtension.internalGetContentProvider().getParent(
648 									anElement);
649 							if ((parent = findParent(foundExtension, anElement, parent)) != null)
650 								parents.add(parent);
651 						}
652 					}
653 				}
654 
655 				@Override
656 				public void handleException(Throwable e) {
657 					NavigatorPlugin.logError(0, NLS.bind(
658 							CommonNavigatorMessages.Exception_Invoking_Extension, new Object[] {
659 									foundExtension.getDescriptor().getId(), anElement }), e);
660 				}
661 			});
662 		}
663 
664 		return parents;
665 
666 	}
667 
findParent(NavigatorContentExtension anExtension, Object anElement, Object aSuggestedParent)668 	private Object findParent(NavigatorContentExtension anExtension, Object anElement, Object aSuggestedParent) {
669 
670 		/* the last valid (non-null) parent for the anElement */
671 		Object lastValidParent = aSuggestedParent;
672 		/* used to keep track of new suggestions */
673 		Object suggestedOverriddenParent = null;
674 		IPipelinedTreeContentProvider piplineContentProvider;
675 		NavigatorContentExtension[] overridingExtensions = anExtension
676 				.getOverridingExtensionsForPossibleChild(anElement);
677 		for (NavigatorContentExtension overridingExtension : overridingExtensions) {
678 			if (overridingExtension.internalGetContentProvider().isPipelined()) {
679 				piplineContentProvider = overridingExtension.internalGetContentProvider();
680 				suggestedOverriddenParent = piplineContentProvider.getPipelinedParent(anElement, lastValidParent);
681 
682 				if (suggestedOverriddenParent != null && !suggestedOverriddenParent.equals(aSuggestedParent))
683 					lastValidParent = suggestedOverriddenParent;
684 
685 				// should never return null
686 				lastValidParent = findParent(overridingExtension, anElement, lastValidParent);
687 			}
688 
689 		}
690 		return lastValidParent;
691 	}
692 
693 }
694