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  *******************************************************************************/
14 package org.eclipse.ui.internal.navigator.extensions;
15 
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.ListIterator;
20 import java.util.Set;
21 import java.util.TreeSet;
22 
23 import org.eclipse.core.expressions.ElementHandler;
24 import org.eclipse.core.expressions.EvaluationResult;
25 import org.eclipse.core.expressions.Expression;
26 import org.eclipse.core.expressions.ExpressionConverter;
27 import org.eclipse.core.expressions.IEvaluationContext;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.IConfigurationElement;
30 import org.eclipse.core.runtime.IStatus;
31 import org.eclipse.jface.viewers.ILabelProvider;
32 import org.eclipse.jface.viewers.IStructuredSelection;
33 import org.eclipse.jface.viewers.ITreeContentProvider;
34 import org.eclipse.osgi.util.NLS;
35 import org.eclipse.ui.IPluginContribution;
36 import org.eclipse.ui.WorkbenchException;
37 import org.eclipse.ui.internal.navigator.CommonNavigatorMessages;
38 import org.eclipse.ui.internal.navigator.CustomAndExpression;
39 import org.eclipse.ui.internal.navigator.NavigatorPlugin;
40 import org.eclipse.ui.internal.navigator.Policy;
41 import org.eclipse.ui.navigator.ICommonContentProvider;
42 import org.eclipse.ui.navigator.ICommonLabelProvider;
43 import org.eclipse.ui.navigator.INavigatorContentDescriptor;
44 import org.eclipse.ui.navigator.OverridePolicy;
45 import org.eclipse.ui.navigator.Priority;
46 
47 /**
48  * Encapsulates the <code>org.eclipse.ui.navigator.navigatorContent</code>
49  * extension point.
50  *
51  * @since 3.2
52  */
53 public final class NavigatorContentDescriptor implements
54 		INavigatorContentDescriptor, INavigatorContentExtPtConstants {
55 
56 	private static final int HASH_CODE_NOT_COMPUTED = -1;
57 	private String id;
58 
59 	private String name;
60 
61 	private IConfigurationElement configElement;
62 
63 	private int priority = Priority.NORMAL_PRIORITY_VALUE;
64 
65 	/**
66 	 * This is calculated based on the priority and appearsBeforeId when all of the descriptors
67 	 * are first loaded. This is what's used to sort on after that.
68 	 */
69 	private int sequenceNumber;
70 
71 	private String appearsBeforeId;
72 
73 	private Expression enablement;
74 
75 	private Expression possibleChildren;
76 
77 	private Expression initialActivation;
78 
79 	private String icon;
80 
81 	private boolean activeByDefault;
82 
83 	private IPluginContribution contribution;
84 
85 	private boolean sortOnly;
86 
87 	private Set overridingExtensions;
88 	private List overridingExtensionsList; // FIXME: will replace 'overridingExtensions' in 3.6
89 
90 	private OverridePolicy overridePolicy;
91 
92 	private String suppressedExtensionId;
93 
94 	private INavigatorContentDescriptor overriddenDescriptor;
95 
96 	private int hashCode = HASH_CODE_NOT_COMPUTED;
97 
98 	private boolean providesSaveables;
99 
100 	/**
101 	 * Creates a new content descriptor from a configuration element.
102 	 *
103 	 * @param configElement
104 	 *            configuration element to create a descriptor from
105 	 *
106 	 * @throws WorkbenchException
107 	 *             if the configuration element could not be parsed. Reasons
108 	 *             include:
109 	 *             <ul>
110 	 *             <li>A required attribute is missing.</li>
111 	 *             <li>More elements are define than is allowed.</li>
112 	 *             </ul>
113 	 */
NavigatorContentDescriptor(IConfigurationElement configElement)114 	/* package */ NavigatorContentDescriptor(IConfigurationElement configElement)
115 			throws WorkbenchException {
116 		super();
117 		this.configElement = configElement;
118 		init();
119 	}
120 
121 	@Override
getId()122 	public String getId() {
123 		return id;
124 	}
125 
126 	@Override
getName()127 	public String getName() {
128 		return name;
129 	}
130 
131 	@Override
getPriority()132 	public int getPriority() {
133 		return priority;
134 	}
135 
136 	/**
137 	 * @return the sequence number
138 	 */
139 	@Override
getSequenceNumber()140 	public int getSequenceNumber() {
141 		return sequenceNumber;
142 	}
143 
setSequenceNumber(int num)144 	void setSequenceNumber(int num) {
145 		sequenceNumber = num;
146 	}
147 
148 	/**
149 	 *
150 	 * @return The value specified by the <i>appearsBefore</i> attribute of the
151 	 *         &lt;navigatorContent/&gt; element.
152 	 */
153 	@Override
getAppearsBeforeId()154 	public String getAppearsBeforeId() {
155 		return appearsBeforeId;
156 	}
157 
158 	@Override
isSortOnly()159 	public boolean isSortOnly() {
160 		return sortOnly;
161 	}
162 
163 	/**
164 	 * Parses the configuration element.
165 	 *
166 	 * @throws WorkbenchException
167 	 *             if the configuration element could not be parsed. Reasons
168 	 *             include:
169 	 *             <ul>
170 	 *             <li>A required attribute is missing.</li>
171 	 *             <li>More elements are define than is allowed.</li>
172 	 *             </ul>
173 	 */
init()174 	private void init() throws WorkbenchException {
175 		id = configElement.getAttribute(ATT_ID);
176 		name = configElement.getAttribute(ATT_NAME);
177 		String priorityString = configElement.getAttribute(ATT_PRIORITY);
178 		icon = configElement.getAttribute(ATT_ICON);
179 
180 		String activeByDefaultString = configElement
181 				.getAttribute(ATT_ACTIVE_BY_DEFAULT);
182 		activeByDefault = (activeByDefaultString != null && activeByDefaultString
183 				.length() > 0) ? Boolean.valueOf(activeByDefaultString)
184 				.booleanValue() : true;
185 
186 		String providesSaveablesString = configElement
187 			.getAttribute(ATT_PROVIDES_SAVEABLES);
188 		providesSaveables = (providesSaveablesString != null && providesSaveablesString
189 				.length() > 0) ? Boolean.valueOf(providesSaveablesString)
190 						.booleanValue() : false;
191 		appearsBeforeId = configElement.getAttribute(ATT_APPEARS_BEFORE);
192 
193 		if (priorityString != null) {
194 			try {
195 				Priority p = Priority.get(priorityString);
196 				priority = p != null ? p.getValue()
197 						: Priority.NORMAL_PRIORITY_VALUE;
198 			} catch (NumberFormatException exception) {
199 				priority = Priority.NORMAL_PRIORITY_VALUE;
200 			}
201 		}
202 
203 		// We start with this because the sort ExtensionPriorityComparator works
204 		// from the sequenceNumber
205 		sequenceNumber = priority;
206 
207 		String sortOnlyString = configElement.getAttribute(ATT_SORT_ONLY);
208 		sortOnly = (sortOnlyString != null && sortOnlyString.length() > 0) ? Boolean.valueOf(
209 				sortOnlyString).booleanValue() : false;
210 
211 		if (id == null) {
212 			throw new WorkbenchException(NLS.bind(
213 					CommonNavigatorMessages.Attribute_Missing_Warning,
214 					new Object[] {
215 							ATT_ID,
216 							id,
217 							configElement.getDeclaringExtension()
218 									.getContributor().getName() }));
219 		}
220 
221 		contribution = new IPluginContribution() {
222 
223 			@Override
224 			public String getLocalId() {
225 				return getId();
226 			}
227 
228 			@Override
229 			public String getPluginId() {
230 				return configElement.getDeclaringExtension().getContributor().getName();
231 			}
232 
233 		};
234 
235 		IConfigurationElement[] children;
236 
237 		children = configElement.getChildren(TAG_INITIAL_ACTIVATION);
238 		if (children.length > 0) {
239 			if (children.length == 1) {
240 				initialActivation = new CustomAndExpression(children[0]);
241 			} else {
242 				throw new WorkbenchException(NLS.bind(
243 						CommonNavigatorMessages.Attribute_Missing_Warning, new Object[] {
244 								TAG_INITIAL_ACTIVATION, id,
245 								configElement.getDeclaringExtension().getContributor().getName() }));
246 			}
247 		}
248 
249 		if (sortOnly)
250 			return;
251 
252 		children = configElement.getChildren(TAG_ENABLEMENT);
253 		if (children.length == 0) {
254 
255 			children = configElement.getChildren(TAG_TRIGGER_POINTS);
256 			if (children.length == 1) {
257 				enablement = new CustomAndExpression(children[0]);
258 			} else {
259 				throw new WorkbenchException(NLS.bind(
260 						CommonNavigatorMessages.Attribute_Missing_Warning,
261 						new Object[] {
262 								TAG_TRIGGER_POINTS,
263 								id,
264 								configElement.getDeclaringExtension()
265 										.getContributor().getName() }));
266 			}
267 
268 			children = configElement.getChildren(TAG_POSSIBLE_CHILDREN);
269 			if (children.length == 1) {
270 				possibleChildren = new CustomAndExpression(children[0]);
271 			} else if(children.length > 1){
272 				throw new WorkbenchException(NLS.bind(
273 						CommonNavigatorMessages.Attribute_Missing_Warning,
274 						new Object[] {
275 								TAG_POSSIBLE_CHILDREN,
276 								id,
277 								configElement.getDeclaringExtension()
278 										.getContributor().getName() }));
279 			}
280 		} else if (children.length == 1) {
281 			try {
282 				enablement = ElementHandler.getDefault().create(
283 						ExpressionConverter.getDefault(), children[0]);
284 			} catch (CoreException e) {
285 				NavigatorPlugin.log(IStatus.ERROR, 0, e.getMessage(), e);
286 			}
287 		} else if (children.length > 1) {
288 			throw new WorkbenchException(NLS.bind(
289 					CommonNavigatorMessages.Attribute_Missing_Warning,
290 					new Object[] {
291 							TAG_ENABLEMENT,
292 							id,
293 							configElement.getDeclaringExtension()
294 									.getContributor().getName() }));
295 		}
296 
297 		children = configElement.getChildren(TAG_OVERRIDE);
298 		if (children.length == 0) {
299 			overridePolicy = OverridePolicy.get(OverridePolicy.InvokeAlwaysRegardlessOfSuppressedExt_LITERAL);
300 		} else if (children.length == 1) {
301 			suppressedExtensionId = children[0]
302 					.getAttribute(ATT_SUPPRESSED_EXT_ID);
303 			overridePolicy = OverridePolicy.get(children[0]
304 					.getAttribute(ATT_POLICY));
305 		} else if (children.length > 1) {
306 			throw new WorkbenchException(NLS.bind(
307 					CommonNavigatorMessages.Too_many_elements_Warning,
308 					new Object[] {
309 							TAG_OVERRIDE,
310 							id,configElement.getDeclaringExtension()
311 									.getContributor().getName() }));
312 		}
313 
314 	}
315 
316 	/**
317 	 * @return Returns the icon.
318 	 */
getIcon()319 	public String getIcon() {
320 		return icon;
321 	}
322 
323 	/**
324 	 * @return Returns the suppressedExtensionId or null if none specified.
325 	 */
326 	@Override
getSuppressedExtensionId()327 	public String getSuppressedExtensionId() {
328 		return suppressedExtensionId;
329 	}
330 
331 	/**
332 	 * @return Returns the overridePolicy or null if this extension does not
333 	 *         override another extension.
334 	 */
335 	@Override
getOverridePolicy()336 	public OverridePolicy getOverridePolicy() {
337 		return overridePolicy;
338 	}
339 
340 	/**
341 	 * @return Returns the contribution.
342 	 */
getContribution()343 	public IPluginContribution getContribution() {
344 		return contribution;
345 	}
346 
347 	/**
348 	 * @return the configuration element
349 	 */
getConfigElement()350 	public IConfigurationElement getConfigElement() {
351 		return configElement;
352 	}
353 
354 	/**
355 	 * The content provider could be an instance of
356 	 * {@link ICommonContentProvider}, but only {@link ITreeContentProvider} is
357 	 * required.
358 	 *
359 	 *
360 	 * @return An instance of the Content provider defined for this extension.
361 	 * @throws CoreException
362 	 *             if an instance of the executable extension could not be
363 	 *             created for any reason
364 	 *
365 	 */
createContentProvider()366 	public ITreeContentProvider createContentProvider() throws CoreException {
367 		if (Policy.DEBUG_EXTENSION_SETUP)
368 			System.out.println("createContentProvider: " + this); //$NON-NLS-1$
369 		return (ITreeContentProvider) configElement
370 				.createExecutableExtension(ATT_CONTENT_PROVIDER);
371 	}
372 
373 	/**
374 	 *
375 	 * The content provider could be an instance of {@link ICommonLabelProvider},
376 	 * but only {@link ILabelProvider} is required.
377 	 *
378 	 * @return An instance of the Label provider defined for this extension
379 	 * @throws CoreException
380 	 *             if an instance of the executable extension could not be
381 	 *             created for any reason
382 	 */
createLabelProvider()383 	public ILabelProvider createLabelProvider() throws CoreException {
384 		if (Policy.DEBUG_EXTENSION_SETUP)
385 			System.out.println("createLabelProvider: " + this); //$NON-NLS-1$
386 		return (ILabelProvider) configElement
387 				.createExecutableExtension(ATT_LABEL_PROVIDER);
388 	}
389 
390 	@Override
isActiveByDefault()391 	public boolean isActiveByDefault() {
392 		if (activeByDefault)
393 			return true;
394 		if (initialActivation == null)
395 			return false;
396 		IEvaluationContext context = NavigatorPlugin.getEvalContext(new Object());
397 		return NavigatorPlugin.safeEvaluate(initialActivation, context) == EvaluationResult.TRUE;
398 	}
399 
400 	/**
401 	 * Determine if this content extension would be able to provide children for
402 	 * the given element.
403 	 *
404 	 * @param anElement
405 	 *            The element that should be used for the evaluation.
406 	 * @return True if and only if the extension is enabled for the element.
407 	 */
408 	@Override
isTriggerPoint(Object anElement)409 	public boolean isTriggerPoint(Object anElement) {
410 
411 		if (enablement == null || anElement == null) {
412 			return false;
413 		}
414 
415 		IEvaluationContext context = NavigatorPlugin.getEvalContext(anElement);
416 		return NavigatorPlugin.safeEvaluate(enablement, context) == EvaluationResult.TRUE;
417 	}
418 
419 	/**
420 	 * Determine if this content extension could provide the given element as a
421 	 * child.
422 	 *
423 	 * <p>
424 	 * This method is used to determine what the parent of an element could be
425 	 * for Link with Editor support.
426 	 * </p>
427 	 *
428 	 * @param anElement
429 	 *            The element that should be used for the evaluation.
430 	 * @return True if and only if the extension might provide an object of this
431 	 *         type as a child.
432 	 */
433 	@Override
isPossibleChild(Object anElement)434 	public boolean isPossibleChild(Object anElement) {
435 
436 		if ((enablement == null && possibleChildren == null)
437 				|| anElement == null) {
438 			return false;
439 		} else if(anElement instanceof IStructuredSelection) {
440 			return arePossibleChildren((IStructuredSelection) anElement);
441 		}
442 
443 		IEvaluationContext context = NavigatorPlugin.getEvalContext(anElement);
444 		if (possibleChildren != null) {
445 			return NavigatorPlugin.safeEvaluate(possibleChildren, context) == EvaluationResult.TRUE;
446 		} else if (enablement != null) {
447 			return NavigatorPlugin.safeEvaluate(enablement, context) == EvaluationResult.TRUE;
448 		}
449 		return false;
450 	}
451 
452 	/**
453 	 * A convenience method to check all elements in a selection.
454 	 *
455 	 * @param aSelection A non-null selection
456 	 * @return True if and only if every element in the selection is a possible child.
457 	 */
458 	@Override
arePossibleChildren(IStructuredSelection aSelection)459 	public boolean arePossibleChildren(IStructuredSelection aSelection) {
460 		if(aSelection.isEmpty()) {
461 			return false;
462 		}
463 		for (Object element : aSelection) {
464 			if(!isPossibleChild(element)) {
465 				return false;
466 			}
467 		}
468 		return true;
469 	}
470 
471 	/**
472 	 *
473 	 * Does not force the creation of the set of overriding extensions.
474 	 *
475 	 * @return True if this extension has overriding extensions.
476 	 */
477 	@Override
hasOverridingExtensions()478 	public boolean hasOverridingExtensions() {
479 		return overridingExtensions != null && overridingExtensions.size() > 0;
480 	}
481 
482 	/**
483 	 * @return The set of overriding extensions (of type
484 	 *         {@link INavigatorContentDescriptor}
485 	 */
486 	@Override
getOverriddingExtensions()487 	public Set getOverriddingExtensions() {
488 		if (overridingExtensions == null) {
489 			overridingExtensions = new TreeSet(ExtensionSequenceNumberComparator.DESCENDING);
490 		}
491 		return overridingExtensions;
492 	}
493 
494 	/**
495 	 *  Returns a list iterator over the overriding extensions.
496 	 *
497 	 * @param fromStart
498 	 *            <code>true</code> if list iterator starts at the beginning and
499 	 *            <code>false</code> if it starts at the end of the list
500 	 * @return a list iterator over the overriding extensions which are ordered
501 	 *         by ExtensionPriorityComparator.DESCENDING
502 	 */
getOverridingExtensionsListIterator(boolean fromStart)503 	public ListIterator getOverridingExtensionsListIterator(boolean fromStart) {
504 		if (overridingExtensions == null)
505 			return Collections.EMPTY_LIST.listIterator();
506 
507 		if (overridingExtensionsList == null)
508 			overridingExtensionsList = new ArrayList(overridingExtensions);
509 
510 		return overridingExtensionsList.listIterator(fromStart ? 0 : overridingExtensionsList.size());
511 	}
512 
513 	@Override
toString()514 	public String toString() {
515 		return "Content[" + id  + "(" + sequenceNumber + ") " + ", \"" + name + "\"]"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
516 	}
517 
518 	@Override
hashCode()519 	public int hashCode() {
520 		if (hashCode == HASH_CODE_NOT_COMPUTED) {
521 			String hashCodeString = configElement.getNamespaceIdentifier() + getId();
522 			hashCode = hashCodeString.hashCode();
523 			if (hashCode == HASH_CODE_NOT_COMPUTED)
524 				hashCode++;
525 		}
526 		return hashCode;
527 	}
528 
529 	/**
530 	 * @return The descriptor of the <code>suppressedExtensionId</code> if
531 	 *         non-null.
532 	 */
533 	@Override
getOverriddenDescriptor()534 	public INavigatorContentDescriptor getOverriddenDescriptor() {
535 		return overriddenDescriptor;
536 	}
537 
538 	/**
539 	 * @param theOverriddenDescriptor
540 	 *            The overriddenDescriptor to set.
541 	 */
setOverriddenDescriptor( INavigatorContentDescriptor theOverriddenDescriptor)542 	/* package */void setOverriddenDescriptor(
543 			INavigatorContentDescriptor theOverriddenDescriptor) {
544 		overriddenDescriptor = theOverriddenDescriptor;
545 	}
546 
547 	@Override
hasSaveablesProvider()548 	public boolean hasSaveablesProvider() {
549 		return providesSaveables;
550 	}
551 
552 }
553