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 * <navigatorContent/> 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